Compare commits
32
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11af286501 | ||
|
|
613d928149 | ||
|
|
7ecc7d33d4 | ||
|
|
9c8c18919f | ||
|
|
69fd525b8d | ||
|
|
1df783d0ff | ||
|
|
8a5afae48a | ||
|
|
15e0a2eb73 | ||
|
|
bf685c5702 | ||
|
|
d1fd6bc8cb | ||
|
|
6c8daab1bd | ||
|
|
3e1f06913f | ||
|
|
1e80c6dff8 | ||
|
|
6482fc9f59 | ||
|
|
31d6fc94fe | ||
|
|
8e89c71a34 | ||
|
|
1ad9376c4b | ||
|
|
3ddc5a8d50 | ||
|
|
6fd3aad81d | ||
|
|
520183c39f | ||
|
|
86078d8b90 | ||
|
|
caa27d6fdb | ||
|
|
24b1dd2ed8 | ||
|
|
c1f4096e11 | ||
|
|
db0ddfe009 | ||
|
|
9ea94e9963 | ||
|
|
c72ae603c1 | ||
|
|
5e8373fcac | ||
|
|
b48b3cbc35 | ||
|
|
515e378b4b | ||
|
|
583668f750 | ||
|
|
d109c0cd7d |
@@ -11,7 +11,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
steps:
|
steps:
|
||||||
- name: Add comment
|
- name: Add comment
|
||||||
uses: peter-evans/create-or-update-comment@fdb73c443d3a4f66832374f01fb9a713fad84937
|
uses: peter-evans/create-or-update-comment@2ef7be1cfae84b02f7b4e47ce45c1f93762d1d92
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.issue.number }}
|
issue-number: ${{ github.event.issue.number }}
|
||||||
body: |
|
body: |
|
||||||
|
|||||||
+7
-3
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/owncast/owncast/config"
|
"github.com/owncast/owncast/config"
|
||||||
"github.com/owncast/owncast/core/chat/events"
|
"github.com/owncast/owncast/core/chat/events"
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
|
"github.com/owncast/owncast/persistence/chatmessagerepository"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
"github.com/owncast/owncast/persistence/configrepository"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
@@ -103,7 +104,8 @@ func SendSystemMessage(text string, ephemeral bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !ephemeral {
|
if !ephemeral {
|
||||||
saveEvent(message.ID, nil, message.Body, message.GetMessageType(), nil, message.Timestamp, nil, nil, nil, nil)
|
chatMessageRepository := chatmessagerepository.Get()
|
||||||
|
chatMessageRepository.SaveEvent(message.ID, nil, message.Body, message.GetMessageType(), nil, message.Timestamp, nil, nil, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -131,7 +133,8 @@ func SendFediverseAction(eventType string, userAccountName string, image *string
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
saveFederatedAction(message)
|
chatMessageRepository := chatmessagerepository.Get()
|
||||||
|
chatMessageRepository.SaveFederatedAction(message)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -152,7 +155,8 @@ func SendSystemAction(text string, ephemeral bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !ephemeral {
|
if !ephemeral {
|
||||||
saveEvent(message.ID, nil, message.Body, message.GetMessageType(), nil, message.Timestamp, nil, nil, nil, nil)
|
chatMessageRepository := chatmessagerepository.Get()
|
||||||
|
chatMessageRepository.SaveEvent(message.ID, nil, message.Body, message.GetMessageType(), nil, message.Timestamp, nil, nil, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+3
-2
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/owncast/owncast/config"
|
"github.com/owncast/owncast/config"
|
||||||
"github.com/owncast/owncast/core/chat/events"
|
"github.com/owncast/owncast/core/chat/events"
|
||||||
"github.com/owncast/owncast/core/webhooks"
|
"github.com/owncast/owncast/core/webhooks"
|
||||||
|
"github.com/owncast/owncast/persistence/chatmessagerepository"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
"github.com/owncast/owncast/persistence/configrepository"
|
||||||
"github.com/owncast/owncast/persistence/userrepository"
|
"github.com/owncast/owncast/persistence/userrepository"
|
||||||
"github.com/owncast/owncast/utils"
|
"github.com/owncast/owncast/utils"
|
||||||
@@ -172,8 +173,8 @@ func (s *Server) userMessageSent(eventData chatClientEvent) {
|
|||||||
// Send chat message sent webhook
|
// Send chat message sent webhook
|
||||||
webhooks.SendChatEvent(&event)
|
webhooks.SendChatEvent(&event)
|
||||||
chatMessagesSentCounter.Inc()
|
chatMessagesSentCounter.Inc()
|
||||||
|
chatMessageRepository := chatmessagerepository.Get()
|
||||||
SaveUserMessage(event)
|
chatMessageRepository.SaveUserMessage(event)
|
||||||
eventData.client.MessageCount++
|
eventData.client.MessageCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,15 @@ import (
|
|||||||
|
|
||||||
"github.com/owncast/owncast/core/chat/events"
|
"github.com/owncast/owncast/core/chat/events"
|
||||||
"github.com/owncast/owncast/core/webhooks"
|
"github.com/owncast/owncast/core/webhooks"
|
||||||
|
"github.com/owncast/owncast/persistence/chatmessagerepository"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetMessagesVisibility will set the visibility of multiple messages by ID.
|
// SetMessagesVisibility will set the visibility of multiple messages by ID.
|
||||||
func SetMessagesVisibility(messageIDs []string, visibility bool) error {
|
func SetMessagesVisibility(messageIDs []string, visibility bool) error {
|
||||||
// Save new message visibility
|
// Save new message visibility
|
||||||
if err := saveMessageVisibility(messageIDs, visibility); err != nil {
|
chatMessageRepository := chatmessagerepository.Get()
|
||||||
|
if err := chatMessageRepository.SetMessageVisibilityForMessageIDs(messageIDs, visibility); err != nil {
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,17 @@
|
|||||||
package chat
|
package chat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/owncast/owncast/core/chat/events"
|
|
||||||
"github.com/owncast/owncast/core/data"
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/models"
|
|
||||||
"github.com/owncast/owncast/persistence/authrepository"
|
"github.com/owncast/owncast/persistence/authrepository"
|
||||||
"github.com/owncast/owncast/persistence/tables"
|
"github.com/owncast/owncast/persistence/tables"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _datastore *data.Datastore
|
var _datastore *data.Datastore
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxBacklogHours = 2 // Keep backlog max hours worth of messages
|
maxBacklogHours = 2 // Keep backlog max hours worth of messages
|
||||||
maxBacklogNumber = 50 // Return max number of messages in history request
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupPersistence() {
|
func setupPersistence() {
|
||||||
@@ -37,456 +29,3 @@ func setupPersistence() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveUserMessage will save a single chat event to the messages database.
|
|
||||||
func SaveUserMessage(event events.UserMessageEvent) {
|
|
||||||
saveEvent(event.ID, &event.User.ID, event.Body, event.Type, event.HiddenAt, event.Timestamp, nil, nil, nil, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveFederatedAction(event events.FediverseEngagementEvent) {
|
|
||||||
saveEvent(event.ID, nil, event.Body, event.Type, nil, event.Timestamp, event.Image, &event.Link, &event.UserAccountName, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// nolint: unparam
|
|
||||||
func saveEvent(id string, userID *string, body string, eventType string, hidden *time.Time, timestamp time.Time, image *string, link *string, title *string, subtitle *string) {
|
|
||||||
defer func() {
|
|
||||||
_historyCache = nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
tx, err := _datastore.DB.Begin()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("error saving", eventType, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer tx.Rollback() // nolint
|
|
||||||
|
|
||||||
stmt, err := tx.Prepare("INSERT INTO messages(id, user_id, body, eventType, hidden_at, timestamp, image, link, title, subtitle) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("error saving", eventType, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer stmt.Close()
|
|
||||||
|
|
||||||
if _, err = stmt.Exec(id, userID, body, eventType, hidden, timestamp, image, link, title, subtitle); err != nil {
|
|
||||||
log.Errorln("error saving", eventType, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = tx.Commit(); err != nil {
|
|
||||||
log.Errorln("error saving", eventType, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeUserMessageEventFromRowData(row rowData) events.UserMessageEvent {
|
|
||||||
scopes := ""
|
|
||||||
if row.userScopes != nil {
|
|
||||||
scopes = *row.userScopes
|
|
||||||
}
|
|
||||||
|
|
||||||
previousUsernames := []string{}
|
|
||||||
if row.previousUsernames != nil {
|
|
||||||
previousUsernames = strings.Split(*row.previousUsernames, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
displayName := ""
|
|
||||||
if row.userDisplayName != nil {
|
|
||||||
displayName = *row.userDisplayName
|
|
||||||
}
|
|
||||||
|
|
||||||
displayColor := 0
|
|
||||||
if row.userDisplayColor != nil {
|
|
||||||
displayColor = *row.userDisplayColor
|
|
||||||
}
|
|
||||||
|
|
||||||
createdAt := time.Time{}
|
|
||||||
if row.userCreatedAt != nil {
|
|
||||||
createdAt = *row.userCreatedAt
|
|
||||||
}
|
|
||||||
|
|
||||||
isBot := (row.userType != nil && *row.userType == "API")
|
|
||||||
scopeSlice := strings.Split(scopes, ",")
|
|
||||||
|
|
||||||
u := models.User{
|
|
||||||
ID: *row.userID,
|
|
||||||
DisplayName: displayName,
|
|
||||||
DisplayColor: displayColor,
|
|
||||||
CreatedAt: createdAt,
|
|
||||||
DisabledAt: row.userDisabledAt,
|
|
||||||
NameChangedAt: row.userNameChangedAt,
|
|
||||||
PreviousNames: previousUsernames,
|
|
||||||
AuthenticatedAt: row.userAuthenticatedAt,
|
|
||||||
Authenticated: row.userAuthenticatedAt != nil,
|
|
||||||
Scopes: scopeSlice,
|
|
||||||
IsBot: isBot,
|
|
||||||
}
|
|
||||||
|
|
||||||
message := events.UserMessageEvent{
|
|
||||||
Event: events.Event{
|
|
||||||
Type: row.eventType,
|
|
||||||
ID: row.id,
|
|
||||||
Timestamp: row.timestamp,
|
|
||||||
},
|
|
||||||
UserEvent: events.UserEvent{
|
|
||||||
User: &u,
|
|
||||||
HiddenAt: row.hiddenAt,
|
|
||||||
},
|
|
||||||
MessageEvent: events.MessageEvent{
|
|
||||||
Body: row.body,
|
|
||||||
RawBody: row.body,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return message
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeSystemMessageChatEventFromRowData(row rowData) events.SystemMessageEvent {
|
|
||||||
message := events.SystemMessageEvent{
|
|
||||||
Event: events.Event{
|
|
||||||
Type: row.eventType,
|
|
||||||
ID: row.id,
|
|
||||||
Timestamp: row.timestamp,
|
|
||||||
},
|
|
||||||
MessageEvent: events.MessageEvent{
|
|
||||||
Body: row.body,
|
|
||||||
RawBody: row.body,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return message
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeActionMessageChatEventFromRowData(row rowData) events.ActionEvent {
|
|
||||||
message := events.ActionEvent{
|
|
||||||
Event: events.Event{
|
|
||||||
Type: row.eventType,
|
|
||||||
ID: row.id,
|
|
||||||
Timestamp: row.timestamp,
|
|
||||||
},
|
|
||||||
MessageEvent: events.MessageEvent{
|
|
||||||
Body: row.body,
|
|
||||||
RawBody: row.body,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return message
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeFederatedActionChatEventFromRowData(row rowData) events.FediverseEngagementEvent {
|
|
||||||
message := events.FediverseEngagementEvent{
|
|
||||||
Event: events.Event{
|
|
||||||
Type: row.eventType,
|
|
||||||
ID: row.id,
|
|
||||||
Timestamp: row.timestamp,
|
|
||||||
},
|
|
||||||
MessageEvent: events.MessageEvent{
|
|
||||||
Body: row.body,
|
|
||||||
RawBody: row.body,
|
|
||||||
},
|
|
||||||
Image: row.image,
|
|
||||||
Link: *row.link,
|
|
||||||
UserAccountName: *row.title,
|
|
||||||
}
|
|
||||||
return message
|
|
||||||
}
|
|
||||||
|
|
||||||
type rowData struct {
|
|
||||||
timestamp time.Time
|
|
||||||
image *string
|
|
||||||
previousUsernames *string
|
|
||||||
|
|
||||||
userDisplayName *string
|
|
||||||
userDisplayColor *int
|
|
||||||
userID *string
|
|
||||||
title *string
|
|
||||||
subtitle *string
|
|
||||||
link *string
|
|
||||||
|
|
||||||
userType *string
|
|
||||||
userScopes *string
|
|
||||||
hiddenAt *time.Time
|
|
||||||
userCreatedAt *time.Time
|
|
||||||
userDisabledAt *time.Time
|
|
||||||
userAuthenticatedAt *time.Time
|
|
||||||
userNameChangedAt *time.Time
|
|
||||||
body string
|
|
||||||
eventType models.EventType
|
|
||||||
id string
|
|
||||||
}
|
|
||||||
|
|
||||||
func getChat(rows *sql.Rows) ([]interface{}, error) {
|
|
||||||
history := make([]interface{}, 0)
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
row := rowData{}
|
|
||||||
|
|
||||||
// Convert a database row into a chat event
|
|
||||||
if err := rows.Scan(
|
|
||||||
&row.id,
|
|
||||||
&row.userID,
|
|
||||||
&row.body,
|
|
||||||
&row.title,
|
|
||||||
&row.subtitle,
|
|
||||||
&row.image,
|
|
||||||
&row.link,
|
|
||||||
&row.eventType,
|
|
||||||
&row.hiddenAt,
|
|
||||||
&row.timestamp,
|
|
||||||
&row.userDisplayName,
|
|
||||||
&row.userDisplayColor,
|
|
||||||
&row.userCreatedAt,
|
|
||||||
&row.userDisabledAt,
|
|
||||||
&row.previousUsernames,
|
|
||||||
&row.userNameChangedAt,
|
|
||||||
&row.userAuthenticatedAt,
|
|
||||||
&row.userScopes,
|
|
||||||
&row.userType,
|
|
||||||
); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var message interface{}
|
|
||||||
|
|
||||||
switch row.eventType {
|
|
||||||
case events.MessageSent:
|
|
||||||
message = makeUserMessageEventFromRowData(row)
|
|
||||||
case events.SystemMessageSent:
|
|
||||||
message = makeSystemMessageChatEventFromRowData(row)
|
|
||||||
case events.ChatActionSent:
|
|
||||||
message = makeActionMessageChatEventFromRowData(row)
|
|
||||||
case events.FediverseEngagementFollow:
|
|
||||||
message = makeFederatedActionChatEventFromRowData(row)
|
|
||||||
case events.FediverseEngagementLike:
|
|
||||||
message = makeFederatedActionChatEventFromRowData(row)
|
|
||||||
case events.FediverseEngagementRepost:
|
|
||||||
message = makeFederatedActionChatEventFromRowData(row)
|
|
||||||
}
|
|
||||||
|
|
||||||
history = append(history, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
return history, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _historyCache *[]interface{}
|
|
||||||
|
|
||||||
// GetChatModerationHistory will return all the chat messages suitable for moderation purposes.
|
|
||||||
func GetChatModerationHistory() []interface{} {
|
|
||||||
if _historyCache != nil {
|
|
||||||
return *_historyCache
|
|
||||||
}
|
|
||||||
|
|
||||||
tx, err := _datastore.DB.Begin()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("error fetching chat moderation history", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
defer tx.Rollback() // nolint
|
|
||||||
|
|
||||||
// Get all messages regardless of visibility
|
|
||||||
query := "SELECT messages.id, user_id, body, title, subtitle, image, link, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at, authenticated_at, scopes, type FROM messages INNER JOIN users ON messages.user_id = users.id ORDER BY timestamp DESC"
|
|
||||||
stmt, err := tx.Prepare(query)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("error fetching chat moderation history", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := stmt.Query()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("error fetching chat moderation history", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
defer stmt.Close()
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
result, err := getChat(rows)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln(err)
|
|
||||||
log.Errorln("There is a problem enumerating chat message rows. Please report this:", query)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_historyCache = &result
|
|
||||||
|
|
||||||
if err = tx.Commit(); err != nil {
|
|
||||||
log.Errorln("error fetching chat moderation history", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetChatHistory will return all the chat messages suitable for returning as user-facing chat history.
|
|
||||||
func GetChatHistory() []interface{} {
|
|
||||||
tx, err := _datastore.DB.Begin()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("error fetching chat history", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
defer tx.Rollback() // nolint
|
|
||||||
|
|
||||||
// Get all visible messages
|
|
||||||
query := "SELECT messages.id, messages.user_id, messages.body, messages.title, messages.subtitle, messages.image, messages.link, messages.eventType, messages.hidden_at, messages.timestamp, users.display_name, users.display_color, users.created_at, users.disabled_at, users.previous_names, users.namechanged_at, users.authenticated_at, users.scopes, users.type FROM users JOIN messages ON users.id = messages.user_id WHERE hidden_at IS NULL AND disabled_at IS NULL ORDER BY timestamp DESC LIMIT ?"
|
|
||||||
|
|
||||||
stmt, err := tx.Prepare(query)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("error fetching chat history", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := stmt.Query(maxBacklogNumber)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("error fetching chat history", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
defer stmt.Close()
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
m, err := getChat(rows)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln(err)
|
|
||||||
log.Errorln("There is a problem enumerating chat message rows. Please report this:", query)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = tx.Commit(); err != nil {
|
|
||||||
log.Errorln("error fetching chat history", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invert order of messages
|
|
||||||
for i, j := 0, len(m)-1; i < j; i, j = i+1, j-1 {
|
|
||||||
m[i], m[j] = m[j], m[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMessagesFromUser returns chat messages that were sent by a specific user.
|
|
||||||
func GetMessagesFromUser(userID string) ([]events.UserMessageEvent, error) {
|
|
||||||
query, err := _datastore.GetQueries().GetMessagesFromUser(context.Background(), sql.NullString{String: userID, Valid: true})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
results := make([]events.UserMessageEvent, len(query))
|
|
||||||
for i, row := range query {
|
|
||||||
results[i] = events.UserMessageEvent{
|
|
||||||
Event: events.Event{
|
|
||||||
Timestamp: row.Timestamp.Time,
|
|
||||||
ID: row.ID,
|
|
||||||
},
|
|
||||||
MessageEvent: events.MessageEvent{
|
|
||||||
Body: row.Body.String,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMessageVisibilityForUserID will bulk change the visibility of messages for a user
|
|
||||||
// and then send out visibility changed events to chat clients.
|
|
||||||
func SetMessageVisibilityForUserID(userID string, visible bool) error {
|
|
||||||
defer func() {
|
|
||||||
_historyCache = nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
tx, err := _datastore.DB.Begin()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("error while setting message visibility", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
defer tx.Rollback() // nolint
|
|
||||||
query := "SELECT messages.id, user_id, body, title, subtitle, image, link, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at, authenticated_at, scopes, type FROM messages INNER JOIN users ON messages.user_id = users.id WHERE user_id IS ?"
|
|
||||||
|
|
||||||
stmt, err := tx.Prepare(query)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("error while setting message visibility", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := stmt.Query(userID)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("error while setting message visibility", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
defer stmt.Close()
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
// Get a list of IDs to send to the connected clients to hide
|
|
||||||
ids := make([]string, 0)
|
|
||||||
|
|
||||||
messages, err := getChat(rows)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln(err)
|
|
||||||
log.Errorln("There is a problem enumerating chat message rows. Please report this:", query)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(messages) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, message := range messages {
|
|
||||||
ids = append(ids, message.(events.UserMessageEvent).ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = tx.Commit(); err != nil {
|
|
||||||
log.Errorln("error while setting message visibility ", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tell the clients to hide/show these messages.
|
|
||||||
return SetMessagesVisibility(ids, visible)
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveMessageVisibility(messageIDs []string, visible bool) error {
|
|
||||||
defer func() {
|
|
||||||
_historyCache = nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
_datastore.DbLock.Lock()
|
|
||||||
defer _datastore.DbLock.Unlock()
|
|
||||||
|
|
||||||
tx, err := _datastore.DB.Begin()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// nolint:gosec
|
|
||||||
stmt, err := tx.Prepare("UPDATE messages SET hidden_at=? WHERE id IN (?" + strings.Repeat(",?", len(messageIDs)-1) + ")")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer stmt.Close()
|
|
||||||
|
|
||||||
var hiddenAt *time.Time
|
|
||||||
if !visible {
|
|
||||||
now := time.Now()
|
|
||||||
hiddenAt = &now
|
|
||||||
} else {
|
|
||||||
hiddenAt = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
args := make([]interface{}, len(messageIDs)+1)
|
|
||||||
args[0] = hiddenAt
|
|
||||||
for i, id := range messageIDs {
|
|
||||||
args[i+1] = id
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = stmt.Exec(args...); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = tx.Commit(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ func SetupPersistence(file string) error {
|
|||||||
_, _ = db.Exec("pragma temp_store = memory")
|
_, _ = db.Exec("pragma temp_store = memory")
|
||||||
_, _ = db.Exec("pragma wal_checkpoint(full)")
|
_, _ = db.Exec("pragma wal_checkpoint(full)")
|
||||||
|
|
||||||
|
tables.CreateConfigTable(db)
|
||||||
tables.CreateWebhooksTable(db)
|
tables.CreateWebhooksTable(db)
|
||||||
tables.CreateUsersTable(db)
|
tables.CreateUsersTable(db)
|
||||||
tables.CreateAccessTokenTable(db)
|
tables.CreateAccessTokenTable(db)
|
||||||
|
|||||||
@@ -107,19 +107,11 @@ func (ds *Datastore) Save(e models.ConfigEntry) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup will create the datastore table and perform initial initialization.
|
// Setup will perform initial initialization.
|
||||||
func (ds *Datastore) Setup() {
|
func (ds *Datastore) Setup() {
|
||||||
ds.cache = make(map[string][]byte)
|
ds.cache = make(map[string][]byte)
|
||||||
ds.DB = GetDatabase()
|
ds.DB = GetDatabase()
|
||||||
ds.DbLock = &sync.Mutex{}
|
ds.DbLock = &sync.Mutex{}
|
||||||
|
|
||||||
createTableSQL := `CREATE TABLE IF NOT EXISTS datastore (
|
|
||||||
"key" string NOT NULL PRIMARY KEY,
|
|
||||||
"value" BLOB,
|
|
||||||
"timestamp" DATE DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
||||||
);`
|
|
||||||
|
|
||||||
ds.MustExec(createTableSQL)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset will delete all config entries in the datastore and start over.
|
// Reset will delete all config entries in the datastore and start over.
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
package data
|
|
||||||
|
|
||||||
// GetMessagesCount will return the number of messages in the database.
|
|
||||||
func GetMessagesCount() int64 {
|
|
||||||
query := `SELECT COUNT(*) FROM messages`
|
|
||||||
rows, err := _db.Query(query)
|
|
||||||
if err != nil || rows.Err() != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
var count int64
|
|
||||||
for rows.Next() {
|
|
||||||
if err := rows.Scan(&count); err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
+3
-2
@@ -6,7 +6,7 @@ import (
|
|||||||
"github.com/nakabonne/tstorage"
|
"github.com/nakabonne/tstorage"
|
||||||
"github.com/owncast/owncast/core"
|
"github.com/owncast/owncast/core"
|
||||||
"github.com/owncast/owncast/core/chat"
|
"github.com/owncast/owncast/core/chat"
|
||||||
"github.com/owncast/owncast/core/data"
|
"github.com/owncast/owncast/persistence/chatmessagerepository"
|
||||||
"github.com/owncast/owncast/persistence/userrepository"
|
"github.com/owncast/owncast/persistence/userrepository"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@@ -56,7 +56,8 @@ func collectChatClientCount() {
|
|||||||
activeChatClientCount.Set(float64(count))
|
activeChatClientCount.Set(float64(count))
|
||||||
|
|
||||||
// Total message count
|
// Total message count
|
||||||
cmc := data.GetMessagesCount()
|
chatMessageRepository := chatmessagerepository.Get()
|
||||||
|
cmc := chatMessageRepository.GetMessagesCount()
|
||||||
// Insert message count into Prometheus collector.
|
// Insert message count into Prometheus collector.
|
||||||
currentChatMessageCount.Set(float64(cmc))
|
currentChatMessageCount.Set(float64(cmc))
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,523 @@
|
|||||||
|
package chatmessagerepository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/owncast/owncast/core/chat/events"
|
||||||
|
"github.com/owncast/owncast/core/data"
|
||||||
|
"github.com/owncast/owncast/models"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const maxBacklogNumber = 50 // Return max number of messages in history request
|
||||||
|
|
||||||
|
type ChatMessageRepository interface {
|
||||||
|
SaveUserMessage(event events.UserMessageEvent)
|
||||||
|
SaveFederatedAction(event events.FediverseEngagementEvent)
|
||||||
|
SaveEvent(id string, userID *string, body string, eventType string, hidden *time.Time, timestamp time.Time, image *string, link *string, title *string, subtitle *string)
|
||||||
|
GetChatModerationHistory() []interface{}
|
||||||
|
GetChatHistory() []interface{}
|
||||||
|
GetMessagesFromUser(userID string) ([]events.UserMessageEvent, error)
|
||||||
|
GetMessageIdsForUserID(userID string) ([]string, error)
|
||||||
|
SetMessageVisibilityForMessageIDs(messageIDs []string, visible bool) error
|
||||||
|
GetMessagesCount() int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type SqlChatMessageRepository struct {
|
||||||
|
datastore *data.Datastore
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: This is temporary during the transition period.
|
||||||
|
var temporaryGlobalInstance ChatMessageRepository
|
||||||
|
|
||||||
|
// Get will return the user repository.
|
||||||
|
func Get() ChatMessageRepository {
|
||||||
|
if temporaryGlobalInstance == nil {
|
||||||
|
i := New(data.GetDatastore())
|
||||||
|
temporaryGlobalInstance = i
|
||||||
|
}
|
||||||
|
return temporaryGlobalInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
// New will create a new instance of the UserRepository.
|
||||||
|
func New(datastore *data.Datastore) ChatMessageRepository {
|
||||||
|
r := SqlChatMessageRepository{
|
||||||
|
datastore: datastore,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &r
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveUserMessage will save a single chat event to the messages database.
|
||||||
|
func (r *SqlChatMessageRepository) SaveUserMessage(event events.UserMessageEvent) {
|
||||||
|
r.SaveEvent(event.ID, &event.User.ID, event.Body, event.Type, event.HiddenAt, event.Timestamp, nil, nil, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SqlChatMessageRepository) SaveFederatedAction(event events.FediverseEngagementEvent) {
|
||||||
|
r.SaveEvent(event.ID, nil, event.Body, event.Type, nil, event.Timestamp, event.Image, &event.Link, &event.UserAccountName, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint: unparam
|
||||||
|
func (r *SqlChatMessageRepository) SaveEvent(id string, userID *string, body string, eventType string, hidden *time.Time, timestamp time.Time, image *string, link *string, title *string, subtitle *string) {
|
||||||
|
defer func() {
|
||||||
|
_historyCache = nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
tx, err := r.datastore.DB.Begin()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("error saving", eventType, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer tx.Rollback() // nolint
|
||||||
|
|
||||||
|
stmt, err := tx.Prepare("INSERT INTO messages(id, user_id, body, eventType, hidden_at, timestamp, image, link, title, subtitle) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("error saving", eventType, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer stmt.Close()
|
||||||
|
|
||||||
|
if _, err = stmt.Exec(id, userID, body, eventType, hidden, timestamp, image, link, title, subtitle); err != nil {
|
||||||
|
log.Errorln("error saving", eventType, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = tx.Commit(); err != nil {
|
||||||
|
log.Errorln("error saving", eventType, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeUserMessageEventFromRowData(row rowData) events.UserMessageEvent {
|
||||||
|
scopes := ""
|
||||||
|
if row.userScopes != nil {
|
||||||
|
scopes = *row.userScopes
|
||||||
|
}
|
||||||
|
|
||||||
|
previousUsernames := []string{}
|
||||||
|
if row.previousUsernames != nil {
|
||||||
|
previousUsernames = strings.Split(*row.previousUsernames, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
displayName := ""
|
||||||
|
if row.userDisplayName != nil {
|
||||||
|
displayName = *row.userDisplayName
|
||||||
|
}
|
||||||
|
|
||||||
|
displayColor := 0
|
||||||
|
if row.userDisplayColor != nil {
|
||||||
|
displayColor = *row.userDisplayColor
|
||||||
|
}
|
||||||
|
|
||||||
|
createdAt := time.Time{}
|
||||||
|
if row.userCreatedAt != nil {
|
||||||
|
createdAt = *row.userCreatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
isBot := (row.userType != nil && *row.userType == "API")
|
||||||
|
scopeSlice := strings.Split(scopes, ",")
|
||||||
|
|
||||||
|
u := models.User{
|
||||||
|
ID: *row.userID,
|
||||||
|
DisplayName: displayName,
|
||||||
|
DisplayColor: displayColor,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
DisabledAt: row.userDisabledAt,
|
||||||
|
NameChangedAt: row.userNameChangedAt,
|
||||||
|
PreviousNames: previousUsernames,
|
||||||
|
AuthenticatedAt: row.userAuthenticatedAt,
|
||||||
|
Authenticated: row.userAuthenticatedAt != nil,
|
||||||
|
Scopes: scopeSlice,
|
||||||
|
IsBot: isBot,
|
||||||
|
}
|
||||||
|
|
||||||
|
message := events.UserMessageEvent{
|
||||||
|
Event: events.Event{
|
||||||
|
Type: row.eventType,
|
||||||
|
ID: row.id,
|
||||||
|
Timestamp: row.timestamp,
|
||||||
|
},
|
||||||
|
UserEvent: events.UserEvent{
|
||||||
|
User: &u,
|
||||||
|
HiddenAt: row.hiddenAt,
|
||||||
|
},
|
||||||
|
MessageEvent: events.MessageEvent{
|
||||||
|
Body: row.body,
|
||||||
|
RawBody: row.body,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSystemMessageChatEventFromRowData(row rowData) events.SystemMessageEvent {
|
||||||
|
message := events.SystemMessageEvent{
|
||||||
|
Event: events.Event{
|
||||||
|
Type: row.eventType,
|
||||||
|
ID: row.id,
|
||||||
|
Timestamp: row.timestamp,
|
||||||
|
},
|
||||||
|
MessageEvent: events.MessageEvent{
|
||||||
|
Body: row.body,
|
||||||
|
RawBody: row.body,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeActionMessageChatEventFromRowData(row rowData) events.ActionEvent {
|
||||||
|
message := events.ActionEvent{
|
||||||
|
Event: events.Event{
|
||||||
|
Type: row.eventType,
|
||||||
|
ID: row.id,
|
||||||
|
Timestamp: row.timestamp,
|
||||||
|
},
|
||||||
|
MessageEvent: events.MessageEvent{
|
||||||
|
Body: row.body,
|
||||||
|
RawBody: row.body,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeFederatedActionChatEventFromRowData(row rowData) events.FediverseEngagementEvent {
|
||||||
|
message := events.FediverseEngagementEvent{
|
||||||
|
Event: events.Event{
|
||||||
|
Type: row.eventType,
|
||||||
|
ID: row.id,
|
||||||
|
Timestamp: row.timestamp,
|
||||||
|
},
|
||||||
|
MessageEvent: events.MessageEvent{
|
||||||
|
Body: row.body,
|
||||||
|
RawBody: row.body,
|
||||||
|
},
|
||||||
|
Image: row.image,
|
||||||
|
Link: *row.link,
|
||||||
|
UserAccountName: *row.title,
|
||||||
|
}
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
type rowData struct {
|
||||||
|
timestamp time.Time
|
||||||
|
image *string
|
||||||
|
previousUsernames *string
|
||||||
|
|
||||||
|
userDisplayName *string
|
||||||
|
userDisplayColor *int
|
||||||
|
userID *string
|
||||||
|
title *string
|
||||||
|
subtitle *string
|
||||||
|
link *string
|
||||||
|
|
||||||
|
userType *string
|
||||||
|
userScopes *string
|
||||||
|
hiddenAt *time.Time
|
||||||
|
userCreatedAt *time.Time
|
||||||
|
userDisabledAt *time.Time
|
||||||
|
userAuthenticatedAt *time.Time
|
||||||
|
userNameChangedAt *time.Time
|
||||||
|
body string
|
||||||
|
eventType models.EventType
|
||||||
|
id string
|
||||||
|
}
|
||||||
|
|
||||||
|
func getChat(rows *sql.Rows) ([]interface{}, error) {
|
||||||
|
history := make([]interface{}, 0)
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
row := rowData{}
|
||||||
|
|
||||||
|
// Convert a database row into a chat event
|
||||||
|
if err := rows.Scan(
|
||||||
|
&row.id,
|
||||||
|
&row.userID,
|
||||||
|
&row.body,
|
||||||
|
&row.title,
|
||||||
|
&row.subtitle,
|
||||||
|
&row.image,
|
||||||
|
&row.link,
|
||||||
|
&row.eventType,
|
||||||
|
&row.hiddenAt,
|
||||||
|
&row.timestamp,
|
||||||
|
&row.userDisplayName,
|
||||||
|
&row.userDisplayColor,
|
||||||
|
&row.userCreatedAt,
|
||||||
|
&row.userDisabledAt,
|
||||||
|
&row.previousUsernames,
|
||||||
|
&row.userNameChangedAt,
|
||||||
|
&row.userAuthenticatedAt,
|
||||||
|
&row.userScopes,
|
||||||
|
&row.userType,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var message interface{}
|
||||||
|
|
||||||
|
switch row.eventType {
|
||||||
|
case events.MessageSent:
|
||||||
|
message = makeUserMessageEventFromRowData(row)
|
||||||
|
case events.SystemMessageSent:
|
||||||
|
message = makeSystemMessageChatEventFromRowData(row)
|
||||||
|
case events.ChatActionSent:
|
||||||
|
message = makeActionMessageChatEventFromRowData(row)
|
||||||
|
case events.FediverseEngagementFollow:
|
||||||
|
message = makeFederatedActionChatEventFromRowData(row)
|
||||||
|
case events.FediverseEngagementLike:
|
||||||
|
message = makeFederatedActionChatEventFromRowData(row)
|
||||||
|
case events.FediverseEngagementRepost:
|
||||||
|
message = makeFederatedActionChatEventFromRowData(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
history = append(history, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return history, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _historyCache *[]interface{}
|
||||||
|
|
||||||
|
// GetChatModerationHistory will return all the chat messages suitable for moderation purposes.
|
||||||
|
func (r *SqlChatMessageRepository) GetChatModerationHistory() []interface{} {
|
||||||
|
if _historyCache != nil {
|
||||||
|
return *_historyCache
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := r.datastore.DB.Begin()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("error fetching chat moderation history", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
defer tx.Rollback() // nolint
|
||||||
|
|
||||||
|
// Get all messages regardless of visibility
|
||||||
|
query := "SELECT messages.id, user_id, body, title, subtitle, image, link, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at, authenticated_at, scopes, type FROM messages INNER JOIN users ON messages.user_id = users.id ORDER BY timestamp DESC"
|
||||||
|
stmt, err := tx.Prepare(query)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("error fetching chat moderation history", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := stmt.Query()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("error fetching chat moderation history", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
defer stmt.Close()
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
result, err := getChat(rows)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln(err)
|
||||||
|
log.Errorln("There is a problem enumerating chat message rows. Please report this:", query)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_historyCache = &result
|
||||||
|
|
||||||
|
if err = tx.Commit(); err != nil {
|
||||||
|
log.Errorln("error fetching chat moderation history", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChatHistory will return all the chat messages suitable for returning as user-facing chat history.
|
||||||
|
func (r *SqlChatMessageRepository) GetChatHistory() []interface{} {
|
||||||
|
tx, err := r.datastore.DB.Begin()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("error fetching chat history", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
defer tx.Rollback() // nolint
|
||||||
|
|
||||||
|
// Get all visible messages
|
||||||
|
query := "SELECT messages.id, messages.user_id, messages.body, messages.title, messages.subtitle, messages.image, messages.link, messages.eventType, messages.hidden_at, messages.timestamp, users.display_name, users.display_color, users.created_at, users.disabled_at, users.previous_names, users.namechanged_at, users.authenticated_at, users.scopes, users.type FROM users JOIN messages ON users.id = messages.user_id WHERE hidden_at IS NULL AND disabled_at IS NULL ORDER BY timestamp DESC LIMIT ?"
|
||||||
|
|
||||||
|
stmt, err := tx.Prepare(query)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("error fetching chat history", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := stmt.Query(maxBacklogNumber)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("error fetching chat history", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
defer stmt.Close()
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
m, err := getChat(rows)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln(err)
|
||||||
|
log.Errorln("There is a problem enumerating chat message rows. Please report this:", query)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = tx.Commit(); err != nil {
|
||||||
|
log.Errorln("error fetching chat history", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invert order of messages
|
||||||
|
for i, j := 0, len(m)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
m[i], m[j] = m[j], m[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMessagesFromUser returns chat messages that were sent by a specific user.
|
||||||
|
func (r *SqlChatMessageRepository) GetMessagesFromUser(userID string) ([]events.UserMessageEvent, error) {
|
||||||
|
query, err := r.datastore.GetQueries().GetMessagesFromUser(context.Background(), sql.NullString{String: userID, Valid: true})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
results := make([]events.UserMessageEvent, len(query))
|
||||||
|
for i, row := range query {
|
||||||
|
results[i] = events.UserMessageEvent{
|
||||||
|
Event: events.Event{
|
||||||
|
Timestamp: row.Timestamp.Time,
|
||||||
|
ID: row.ID,
|
||||||
|
},
|
||||||
|
MessageEvent: events.MessageEvent{
|
||||||
|
Body: row.Body.String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMessageIdsForUserID will return the chat message IDs for a specific user.
|
||||||
|
func (r *SqlChatMessageRepository) GetMessageIdsForUserID(userID string) ([]string, error) {
|
||||||
|
defer func() {
|
||||||
|
_historyCache = nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
tx, err := r.datastore.DB.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("error while setting message visibility")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer tx.Rollback() // nolint
|
||||||
|
query := "SELECT messages.id, user_id, body, title, subtitle, image, link, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at, authenticated_at, scopes, type FROM messages INNER JOIN users ON messages.user_id = users.id WHERE user_id IS ?"
|
||||||
|
|
||||||
|
stmt, err := tx.Prepare(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("error while setting message visibility")
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := stmt.Query(userID)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("error while setting message visibility", err)
|
||||||
|
return nil, errors.New("error while setting message visibility")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer stmt.Close()
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
// Get a list of IDs to send to the connected clients to hide
|
||||||
|
ids := make([]string, 0)
|
||||||
|
|
||||||
|
messages, err := getChat(rows)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln(err)
|
||||||
|
return nil, errors.New("There is a problem enumerating chat message rows. Please report this: " + query)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(messages) == 0 {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, message := range messages {
|
||||||
|
ids = append(ids, message.(events.UserMessageEvent).ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = tx.Commit(); err != nil {
|
||||||
|
return nil, errors.New("error while setting message visibility")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tell the clients to hide/show these messages.
|
||||||
|
return ids, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SqlChatMessageRepository) SetMessageVisibilityForMessageIDs(messageIDs []string, visible bool) error {
|
||||||
|
defer func() {
|
||||||
|
_historyCache = nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
if len(messageIDs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r.datastore.DbLock.Lock()
|
||||||
|
defer r.datastore.DbLock.Unlock()
|
||||||
|
|
||||||
|
tx, err := r.datastore.DB.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint:gosec
|
||||||
|
stmt, err := tx.Prepare("UPDATE messages SET hidden_at=? WHERE id IN (?" + strings.Repeat(",?", len(messageIDs)-1) + ")")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer stmt.Close()
|
||||||
|
|
||||||
|
var hiddenAt *time.Time
|
||||||
|
if !visible {
|
||||||
|
now := time.Now()
|
||||||
|
hiddenAt = &now
|
||||||
|
} else {
|
||||||
|
hiddenAt = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
args := make([]interface{}, len(messageIDs)+1)
|
||||||
|
args[0] = hiddenAt
|
||||||
|
for i, id := range messageIDs {
|
||||||
|
args[i+1] = id
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = stmt.Exec(args...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = tx.Commit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMessagesCount will return the number of messages in the database.
|
||||||
|
func (r *SqlChatMessageRepository) GetMessagesCount() int64 {
|
||||||
|
query := `SELECT COUNT(*) FROM messages`
|
||||||
|
rows, err := r.datastore.DB.Query(query)
|
||||||
|
if err != nil || rows.Err() != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var count int64
|
||||||
|
for rows.Next() {
|
||||||
|
if err := rows.Scan(&count); err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package tables
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/owncast/owncast/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateConfigTable(db *sql.DB) {
|
||||||
|
createTableSQL := `CREATE TABLE IF NOT EXISTS datastore (
|
||||||
|
"key" string NOT NULL PRIMARY KEY,
|
||||||
|
"value" BLOB,
|
||||||
|
"timestamp" DATE DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||||
|
);`
|
||||||
|
|
||||||
|
utils.MustExec(createTableSQL, db)
|
||||||
|
}
|
||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+2
-2
File diff suppressed because one or more lines are too long
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -22,6 +22,11 @@ const establishedUserFailedChatMessage = {
|
|||||||
type: 'CHAT',
|
type: 'CHAT',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const establishedUserSucceedChatMessage = {
|
||||||
|
body: 'this message should send ' + Math.floor(Math.random() * 100),
|
||||||
|
type: 'CHAT',
|
||||||
|
};
|
||||||
|
|
||||||
test('send a chat message', async () => {
|
test('send a chat message', async () => {
|
||||||
const registration = await registerChat();
|
const registration = await registerChat();
|
||||||
const accessToken = registration.accessToken;
|
const accessToken = registration.accessToken;
|
||||||
@@ -36,7 +41,7 @@ test('verify admin can make API call to mark message as hidden', async () => {
|
|||||||
`ws://localhost:8080/ws?accessToken=${accessToken}`,
|
`ws://localhost:8080/ws?accessToken=${accessToken}`,
|
||||||
{
|
{
|
||||||
origin: 'http://localhost:8080',
|
origin: 'http://localhost:8080',
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify the visibility change comes through the websocket
|
// Verify the visibility change comes through the websocket
|
||||||
@@ -70,7 +75,7 @@ test('verify message has become hidden', async () => {
|
|||||||
return obj.id === messageId;
|
return obj.id === messageId;
|
||||||
});
|
});
|
||||||
expect(message.length).toBe(1);
|
expect(message.length).toBe(1);
|
||||||
// expect(message[0].hiddenAt).toBeTruthy();
|
expect(message[0].hiddenAt).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('enable established chat user mode', async () => {
|
test('enable established chat user mode', async () => {
|
||||||
@@ -97,3 +102,20 @@ test('verify rejected message is not in the chat feed', async () => {
|
|||||||
test('disable established chat user mode', async () => {
|
test('disable established chat user mode', async () => {
|
||||||
await sendAdminRequest('config/chat/establishedusermode', false);
|
await sendAdminRequest('config/chat/establishedusermode', false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('send a message after established user mode is disabled', async () => {
|
||||||
|
const registration = await registerChat();
|
||||||
|
const accessToken = registration.accessToken;
|
||||||
|
|
||||||
|
await sendChatMessage(establishedUserSucceedChatMessage, accessToken);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('verify message is in the chat feed', async () => {
|
||||||
|
const res = await getAdminResponse('chat/messages');
|
||||||
|
|
||||||
|
const message = res.body.filter((obj) => {
|
||||||
|
return obj.body === establishedUserSucceedChatMessage.body;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(message.length).toBe(0);
|
||||||
|
});
|
||||||
|
|||||||
Regular → Executable
@@ -53,7 +53,7 @@
|
|||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "the next time goes live",
|
||||||
"Follow": "Follow",
|
"Follow": "Follow",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "آخر بث مباشر {{timeAgo}}",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"User": "User",
|
"User": "User",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Visible messages",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "the next time goes live",
|
||||||
"Follow": "Follow",
|
"Follow": "Follow",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "গত {{timeAgo}} এর শেষ লাইভ",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"User": "User",
|
"User": "User",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Visible messages",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "the next time goes live",
|
||||||
"Follow": "Follow",
|
"Follow": "Follow",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "Letzter Live- {{timeAgo}} vor",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"User": "User",
|
"User": "User",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Visible messages",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "the next time goes live",
|
||||||
"Follow": "Follow",
|
"Follow": "Follow",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "Τελευταία ζωντανή σύνδεση {{timeAgo}} πριν",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"User": "User",
|
"User": "User",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Visible messages",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "the next time goes live",
|
||||||
"Follow": "Follow",
|
"Follow": "Follow",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "Última versión de {{timeAgo}} hace",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"User": "User",
|
"User": "User",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Visible messages",
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
{
|
||||||
|
"Notify": "Jakinarazi",
|
||||||
|
"Level": "Maila",
|
||||||
|
"Info": "Informazioa",
|
||||||
|
"Warning": "Abisua",
|
||||||
|
"Error": "Errorea",
|
||||||
|
"Timestamp": "Denbora-zigilua",
|
||||||
|
"Message": "Mezua",
|
||||||
|
"Logs": "Erregistroak",
|
||||||
|
"Link": "Esteka",
|
||||||
|
"No news": "Zaharrak berri.",
|
||||||
|
"News & Updates from Owncast": "Owncast-en albiste eta eguneraketak",
|
||||||
|
"Use your broadcasting software": "Erabili zure igorpen-softwarea",
|
||||||
|
"Learn how to point your existing software to your new server and start streaming your content": "Ikasi lehendik duzun softwarea zerbitzari berrira bideratzen eta hasi zure edukia erreproduzitzen.",
|
||||||
|
"View": "Ikusi",
|
||||||
|
"Overridden via command line": "Komando-lerroaren bidez gainidatzi da.",
|
||||||
|
"Embed your video onto other sites": "Txertatu zure bideoa beste webgune batzuetan",
|
||||||
|
"Learn how you can add your Owncast stream to other sites you control": "Ikasi nola gehi dezakezun zure Owncasteko streama kontrolatzen dituzun beste gune batzuetara.",
|
||||||
|
"Chat is disabled": "Txata desgaituta dago",
|
||||||
|
"Chat will continue to be disabled until you begin a live stream": "Txata desgaituta egongo da zuzeneko igorpen bat hasi arte.",
|
||||||
|
"Find an audience on the Owncast Directory": "Aurkitu zure entzulegoa Owncast direktorioan",
|
||||||
|
"List yourself in the Owncast Directory and show off your stream": {
|
||||||
|
" Enable it in": "Zerrendatu zeure burua Owncast direktorioan eta erakutsi zure streama. Gaitu hemen"
|
||||||
|
},
|
||||||
|
"settings": "Ezarpenak",
|
||||||
|
"Add your Owncast instance to the Fediverse": "Gehitu zure Owncast instantzia fedibertsora",
|
||||||
|
"Enable Owncast social features": "Gaitu Owncasten sare sozialen funtzioak",
|
||||||
|
"to have your instance join the Fediverse, allowing people to follow, share and engage with your live stream": "zure instantzia fedibertsoan sartzeko, jendeari zure zuzeneko igorpenarekin jarraitzeko, partekatzeko eta parte hartzeko aukera emanez.",
|
||||||
|
"No stream is active": "Ez da aktibo dagoen zuzenekorik",
|
||||||
|
"You should start one": "Bat abiatu zenezake",
|
||||||
|
"Healthy Stream": "Zuzenekoaren osasuna",
|
||||||
|
"Yes": "Bai",
|
||||||
|
"No": "Ez",
|
||||||
|
"Playback Health": "Erreprodukzioaren osasuna",
|
||||||
|
"Stream health represents": "Zuzenekoaren osasunak ordezkatzen du",
|
||||||
|
"of all known players": {
|
||||||
|
" Other player status is unknown": "erreproduzitzaile ezagun guztietatik. Beste erreproduzitzaileen egoera ezezaguna da."
|
||||||
|
},
|
||||||
|
"TROUBLESHOOT": "Arazoen konponketa",
|
||||||
|
"Powered by Owncast": "Owncast-i esker",
|
||||||
|
"Documentation": "Dokumentazioa",
|
||||||
|
"Contribute": "Egin ekarpena",
|
||||||
|
"Source": "Iturria",
|
||||||
|
"Skip to player": "Joan erreproduktorera",
|
||||||
|
"Skip to offline message": "Joan lineaz kanpoko mezura",
|
||||||
|
"Skip to page content": "Joan orriaren edukira",
|
||||||
|
"Skip to footer": "Joan orriaren oinera",
|
||||||
|
"Chat will be available when the stream is live": "Txata erabilgarri egongo da zuzenekoaren igorpena abiatutakoan.",
|
||||||
|
"Chat is offline": "Txata lineaz kanpo dago",
|
||||||
|
"Stay updated!": "Eguneraketak jarraitu!",
|
||||||
|
"Click and never miss future streams!": "Egin klik eta ez itzazu ahaztu etorkizuneko zuzenekoak!",
|
||||||
|
"This stream is offline": "Zuzenekoa bukatu da.",
|
||||||
|
"the next time goes live": "zuzenekoa abiatzen den hurrengoan",
|
||||||
|
"Follow": "Jarraitu",
|
||||||
|
"on the Fediverse to see the next time goes live": "fedibertsoan hurrengo zuzenekoaren berri izan dezazun",
|
||||||
|
"Last live ago": "Azken zuzenekoa duela {{timeAgo}}",
|
||||||
|
"Time": "Denbora",
|
||||||
|
"User": "Erabiltzailea",
|
||||||
|
"Visible messages": "Ikus daitezkeen mezuak",
|
||||||
|
"Hidden messages": "Ezkututako mezuak",
|
||||||
|
"Chat Messages": "Txateko mezuak",
|
||||||
|
"Manage the messages from viewers that show up on your stream": "Kudeatu zuzenekoan agertzen diren ikusleen mezuak.",
|
||||||
|
"Show": "Erakutsi",
|
||||||
|
"Hide": "Ezkutatu",
|
||||||
|
"Visit the": "Bisitatu",
|
||||||
|
"documentation": "dokumentazioa",
|
||||||
|
"to configure additional details about your viewers": "zure ikusleei buruzko xehetasun gehiago konfiguratzeko.",
|
||||||
|
"When a stream is active and chat is enabled, connected chat clients will be displayed here": "Zuzeneko bat aktibo dagoenean eta txata gaituta dagoenean, konektatutako txat-bezeroak hemen bistaratuko dira.",
|
||||||
|
"Connected": "Konektatuta",
|
||||||
|
"offline": "lineaz kanpo",
|
||||||
|
"Banned Users": "Debekatutako erabiltzaileak",
|
||||||
|
"IP Bans": "IP debekuak",
|
||||||
|
"Moderators": "Moderatzaileak",
|
||||||
|
"Hardware Info": "Hardware-ari buruzko informazioa",
|
||||||
|
"Please wait": "Itxaron mesedez",
|
||||||
|
"No hardware details have been collected yet": "Ez dira oraindik hardware-ari buruzko xehetasunak jaso.",
|
||||||
|
"CPU": "PUZ",
|
||||||
|
"Memory": "Memoria",
|
||||||
|
"Disk": "Biltegiratzea",
|
||||||
|
"used": "erabilita",
|
||||||
|
"I want to configure my owncast instance": "Nire Owncast instantzia konfiguratu nahi dut",
|
||||||
|
"Learn more": "Ikasi gehiago",
|
||||||
|
"Help configuring my broadcasting software": "Laguntza emanaldietarako software-a konfiguratzen",
|
||||||
|
"I want to embed my stream into another site": "Nire zuzenekoa beste gune batean txertatu nahi dut",
|
||||||
|
"I want to customize my website": "Nire webgunea pertsonalizatu nahi dut",
|
||||||
|
"I want to tweak my video output": "Nire bideo irteera aldatu nahi dut",
|
||||||
|
"I want to use an external storage provider": "Kanpoko biltegiratze hornitzaile bat erabili nahi dut",
|
||||||
|
"I found a bug": "Arazo bat topatu dut",
|
||||||
|
"If you found a bug, then please": "Arazo bat topatu baduzu, mesedez,",
|
||||||
|
"let us know": "jakinaraziguzu",
|
||||||
|
"I have a general question": "Galdera orokor bat daukat",
|
||||||
|
"Most general questions are answered in our": "Galdera orokor gehienak erantzuten dira gure",
|
||||||
|
"FAQ": "FAQ (Maiz Egindako Galderak)",
|
||||||
|
"or exist in our": "edo existitzen dira gure",
|
||||||
|
"discussions": "eztabaidak",
|
||||||
|
"I want to build add-ons for Owncast": "Owncast-erako gehigarriak sortu nahi ditut",
|
||||||
|
"You can build your own bots, overlays, tools and add-ons with our": "Zure bot-ak, gainjartzeak, tresnak eta gehigarriak eraiki ditzakezu gure",
|
||||||
|
"developer APIs": "garatzaileentzako APIak.",
|
||||||
|
"How can we help you?": "Nola lagun zaitzakegu?",
|
||||||
|
"Troubleshooting": "Arazoen konponketa",
|
||||||
|
"Fix your problems": "Arazoak konpondu",
|
||||||
|
"Read the Docs": "Irakurri dokumentazioa",
|
||||||
|
"Common tasks": "Ataza arruntak",
|
||||||
|
"Other": "Beste edozein",
|
||||||
|
"Outbound Video Stream": "Bideoaren igorpena kanpora atera",
|
||||||
|
"Outbound Audio Stream": "Soinuaren igorpena kanpora atera",
|
||||||
|
"Stream started": "Zuzeneko igorpena abiatu da",
|
||||||
|
"Viewers": "Ikusleak",
|
||||||
|
"Peak viewer count": "Ikusle kopuruaren goren maila lortu",
|
||||||
|
"Outbound Stream Details": "Igorpenaren xehetasunak kanpora atera",
|
||||||
|
"Inbound Stream Details": "Igorpenaren xehetasunak barrura ekarri",
|
||||||
|
"Input": "Sarrera",
|
||||||
|
"Inbound Video Stream": "Bideoaren igorpena barrura sartu",
|
||||||
|
"Inbound Audio Stream": "Soinuaren igorpena barrura sartu",
|
||||||
|
"Current stream": "Oraingo igorpena",
|
||||||
|
"Last 12 hours": "Azken 12 orduetan",
|
||||||
|
"Last 24 hours": "Azken 24 orduetan",
|
||||||
|
"Last 7 days": "Azken 7 egunetan",
|
||||||
|
"Last 30 days": "Azken 30 egunetan",
|
||||||
|
"Last 3 months": "Azken 3 hilabeteetan",
|
||||||
|
"Last 6 months": "Azken 6 hilabeteetan",
|
||||||
|
"Viewer Info": "Ikus-entzuleen informazioa",
|
||||||
|
"Current viewers": "Uneko ikusleak",
|
||||||
|
"Max viewers this stream": "Igorpen honek izan duen gehiengo ikus-entzule kopurua",
|
||||||
|
"Max viewers last stream": "Azken igorpenak izan duen gehiengo ikus-entzule kopurua",
|
||||||
|
"max viewers": "gehiengo ikus-entzule kopurua",
|
||||||
|
"No viewer data has been collected yet": "Oraindik ez da ikus-entzuleei buruzko daturik jaso."
|
||||||
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"Notify": "Notify",
|
"Notify": "Notifier",
|
||||||
"Level": "Level",
|
"Level": "Niveau",
|
||||||
"Info": "Info",
|
"Info": "Info",
|
||||||
"Warning": "Warning",
|
"Warning": "Alerte",
|
||||||
"Error": "Error",
|
"Error": "Erreur",
|
||||||
"Timestamp": "Timestamp",
|
"Timestamp": "Horodatage",
|
||||||
"Message": "Message",
|
"Message": "Message",
|
||||||
"Logs": "Logs",
|
"Logs": "Journaux",
|
||||||
"Link": "Link",
|
"Link": "Link",
|
||||||
"No news": "No news.",
|
"No news": "No news.",
|
||||||
"News & Updates from Owncast": "News & Updates from Owncast",
|
"News & Updates from Owncast": "News & Updates from Owncast",
|
||||||
"Use your broadcasting software": "Use your broadcasting software",
|
"Use your broadcasting software": "Utilisez votre logiciel de diffusion",
|
||||||
"Learn how to point your existing software to your new server and start streaming your content": "Learn how to point your existing software to your new server and start streaming your content.",
|
"Learn how to point your existing software to your new server and start streaming your content": "Learn how to point your existing software to your new server and start streaming your content.",
|
||||||
"View": "View",
|
"View": "View",
|
||||||
"Overridden via command line": "Overridden via command line.",
|
"Overridden via command line": "Overridden via command line.",
|
||||||
@@ -22,11 +22,11 @@
|
|||||||
"List yourself in the Owncast Directory and show off your stream": {
|
"List yourself in the Owncast Directory and show off your stream": {
|
||||||
" Enable it in": "List yourself in the Owncast Directory and show off your stream. Enable it in"
|
" Enable it in": "List yourself in the Owncast Directory and show off your stream. Enable it in"
|
||||||
},
|
},
|
||||||
"settings": "settings.",
|
"settings": "paramètres.",
|
||||||
"Add your Owncast instance to the Fediverse": "Add your Owncast instance to the Fediverse",
|
"Add your Owncast instance to the Fediverse": "Add your Owncast instance to the Fediverse",
|
||||||
"Enable Owncast social features": "Enable Owncast social features",
|
"Enable Owncast social features": "Enable Owncast social features",
|
||||||
"to have your instance join the Fediverse, allowing people to follow, share and engage with your live stream": "to have your instance join the Fediverse, allowing people to follow, share and engage with your live stream.",
|
"to have your instance join the Fediverse, allowing people to follow, share and engage with your live stream": "to have your instance join the Fediverse, allowing people to follow, share and engage with your live stream.",
|
||||||
"No stream is active": "No stream is active",
|
"No stream is active": "Aucune diffusion en cours",
|
||||||
"You should start one": "You should start one.",
|
"You should start one": "You should start one.",
|
||||||
"Healthy Stream": "Healthy Stream",
|
"Healthy Stream": "Healthy Stream",
|
||||||
"Yes": "Oui",
|
"Yes": "Oui",
|
||||||
@@ -46,46 +46,46 @@
|
|||||||
"Skip to page content": "Skip to page content",
|
"Skip to page content": "Skip to page content",
|
||||||
"Skip to footer": "Skip to footer",
|
"Skip to footer": "Skip to footer",
|
||||||
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
||||||
"Chat is offline": "Chat is offline",
|
"Chat is offline": "Clavardage hors ligne",
|
||||||
"Stay updated!": "Stay updated!",
|
"Stay updated!": "Stay updated!",
|
||||||
"Click and never miss future streams!": "Click and never miss future streams!",
|
"Click and never miss future streams!": "Click and never miss future streams!",
|
||||||
"This stream is offline": "This stream is offline.",
|
"This stream is offline": "This stream is offline.",
|
||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "the next time goes live",
|
||||||
"Follow": "Follow",
|
"Follow": "Suivre",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "Dernière diffusion il y a {{timeAgo}}",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"User": "Utilisateur",
|
"User": "Utilisateur",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Visible messages",
|
||||||
"Hidden messages": "Hidden messages",
|
"Hidden messages": "Hidden messages",
|
||||||
"Chat Messages": "Chat Messages",
|
"Chat Messages": "Chat Messages",
|
||||||
"Manage the messages from viewers that show up on your stream": "Manage the messages from viewers that show up on your stream.",
|
"Manage the messages from viewers that show up on your stream": "Manage the messages from viewers that show up on your stream.",
|
||||||
"Show": "Show",
|
"Show": "Afficher",
|
||||||
"Hide": "Hide",
|
"Hide": "Masquer",
|
||||||
"Visit the": "Visit the",
|
"Visit the": "Visit the",
|
||||||
"documentation": "documentation",
|
"documentation": "documentation",
|
||||||
"to configure additional details about your viewers": "to configure additional details about your viewers.",
|
"to configure additional details about your viewers": "to configure additional details about your viewers.",
|
||||||
"When a stream is active and chat is enabled, connected chat clients will be displayed here": "When a stream is active and chat is enabled, connected chat clients will be displayed here.",
|
"When a stream is active and chat is enabled, connected chat clients will be displayed here": "When a stream is active and chat is enabled, connected chat clients will be displayed here.",
|
||||||
"Connected": "Connected",
|
"Connected": "Connecté",
|
||||||
"offline": "offline",
|
"offline": "offline",
|
||||||
"Banned Users": "Banned Users",
|
"Banned Users": "Utilisateurs Bannis",
|
||||||
"IP Bans": "IP Bans",
|
"IP Bans": "IP Bannies",
|
||||||
"Moderators": "Moderators",
|
"Moderators": "Modérateurs",
|
||||||
"Hardware Info": "Hardware Info",
|
"Hardware Info": "Hardware Info",
|
||||||
"Please wait": "Please wait",
|
"Please wait": "Veuillez patienter",
|
||||||
"No hardware details have been collected yet": "No hardware details have been collected yet.",
|
"No hardware details have been collected yet": "No hardware details have been collected yet.",
|
||||||
"CPU": "UCT",
|
"CPU": "UCT",
|
||||||
"Memory": "Mémoire",
|
"Memory": "Mémoire",
|
||||||
"Disk": "Disque",
|
"Disk": "Disque",
|
||||||
"used": "used",
|
"used": "used",
|
||||||
"I want to configure my owncast instance": "I want to configure my owncast instance",
|
"I want to configure my owncast instance": "Je veux configurer mon instance Owncast",
|
||||||
"Learn more": "Learn more",
|
"Learn more": "Learn more",
|
||||||
"Help configuring my broadcasting software": "Help configuring my broadcasting software",
|
"Help configuring my broadcasting software": "Help configuring my broadcasting software",
|
||||||
"I want to embed my stream into another site": "I want to embed my stream into another site",
|
"I want to embed my stream into another site": "I want to embed my stream into another site",
|
||||||
"I want to customize my website": "I want to customize my website",
|
"I want to customize my website": "I want to customize my website",
|
||||||
"I want to tweak my video output": "I want to tweak my video output",
|
"I want to tweak my video output": "I want to tweak my video output",
|
||||||
"I want to use an external storage provider": "I want to use an external storage provider",
|
"I want to use an external storage provider": "I want to use an external storage provider",
|
||||||
"I found a bug": "I found a bug",
|
"I found a bug": "J'ai trouvé un bogue",
|
||||||
"If you found a bug, then please": "If you found a bug, then please",
|
"If you found a bug, then please": "If you found a bug, then please",
|
||||||
"let us know": "let us know",
|
"let us know": "let us know",
|
||||||
"I have a general question": "I have a general question",
|
"I have a general question": "I have a general question",
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
"Outbound Video Stream": "Outbound Video Stream",
|
"Outbound Video Stream": "Outbound Video Stream",
|
||||||
"Outbound Audio Stream": "Outbound Audio Stream",
|
"Outbound Audio Stream": "Outbound Audio Stream",
|
||||||
"Stream started": "Stream started",
|
"Stream started": "Stream started",
|
||||||
"Viewers": "Viewers",
|
"Viewers": "Spectateurs",
|
||||||
"Peak viewer count": "Peak viewer count",
|
"Peak viewer count": "Peak viewer count",
|
||||||
"Outbound Stream Details": "Outbound Stream Details",
|
"Outbound Stream Details": "Outbound Stream Details",
|
||||||
"Inbound Stream Details": "Inbound Stream Details",
|
"Inbound Stream Details": "Inbound Stream Details",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "the next time goes live",
|
||||||
"Follow": "Follow",
|
"Follow": "Follow",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "An t-ainm beo deireanach {{timeAgo}} ó shin",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"User": "User",
|
"User": "User",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Visible messages",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "the next time goes live",
|
||||||
"Follow": "Follow",
|
"Follow": "Follow",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "पिछली बार लाइव {{timeAgo}} पहले",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"User": "User",
|
"User": "User",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Visible messages",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "the next time goes live",
|
||||||
"Follow": "Follow",
|
"Follow": "Follow",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "Zadnji put uživo {{timeAgo}} prije",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"User": "User",
|
"User": "User",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Visible messages",
|
||||||
|
|||||||
+116
-116
@@ -1,128 +1,128 @@
|
|||||||
{
|
{
|
||||||
"Notify": "Notify",
|
"Notify": "Notifica",
|
||||||
"Level": "Level",
|
"Level": "Livello",
|
||||||
"Info": "Info",
|
"Info": "Info",
|
||||||
"Warning": "Warning",
|
"Warning": "Attenzione",
|
||||||
"Error": "Error",
|
"Error": "Errore",
|
||||||
"Timestamp": "Timestamp",
|
"Timestamp": "Data",
|
||||||
"Message": "Message",
|
"Message": "Messaggio",
|
||||||
"Logs": "Logs",
|
"Logs": "Registri",
|
||||||
"Link": "Link",
|
"Link": "Collegamento",
|
||||||
"No news": "No news.",
|
"No news": "Nessuna notizia.",
|
||||||
"News & Updates from Owncast": "News & Updates from Owncast",
|
"News & Updates from Owncast": "Notizie & Aggiornamenti da Owncast",
|
||||||
"Use your broadcasting software": "Use your broadcasting software",
|
"Use your broadcasting software": "Usa il tuo software di trasmissione",
|
||||||
"Learn how to point your existing software to your new server and start streaming your content": "Learn how to point your existing software to your new server and start streaming your content.",
|
"Learn how to point your existing software to your new server and start streaming your content": "Scopri come puntare il software esistente sul tuo nuovo server e iniziare a trasmettere i tuoi contenuti.",
|
||||||
"View": "View",
|
"View": "Visualizza",
|
||||||
"Overridden via command line": "Overridden via command line.",
|
"Overridden via command line": "Sovrascrivi tramite riga di comando.",
|
||||||
"Embed your video onto other sites": "Embed your video onto other sites",
|
"Embed your video onto other sites": "Incorpora il tuo video in altri siti",
|
||||||
"Learn how you can add your Owncast stream to other sites you control": "Learn how you can add your Owncast stream to other sites you control.",
|
"Learn how you can add your Owncast stream to other sites you control": "Scopri come puoi aggiungere il tuo flusso Owncast ad altri siti che controlli.",
|
||||||
"Chat is disabled": "Chat is disabled",
|
"Chat is disabled": "La chat è disattivata",
|
||||||
"Chat will continue to be disabled until you begin a live stream": "Chat will continue to be disabled until you begin a live stream.",
|
"Chat will continue to be disabled until you begin a live stream": "La chat continuerà ad essere disabilitata fino a quando non inizi un live stream.",
|
||||||
"Find an audience on the Owncast Directory": "Find an audience on the Owncast Directory",
|
"Find an audience on the Owncast Directory": "Trova un pubblico nella directory di Owncast",
|
||||||
"List yourself in the Owncast Directory and show off your stream": {
|
"List yourself in the Owncast Directory and show off your stream": {
|
||||||
" Enable it in": "List yourself in the Owncast Directory and show off your stream. Enable it in"
|
" Enable it in": "Elenca te stesso nella directory di Owncast e mostra il tuo flusso. Abilitalo in"
|
||||||
},
|
},
|
||||||
"settings": "settings.",
|
"settings": "impostazioni.",
|
||||||
"Add your Owncast instance to the Fediverse": "Add your Owncast instance to the Fediverse",
|
"Add your Owncast instance to the Fediverse": "Aggiungi la tua istanza Owncast al Fediverso",
|
||||||
"Enable Owncast social features": "Enable Owncast social features",
|
"Enable Owncast social features": "Abilita funzionalità sociali di Owncast",
|
||||||
"to have your instance join the Fediverse, allowing people to follow, share and engage with your live stream": "to have your instance join the Fediverse, allowing people to follow, share and engage with your live stream.",
|
"to have your instance join the Fediverse, allowing people to follow, share and engage with your live stream": "per far partecipare la tua istanza a Fediverse, permettendo alle persone di seguire, condividere e impegnarsi con il tuo live stream.",
|
||||||
"No stream is active": "No stream is active",
|
"No stream is active": "Nessun flusso attivo",
|
||||||
"You should start one": "You should start one.",
|
"You should start one": "Dovreste iniziarne uno.",
|
||||||
"Healthy Stream": "Healthy Stream",
|
"Healthy Stream": "Flusso Sano",
|
||||||
"Yes": "Yes",
|
"Yes": "Si",
|
||||||
"No": "No",
|
"No": "No",
|
||||||
"Playback Health": "Playback Health",
|
"Playback Health": "Salute Riproduzione",
|
||||||
"Stream health represents": "Stream health represents",
|
"Stream health represents": "Salute Flusso rappresenta",
|
||||||
"of all known players": {
|
"of all known players": {
|
||||||
" Other player status is unknown": "of all known players. Other player status is unknown."
|
" Other player status is unknown": "di tutti i lettori conosciuti. Lo stato di altri lettori è sconosciuto."
|
||||||
},
|
},
|
||||||
"TROUBLESHOOT": "TROUBLESHOOT",
|
"TROUBLESHOOT": "TROUBLESHOOT",
|
||||||
"Powered by Owncast": "Powered by Owncast",
|
"Powered by Owncast": "Fornito da Owncast",
|
||||||
"Documentation": "Documentation",
|
"Documentation": "Documentazione",
|
||||||
"Contribute": "Contribute",
|
"Contribute": "Contribuisci",
|
||||||
"Source": "Source",
|
"Source": "Fonte",
|
||||||
"Skip to player": "Skip to player",
|
"Skip to player": "Vai al lettore video",
|
||||||
"Skip to offline message": "Skip to offline message",
|
"Skip to offline message": "Salta al messaggio fuori rete",
|
||||||
"Skip to page content": "Skip to page content",
|
"Skip to page content": "Salta al contenuto della pagina",
|
||||||
"Skip to footer": "Skip to footer",
|
"Skip to footer": "Salta al piè di pagina",
|
||||||
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
"Chat will be available when the stream is live": "La chat sarà disponibile quando lo stream è in diretta.",
|
||||||
"Chat is offline": "Chat is offline",
|
"Chat is offline": "La chat è offline",
|
||||||
"Stay updated!": "Stay updated!",
|
"Stay updated!": "Resta Aggiornato!",
|
||||||
"Click and never miss future streams!": "Click and never miss future streams!",
|
"Click and never miss future streams!": "Clicca e non perderti mai le dirette future!",
|
||||||
"This stream is offline": "This stream is offline.",
|
"This stream is offline": "Questa diretta è offline.",
|
||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "la prossima volta che andrà in diretta",
|
||||||
"Follow": "Follow",
|
"Follow": "Segui",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "sul Fediverso per vedere la prossima volta che andrà in diretta",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "Ultima diretta {{timeAgo}} fa",
|
||||||
"Time": "Time",
|
"Time": "Tempo",
|
||||||
"User": "User",
|
"User": "Utente",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Messaggi visibili",
|
||||||
"Hidden messages": "Hidden messages",
|
"Hidden messages": "Messaggi nascosti",
|
||||||
"Chat Messages": "Chat Messages",
|
"Chat Messages": "Messaggi chat",
|
||||||
"Manage the messages from viewers that show up on your stream": "Manage the messages from viewers that show up on your stream.",
|
"Manage the messages from viewers that show up on your stream": "Gestisci i messaggi dagli spettatori che appaiono sul tuo flusso.",
|
||||||
"Show": "Show",
|
"Show": "Mostra",
|
||||||
"Hide": "Hide",
|
"Hide": "Nascondi",
|
||||||
"Visit the": "Visit the",
|
"Visit the": "Visita il",
|
||||||
"documentation": "documentation",
|
"documentation": "documentazione",
|
||||||
"to configure additional details about your viewers": "to configure additional details about your viewers.",
|
"to configure additional details about your viewers": "per configurare ulteriori dettagli sui tuoi spettatori.",
|
||||||
"When a stream is active and chat is enabled, connected chat clients will be displayed here": "When a stream is active and chat is enabled, connected chat clients will be displayed here.",
|
"When a stream is active and chat is enabled, connected chat clients will be displayed here": "Quando uno stream è attivo e la chat è abilitata, i client di chat connessi verranno visualizzati qui.",
|
||||||
"Connected": "Connected",
|
"Connected": "Connesso",
|
||||||
"offline": "offline",
|
"offline": "offline",
|
||||||
"Banned Users": "Banned Users",
|
"Banned Users": "Utenti bloccati",
|
||||||
"IP Bans": "IP Bans",
|
"IP Bans": "IP Bannati",
|
||||||
"Moderators": "Moderators",
|
"Moderators": "Moderatori",
|
||||||
"Hardware Info": "Hardware Info",
|
"Hardware Info": "Informazioni Hardware",
|
||||||
"Please wait": "Please wait",
|
"Please wait": "Attendere prego",
|
||||||
"No hardware details have been collected yet": "No hardware details have been collected yet.",
|
"No hardware details have been collected yet": "Non sono stati ancora raccolti dettagli hardware.",
|
||||||
"CPU": "CPU",
|
"CPU": "CPU",
|
||||||
"Memory": "Memory",
|
"Memory": "Memoria",
|
||||||
"Disk": "Disk",
|
"Disk": "Disco",
|
||||||
"used": "used",
|
"used": "usato",
|
||||||
"I want to configure my owncast instance": "I want to configure my owncast instance",
|
"I want to configure my owncast instance": "Voglio configurare la mia istanza owncast",
|
||||||
"Learn more": "Learn more",
|
"Learn more": "Per saperne di più",
|
||||||
"Help configuring my broadcasting software": "Help configuring my broadcasting software",
|
"Help configuring my broadcasting software": "Aiuta a configurare il mio software di trasmissione",
|
||||||
"I want to embed my stream into another site": "I want to embed my stream into another site",
|
"I want to embed my stream into another site": "Voglio incorporare il mio flusso in un altro sito",
|
||||||
"I want to customize my website": "I want to customize my website",
|
"I want to customize my website": "Voglio personalizzare il mio sito web",
|
||||||
"I want to tweak my video output": "I want to tweak my video output",
|
"I want to tweak my video output": "Voglio modificare la mia uscita video",
|
||||||
"I want to use an external storage provider": "I want to use an external storage provider",
|
"I want to use an external storage provider": "Voglio usare un provider di archiviazione esterno",
|
||||||
"I found a bug": "I found a bug",
|
"I found a bug": "Ho trovato un bug",
|
||||||
"If you found a bug, then please": "If you found a bug, then please",
|
"If you found a bug, then please": "Se hai trovato un bug, per favore",
|
||||||
"let us know": "let us know",
|
"let us know": "faccelo sapere",
|
||||||
"I have a general question": "I have a general question",
|
"I have a general question": "Ho una domanda generale",
|
||||||
"Most general questions are answered in our": "Most general questions are answered in our",
|
"Most general questions are answered in our": "Le domande più generali sono risposte nel nostro",
|
||||||
"FAQ": "FAQ",
|
"FAQ": "FAQ",
|
||||||
"or exist in our": "or exist in our",
|
"or exist in our": "o esiste nel nostro",
|
||||||
"discussions": "discussions",
|
"discussions": "discussioni",
|
||||||
"I want to build add-ons for Owncast": "I want to build add-ons for Owncast",
|
"I want to build add-ons for Owncast": "Voglio costruire componenti aggiuntivi per Owncast",
|
||||||
"You can build your own bots, overlays, tools and add-ons with our": "You can build your own bots, overlays, tools and add-ons with our",
|
"You can build your own bots, overlays, tools and add-ons with our": "È possibile costruire i propri bot, sovrapposizioni, strumenti e componenti aggiuntivi con il nostro",
|
||||||
"developer APIs": "developer APIs.",
|
"developer APIs": "API per sviluppatori.",
|
||||||
"How can we help you?": "How can we help you?",
|
"How can we help you?": "Come possiamo aiutarti?",
|
||||||
"Troubleshooting": "Troubleshooting",
|
"Troubleshooting": "Risoluzione problemi",
|
||||||
"Fix your problems": "Fix your problems",
|
"Fix your problems": "Risolvi i tuoi problemi",
|
||||||
"Read the Docs": "Read the Docs",
|
"Read the Docs": "Leggi la documentazione",
|
||||||
"Common tasks": "Common tasks",
|
"Common tasks": "Attività comuni",
|
||||||
"Other": "Other",
|
"Other": "Altro",
|
||||||
"Outbound Video Stream": "Outbound Video Stream",
|
"Outbound Video Stream": "Flusso Video In Uscita",
|
||||||
"Outbound Audio Stream": "Outbound Audio Stream",
|
"Outbound Audio Stream": "Flusso Audio In Uscita",
|
||||||
"Stream started": "Stream started",
|
"Stream started": "Stream avviato",
|
||||||
"Viewers": "Viewers",
|
"Viewers": "Spettatori",
|
||||||
"Peak viewer count": "Peak viewer count",
|
"Peak viewer count": "Picco conteggio spettatore",
|
||||||
"Outbound Stream Details": "Outbound Stream Details",
|
"Outbound Stream Details": "Dettagli Stream In Uscita",
|
||||||
"Inbound Stream Details": "Inbound Stream Details",
|
"Inbound Stream Details": "Dettagli Stream In Entrata",
|
||||||
"Input": "Input",
|
"Input": "Ingresso",
|
||||||
"Inbound Video Stream": "Inbound Video Stream",
|
"Inbound Video Stream": "Flusso Video In Entrata",
|
||||||
"Inbound Audio Stream": "Inbound Audio Stream",
|
"Inbound Audio Stream": "Flusso Audio In Entrata",
|
||||||
"Current stream": "Current stream",
|
"Current stream": "Flusso corrente",
|
||||||
"Last 12 hours": "Last 12 hours",
|
"Last 12 hours": "Ultime 12 ore",
|
||||||
"Last 24 hours": "Last 24 hours",
|
"Last 24 hours": "Ultime 24 ore",
|
||||||
"Last 7 days": "Last 7 days",
|
"Last 7 days": "Ultimi 7 giorni",
|
||||||
"Last 30 days": "Last 30 days",
|
"Last 30 days": "Ultimi 30 giorni",
|
||||||
"Last 3 months": "Last 3 months",
|
"Last 3 months": "Ultimi 3 mesi",
|
||||||
"Last 6 months": "Last 6 months",
|
"Last 6 months": "Ultimi 6 mesi",
|
||||||
"Viewer Info": "Viewer Info",
|
"Viewer Info": "Informazioni Spettatore",
|
||||||
"Current viewers": "Current viewers",
|
"Current viewers": "Spettatori attuali",
|
||||||
"Max viewers this stream": "Max viewers this stream",
|
"Max viewers this stream": "Massimo spettatori di questo flusso",
|
||||||
"Max viewers last stream": "Max viewers last stream",
|
"Max viewers last stream": "Massimo spettatori ultimo flusso",
|
||||||
"max viewers": "max viewers",
|
"max viewers": "massimo spettatori",
|
||||||
"No viewer data has been collected yet": "No viewer data has been collected yet."
|
"No viewer data has been collected yet": "Non sono stati ancora raccolti dati degli spettatori."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "the next time goes live",
|
||||||
"Follow": "Follow",
|
"Follow": "Follow",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "前回のライブ {{timeAgo}} 前",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"User": "User",
|
"User": "User",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Visible messages",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "the next time goes live",
|
||||||
"Follow": "Follow",
|
"Follow": "Follow",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "마지막 라이브 {{timeAgo}} 전",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"User": "User",
|
"User": "User",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Visible messages",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "the next time goes live",
|
||||||
"Follow": "Follow",
|
"Follow": "Follow",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "Siaran langsung terakhir {{timeAgo}} yang lalu",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"User": "User",
|
"User": "User",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Visible messages",
|
||||||
|
|||||||
@@ -5,14 +5,14 @@
|
|||||||
"Warning": "Waarschuwing",
|
"Warning": "Waarschuwing",
|
||||||
"Error": "Fout",
|
"Error": "Fout",
|
||||||
"Timestamp": "Timestamp",
|
"Timestamp": "Timestamp",
|
||||||
"Message": "Message",
|
"Message": "Bericht",
|
||||||
"Logs": "Logs",
|
"Logs": "Logs",
|
||||||
"Link": "Link",
|
"Link": "Link",
|
||||||
"No news": "No news.",
|
"No news": "Geen nieuws.",
|
||||||
"News & Updates from Owncast": "News & Updates from Owncast",
|
"News & Updates from Owncast": "News & Updates from Owncast",
|
||||||
"Use your broadcasting software": "Use your broadcasting software",
|
"Use your broadcasting software": "Use your broadcasting software",
|
||||||
"Learn how to point your existing software to your new server and start streaming your content": "Learn how to point your existing software to your new server and start streaming your content.",
|
"Learn how to point your existing software to your new server and start streaming your content": "Learn how to point your existing software to your new server and start streaming your content.",
|
||||||
"View": "View",
|
"View": "Bekijk",
|
||||||
"Overridden via command line": "Overridden via command line.",
|
"Overridden via command line": "Overridden via command line.",
|
||||||
"Embed your video onto other sites": "Embed your video onto other sites",
|
"Embed your video onto other sites": "Embed your video onto other sites",
|
||||||
"Learn how you can add your Owncast stream to other sites you control": "Learn how you can add your Owncast stream to other sites you control.",
|
"Learn how you can add your Owncast stream to other sites you control": "Learn how you can add your Owncast stream to other sites you control.",
|
||||||
@@ -29,8 +29,8 @@
|
|||||||
"No stream is active": "No stream is active",
|
"No stream is active": "No stream is active",
|
||||||
"You should start one": "You should start one.",
|
"You should start one": "You should start one.",
|
||||||
"Healthy Stream": "Healthy Stream",
|
"Healthy Stream": "Healthy Stream",
|
||||||
"Yes": "Yes",
|
"Yes": "Ja",
|
||||||
"No": "No",
|
"No": "Nee",
|
||||||
"Playback Health": "Playback Health",
|
"Playback Health": "Playback Health",
|
||||||
"Stream health represents": "Stream health represents",
|
"Stream health represents": "Stream health represents",
|
||||||
"of all known players": {
|
"of all known players": {
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "the next time goes live",
|
||||||
"Follow": "Follow",
|
"Follow": "Follow",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "Laatste live {{timeAgo}} geleden",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"User": "User",
|
"User": "User",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Visible messages",
|
||||||
@@ -66,18 +66,18 @@
|
|||||||
"documentation": "documentation",
|
"documentation": "documentation",
|
||||||
"to configure additional details about your viewers": "to configure additional details about your viewers.",
|
"to configure additional details about your viewers": "to configure additional details about your viewers.",
|
||||||
"When a stream is active and chat is enabled, connected chat clients will be displayed here": "When a stream is active and chat is enabled, connected chat clients will be displayed here.",
|
"When a stream is active and chat is enabled, connected chat clients will be displayed here": "When a stream is active and chat is enabled, connected chat clients will be displayed here.",
|
||||||
"Connected": "Connected",
|
"Connected": "Verbonden",
|
||||||
"offline": "offline",
|
"offline": "offline",
|
||||||
"Banned Users": "Banned Users",
|
"Banned Users": "Verbannen gebruikers",
|
||||||
"IP Bans": "IP Bans",
|
"IP Bans": "IP Bans",
|
||||||
"Moderators": "Moderators",
|
"Moderators": "Moderators",
|
||||||
"Hardware Info": "Hardware Info",
|
"Hardware Info": "Hardware Info",
|
||||||
"Please wait": "Please wait",
|
"Please wait": "Even geduld...",
|
||||||
"No hardware details have been collected yet": "No hardware details have been collected yet.",
|
"No hardware details have been collected yet": "No hardware details have been collected yet.",
|
||||||
"CPU": "CPU",
|
"CPU": "CPU",
|
||||||
"Memory": "Memory",
|
"Memory": "Geheugen",
|
||||||
"Disk": "Disk",
|
"Disk": "Schijfruimte",
|
||||||
"used": "used",
|
"used": "gebruikt",
|
||||||
"I want to configure my owncast instance": "I want to configure my owncast instance",
|
"I want to configure my owncast instance": "I want to configure my owncast instance",
|
||||||
"Learn more": "Learn more",
|
"Learn more": "Learn more",
|
||||||
"Help configuring my broadcasting software": "Help configuring my broadcasting software",
|
"Help configuring my broadcasting software": "Help configuring my broadcasting software",
|
||||||
@@ -99,26 +99,26 @@
|
|||||||
"How can we help you?": "How can we help you?",
|
"How can we help you?": "How can we help you?",
|
||||||
"Troubleshooting": "Troubleshooting",
|
"Troubleshooting": "Troubleshooting",
|
||||||
"Fix your problems": "Fix your problems",
|
"Fix your problems": "Fix your problems",
|
||||||
"Read the Docs": "Read the Docs",
|
"Read the Docs": "Lees de documentatie",
|
||||||
"Common tasks": "Common tasks",
|
"Common tasks": "Common tasks",
|
||||||
"Other": "Other",
|
"Other": "Other",
|
||||||
"Outbound Video Stream": "Outbound Video Stream",
|
"Outbound Video Stream": "Outbound Video Stream",
|
||||||
"Outbound Audio Stream": "Outbound Audio Stream",
|
"Outbound Audio Stream": "Outbound Audio Stream",
|
||||||
"Stream started": "Stream started",
|
"Stream started": "Stream started",
|
||||||
"Viewers": "Viewers",
|
"Viewers": "Kijkers",
|
||||||
"Peak viewer count": "Peak viewer count",
|
"Peak viewer count": "Peak viewer count",
|
||||||
"Outbound Stream Details": "Outbound Stream Details",
|
"Outbound Stream Details": "Outbound Stream Details",
|
||||||
"Inbound Stream Details": "Inbound Stream Details",
|
"Inbound Stream Details": "Inbound Stream Details",
|
||||||
"Input": "Input",
|
"Input": "Input",
|
||||||
"Inbound Video Stream": "Inbound Video Stream",
|
"Inbound Video Stream": "Inbound Video Stream",
|
||||||
"Inbound Audio Stream": "Inbound Audio Stream",
|
"Inbound Audio Stream": "Inbound Audio Stream",
|
||||||
"Current stream": "Current stream",
|
"Current stream": "Huidige stream",
|
||||||
"Last 12 hours": "Last 12 hours",
|
"Last 12 hours": "Laatste 12 uur",
|
||||||
"Last 24 hours": "Last 24 hours",
|
"Last 24 hours": "Laatste 24 uur",
|
||||||
"Last 7 days": "Last 7 days",
|
"Last 7 days": "Afgelopen 7 dagen",
|
||||||
"Last 30 days": "Last 30 days",
|
"Last 30 days": "Afgelopen 30 dagen",
|
||||||
"Last 3 months": "Last 3 months",
|
"Last 3 months": "Afgelopen 3 maanden",
|
||||||
"Last 6 months": "Last 6 months",
|
"Last 6 months": "Afgelopen 6 maanden",
|
||||||
"Viewer Info": "Viewer Info",
|
"Viewer Info": "Viewer Info",
|
||||||
"Current viewers": "Current viewers",
|
"Current viewers": "Current viewers",
|
||||||
"Max viewers this stream": "Max viewers this stream",
|
"Max viewers this stream": "Max viewers this stream",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "the next time goes live",
|
||||||
"Follow": "Follow",
|
"Follow": "Follow",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "Sist live {{timeAgo}} siden",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"User": "User",
|
"User": "User",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Visible messages",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "the next time goes live",
|
||||||
"Follow": "Follow",
|
"Follow": "Follow",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "ਪਿਛਲੀਆਂ ਲਾਈਵ {{timeAgo}} ਪਹਿਲਾਂ",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"User": "User",
|
"User": "User",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Visible messages",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "the next time goes live",
|
||||||
"Follow": "Follow",
|
"Follow": "Follow",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "Ostatnio na żywo {{timeAgo}} temu",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"User": "User",
|
"User": "User",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Visible messages",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "the next time goes live",
|
||||||
"Follow": "Follow",
|
"Follow": "Follow",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "Última transmissão ao vivo {{timeAgo}} ago",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"User": "User",
|
"User": "User",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Visible messages",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "the next time goes live",
|
||||||
"Follow": "Follow",
|
"Follow": "Follow",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "Последняя живая {{timeAgo}} назад",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"User": "User",
|
"User": "User",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Visible messages",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "the next time goes live",
|
||||||
"Follow": "Follow",
|
"Follow": "Follow",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "Senast live {{timeAgo}} sedan",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"User": "User",
|
"User": "User",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Visible messages",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "the next time goes live",
|
||||||
"Follow": "Follow",
|
"Follow": "Follow",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "ถ่ายทอดสดล่าสุดเมื่อ {{timeAgo}} ที่ผ่านมา",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"User": "User",
|
"User": "User",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Visible messages",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "the next time goes live",
|
||||||
"Follow": "Follow",
|
"Follow": "Follow",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "Lần phát trực tiếp cuối cùng {{timeAgo}} trước",
|
||||||
"Time": "Time",
|
"Time": "Time",
|
||||||
"User": "User",
|
"User": "User",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Visible messages",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"Notify": "通知",
|
"Notify": "通知",
|
||||||
"Level": "Level",
|
"Level": "水平",
|
||||||
"Info": "資訊",
|
"Info": "資訊",
|
||||||
"Warning": "警告",
|
"Warning": "警告",
|
||||||
"Error": "錯誤",
|
"Error": "錯誤",
|
||||||
@@ -8,11 +8,11 @@
|
|||||||
"Message": "訊息",
|
"Message": "訊息",
|
||||||
"Logs": "紀錄",
|
"Logs": "紀錄",
|
||||||
"Link": "Link",
|
"Link": "Link",
|
||||||
"No news": "沒有消息。",
|
"No news": "沒有新聞。",
|
||||||
"News & Updates from Owncast": "News & Updates from Owncast",
|
"News & Updates from Owncast": "News & Updates from Owncast",
|
||||||
"Use your broadcasting software": "Use your broadcasting software",
|
"Use your broadcasting software": "Use your broadcasting software",
|
||||||
"Learn how to point your existing software to your new server and start streaming your content": "Learn how to point your existing software to your new server and start streaming your content.",
|
"Learn how to point your existing software to your new server and start streaming your content": "Learn how to point your existing software to your new server and start streaming your content.",
|
||||||
"View": "View",
|
"View": "查看",
|
||||||
"Overridden via command line": "Overridden via command line.",
|
"Overridden via command line": "Overridden via command line.",
|
||||||
"Embed your video onto other sites": "Embed your video onto other sites",
|
"Embed your video onto other sites": "Embed your video onto other sites",
|
||||||
"Learn how you can add your Owncast stream to other sites you control": "Learn how you can add your Owncast stream to other sites you control.",
|
"Learn how you can add your Owncast stream to other sites you control": "Learn how you can add your Owncast stream to other sites you control.",
|
||||||
@@ -36,26 +36,26 @@
|
|||||||
"of all known players": {
|
"of all known players": {
|
||||||
" Other player status is unknown": "of all known players. Other player status is unknown."
|
" Other player status is unknown": "of all known players. Other player status is unknown."
|
||||||
},
|
},
|
||||||
"TROUBLESHOOT": "TROUBLESHOOT",
|
"TROUBLESHOOT": "排除故障",
|
||||||
"Powered by Owncast": "Powered by Owncast",
|
"Powered by Owncast": "由Owncast提供支援",
|
||||||
"Documentation": "文件",
|
"Documentation": "文件",
|
||||||
"Contribute": "Contribute",
|
"Contribute": "Contribute",
|
||||||
"Source": "Source",
|
"Source": "源導",
|
||||||
"Skip to player": "Skip to player",
|
"Skip to player": "Skip to player",
|
||||||
"Skip to offline message": "Skip to offline message",
|
"Skip to offline message": "跳到至離線訊息",
|
||||||
"Skip to page content": "Skip to page content",
|
"Skip to page content": "Skip to page content",
|
||||||
"Skip to footer": "Skip to footer",
|
"Skip to footer": "跳到至頁腳",
|
||||||
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
||||||
"Chat is offline": "Chat is offline",
|
"Chat is offline": "聊天室已離線。",
|
||||||
"Stay updated!": "Stay updated!",
|
"Stay updated!": "Stay updated!",
|
||||||
"Click and never miss future streams!": "Click and never miss future streams!",
|
"Click and never miss future streams!": "Click and never miss future streams!",
|
||||||
"This stream is offline": "This stream is offline.",
|
"This stream is offline": "實況已離線。",
|
||||||
"the next time goes live": "the next time goes live",
|
"the next time goes live": "the next time goes live",
|
||||||
"Follow": "追蹤",
|
"Follow": "追隨",
|
||||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||||
"Last live ago": "Last live {{timeAgo}} ago",
|
"Last live ago": "上次實況時間:{{timeAgo}}前",
|
||||||
"Time": "時間",
|
"Time": "時間",
|
||||||
"User": "User",
|
"User": "用戶",
|
||||||
"Visible messages": "Visible messages",
|
"Visible messages": "Visible messages",
|
||||||
"Hidden messages": "Hidden messages",
|
"Hidden messages": "Hidden messages",
|
||||||
"Chat Messages": "聊天訊息",
|
"Chat Messages": "聊天訊息",
|
||||||
@@ -67,17 +67,17 @@
|
|||||||
"to configure additional details about your viewers": "to configure additional details about your viewers.",
|
"to configure additional details about your viewers": "to configure additional details about your viewers.",
|
||||||
"When a stream is active and chat is enabled, connected chat clients will be displayed here": "When a stream is active and chat is enabled, connected chat clients will be displayed here.",
|
"When a stream is active and chat is enabled, connected chat clients will be displayed here": "When a stream is active and chat is enabled, connected chat clients will be displayed here.",
|
||||||
"Connected": "已連接",
|
"Connected": "已連接",
|
||||||
"offline": "offline",
|
"offline": "已離線",
|
||||||
"Banned Users": "Banned Users",
|
"Banned Users": "被遮蔽的用戶",
|
||||||
"IP Bans": "IP Bans",
|
"IP Bans": "IP 封鎖",
|
||||||
"Moderators": "版主",
|
"Moderators": "版主",
|
||||||
"Hardware Info": "Hardware Info",
|
"Hardware Info": "硬體資訊",
|
||||||
"Please wait": "請稍後",
|
"Please wait": "請稍後",
|
||||||
"No hardware details have been collected yet": "No hardware details have been collected yet.",
|
"No hardware details have been collected yet": "No hardware details have been collected yet.",
|
||||||
"CPU": "中央處理器",
|
"CPU": "中央處理器",
|
||||||
"Memory": "Memory",
|
"Memory": "記憶體",
|
||||||
"Disk": "Disk",
|
"Disk": "磁碟",
|
||||||
"used": "used",
|
"used": "使用情況",
|
||||||
"I want to configure my owncast instance": "I want to configure my owncast instance",
|
"I want to configure my owncast instance": "I want to configure my owncast instance",
|
||||||
"Learn more": "了解更多",
|
"Learn more": "了解更多",
|
||||||
"Help configuring my broadcasting software": "Help configuring my broadcasting software",
|
"Help configuring my broadcasting software": "Help configuring my broadcasting software",
|
||||||
@@ -86,9 +86,9 @@
|
|||||||
"I want to tweak my video output": "I want to tweak my video output",
|
"I want to tweak my video output": "I want to tweak my video output",
|
||||||
"I want to use an external storage provider": "I want to use an external storage provider",
|
"I want to use an external storage provider": "I want to use an external storage provider",
|
||||||
"I found a bug": "报告 Bug",
|
"I found a bug": "报告 Bug",
|
||||||
"If you found a bug, then please": "If you found a bug, then please",
|
"If you found a bug, then please": "如果您發現 Bug,請",
|
||||||
"let us know": "let us know",
|
"let us know": "告知我們",
|
||||||
"I have a general question": "I have a general question",
|
"I have a general question": "我有一個一般性問題",
|
||||||
"Most general questions are answered in our": "Most general questions are answered in our",
|
"Most general questions are answered in our": "Most general questions are answered in our",
|
||||||
"FAQ": "常見問題",
|
"FAQ": "常見問題",
|
||||||
"or exist in our": "or exist in our",
|
"or exist in our": "or exist in our",
|
||||||
@@ -99,12 +99,12 @@
|
|||||||
"How can we help you?": "我們該怎樣幫助你?",
|
"How can we help you?": "我們該怎樣幫助你?",
|
||||||
"Troubleshooting": "疑難排解",
|
"Troubleshooting": "疑難排解",
|
||||||
"Fix your problems": "Fix your problems",
|
"Fix your problems": "Fix your problems",
|
||||||
"Read the Docs": "Read the Docs",
|
"Read the Docs": "閱讀文件",
|
||||||
"Common tasks": "Common tasks",
|
"Common tasks": "Common tasks",
|
||||||
"Other": "其他",
|
"Other": "其他",
|
||||||
"Outbound Video Stream": "Outbound Video Stream",
|
"Outbound Video Stream": "Outbound Video Stream",
|
||||||
"Outbound Audio Stream": "Outbound Audio Stream",
|
"Outbound Audio Stream": "Outbound Audio Stream",
|
||||||
"Stream started": "Stream started",
|
"Stream started": "實況已開始",
|
||||||
"Viewers": "瀏覽者",
|
"Viewers": "瀏覽者",
|
||||||
"Peak viewer count": "Peak viewer count",
|
"Peak viewer count": "Peak viewer count",
|
||||||
"Outbound Stream Details": "Outbound Stream Details",
|
"Outbound Stream Details": "Outbound Stream Details",
|
||||||
@@ -119,8 +119,8 @@
|
|||||||
"Last 30 days": "Last 30 days",
|
"Last 30 days": "Last 30 days",
|
||||||
"Last 3 months": "Last 3 months",
|
"Last 3 months": "Last 3 months",
|
||||||
"Last 6 months": "Last 6 months",
|
"Last 6 months": "Last 6 months",
|
||||||
"Viewer Info": "Viewer Info",
|
"Viewer Info": "觀看人訊息",
|
||||||
"Current viewers": "Current viewers",
|
"Current viewers": "目前收看人數",
|
||||||
"Max viewers this stream": "Max viewers this stream",
|
"Max viewers this stream": "Max viewers this stream",
|
||||||
"Max viewers last stream": "Max viewers last stream",
|
"Max viewers last stream": "Max viewers last stream",
|
||||||
"max viewers": "max viewers",
|
"max viewers": "max viewers",
|
||||||
|
|||||||
Generated
+39
-39
@@ -57,7 +57,7 @@
|
|||||||
"workbox-precaching": "^7.0.0",
|
"workbox-precaching": "^7.0.0",
|
||||||
"workbox-window": "^7.0.0",
|
"workbox-window": "^7.0.0",
|
||||||
"xstate": "4.38.3",
|
"xstate": "4.38.3",
|
||||||
"yaml": "2.6.0"
|
"yaml": "2.7.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.26.0",
|
"@babel/core": "7.26.0",
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
"@types/classnames": "2.3.4",
|
"@types/classnames": "2.3.4",
|
||||||
"@types/jest": "^29.5.0",
|
"@types/jest": "^29.5.0",
|
||||||
"@types/markdown-it": "14.1.2",
|
"@types/markdown-it": "14.1.2",
|
||||||
"@types/node": "22.10.6",
|
"@types/node": "22.10.7",
|
||||||
"@types/prop-types": "15.7.14",
|
"@types/prop-types": "15.7.14",
|
||||||
"@types/react": "18.3.18",
|
"@types/react": "18.3.18",
|
||||||
"@types/react-linkify": "1.0.4",
|
"@types/react-linkify": "1.0.4",
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "8.13.0",
|
"@typescript-eslint/eslint-plugin": "8.13.0",
|
||||||
"@typescript-eslint/parser": "8.13.0",
|
"@typescript-eslint/parser": "8.13.0",
|
||||||
"babel-loader": "9.2.1",
|
"babel-loader": "9.2.1",
|
||||||
"chromatic": "11.24.0",
|
"chromatic": "11.25.0",
|
||||||
"css-loader": "7.1.2",
|
"css-loader": "7.1.2",
|
||||||
"cypress": "^13.2.0",
|
"cypress": "^13.2.0",
|
||||||
"eslint": "8.57.1",
|
"eslint": "8.57.1",
|
||||||
@@ -108,7 +108,7 @@
|
|||||||
"eslint-plugin-prettier": "^5.0.0",
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
"eslint-plugin-react": "7.37.4",
|
"eslint-plugin-react": "7.37.4",
|
||||||
"eslint-plugin-react-hooks": "4.6.2",
|
"eslint-plugin-react-hooks": "4.6.2",
|
||||||
"eslint-plugin-storybook": "0.11.0",
|
"eslint-plugin-storybook": "0.11.2",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"html-webpack-plugin": "5.6.3",
|
"html-webpack-plugin": "5.6.3",
|
||||||
"install": "^0.13.0",
|
"install": "^0.13.0",
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.31",
|
||||||
"prettier": "3.4.2",
|
"prettier": "3.4.2",
|
||||||
"resolve-url-loader": "^5.0.0",
|
"resolve-url-loader": "^5.0.0",
|
||||||
"sass": "1.80.6",
|
"sass": "1.83.4",
|
||||||
"sass-loader": "^14.0.0",
|
"sass-loader": "^14.0.0",
|
||||||
"storybook": "^8.3.6",
|
"storybook": "^8.3.6",
|
||||||
"storybook-addon-fetch-mock": "^2.0.1",
|
"storybook-addon-fetch-mock": "^2.0.1",
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
"stylelint-config-standard": "^36.0.0",
|
"stylelint-config-standard": "^36.0.0",
|
||||||
"stylelint-config-standard-scss": "^14.0.0",
|
"stylelint-config-standard-scss": "^14.0.0",
|
||||||
"ts-jest": "^29.1.0",
|
"ts-jest": "^29.1.0",
|
||||||
"typescript": "5.6.3"
|
"typescript": "5.7.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@adobe/css-tools": {
|
"node_modules/@adobe/css-tools": {
|
||||||
@@ -9163,9 +9163,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.10.6",
|
"version": "22.10.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz",
|
||||||
"integrity": "sha512-qNiuwC4ZDAUNcY47xgaSuS92cjf8JbSUoaKS77bmLG1rU7MlATVSiw/IlrjtIyyskXBZ8KkNfjK/P5na7rgXbQ==",
|
"integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.20.0"
|
"undici-types": "~6.20.0"
|
||||||
@@ -12186,9 +12186,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chromatic": {
|
"node_modules/chromatic": {
|
||||||
"version": "11.24.0",
|
"version": "11.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/chromatic/-/chromatic-11.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/chromatic/-/chromatic-11.25.0.tgz",
|
||||||
"integrity": "sha512-IocIxnxera27bRA51HHtfV9/Daqgj0E4BWYV12/nlf7qPJW1T82raQ5ypxZDJA4bXQywxOzpG2e6WWYvz/AwHA==",
|
"integrity": "sha512-P2BVe0rRLS9WM+eSG3u1SRg0Mi2vopsdPs2FiXwUiPqZ6hs9fe66d3Pnt7CfQ22v2jThuPEXYjYEeuL75a16Bw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -15915,9 +15915,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-storybook": {
|
"node_modules/eslint-plugin-storybook": {
|
||||||
"version": "0.11.0",
|
"version": "0.11.2",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-0.11.2.tgz",
|
||||||
"integrity": "sha512-MvPJgF+ORwgK04a1CY5itO4pwdAOFIRqczlNEHL62+4Ocvj1d61GWRqIdeX1BNCKno6fdPC6TksUHCZMGsq26g==",
|
"integrity": "sha512-0Z4DUklJrC+GHjCRXa7PYfPzWC15DaVnwaOYenpgXiCEijXPZkLKCms+rHhtoRcWccP7Z8DpOOaP1gc3P9oOwg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -15929,7 +15929,7 @@
|
|||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"eslint": ">=6"
|
"eslint": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-scope": {
|
"node_modules/eslint-scope": {
|
||||||
@@ -19258,9 +19258,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/immutable": {
|
"node_modules/immutable": {
|
||||||
"version": "4.3.7",
|
"version": "5.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz",
|
||||||
"integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==",
|
"integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
@@ -22493,9 +22493,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/knip": {
|
"node_modules/knip": {
|
||||||
"version": "5.42.1",
|
"version": "5.42.2",
|
||||||
"resolved": "https://registry.npmjs.org/knip/-/knip-5.42.1.tgz",
|
"resolved": "https://registry.npmjs.org/knip/-/knip-5.42.2.tgz",
|
||||||
"integrity": "sha512-xTnwo0I5TLAEU1BNqi8EwnvxI/5yJUJmBiXJdnS0+2FDE6WQII3upoImuUbx1GxrftHmY+prqfL6XuO6JEtYBw==",
|
"integrity": "sha512-hVtZ6V59COFz3Y0/BHrWMlPAx82EdX/xFHXbutIRSNfJFPMGmIpxLBWTg35F4XQJGwlu5uWiJf8rBYYkmlYWWQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -36992,14 +36992,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sass": {
|
"node_modules/sass": {
|
||||||
"version": "1.80.6",
|
"version": "1.83.4",
|
||||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.80.6.tgz",
|
"resolved": "https://registry.npmjs.org/sass/-/sass-1.83.4.tgz",
|
||||||
"integrity": "sha512-ccZgdHNiBF1NHBsWvacvT5rju3y1d/Eu+8Ex6c21nHp2lZGLBEtuwc415QfiI1PJa1TpCo3iXwwSRjRpn2Ckjg==",
|
"integrity": "sha512-B1bozCeNQiOgDcLd33e2Cs2U60wZwjUUXzh900ZyQF5qUasvMdDZYbQ566LJu7cqR+sAHlAfO6RMkaID5s6qpA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chokidar": "^4.0.0",
|
"chokidar": "^4.0.0",
|
||||||
"immutable": "^4.0.0",
|
"immutable": "^5.0.2",
|
||||||
"source-map-js": ">=0.6.2 <2.0.0"
|
"source-map-js": ">=0.6.2 <2.0.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -37070,13 +37070,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sass/node_modules/readdirp": {
|
"node_modules/sass/node_modules/readdirp": {
|
||||||
"version": "4.0.2",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.1.tgz",
|
||||||
"integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==",
|
"integrity": "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 14.16.0"
|
"node": ">= 14.18.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "individual",
|
"type": "individual",
|
||||||
@@ -39856,9 +39856,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.6.3",
|
"version": "5.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
||||||
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
|
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
@@ -39965,9 +39965,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici": {
|
"node_modules/undici": {
|
||||||
"version": "6.21.0",
|
"version": "6.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz",
|
||||||
"integrity": "sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw==",
|
"integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.17"
|
"node": ">=18.17"
|
||||||
@@ -41959,9 +41959,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/yaml": {
|
"node_modules/yaml": {
|
||||||
"version": "2.6.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
|
||||||
"integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==",
|
"integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"yaml": "bin.mjs"
|
"yaml": "bin.mjs"
|
||||||
|
|||||||
+6
-6
@@ -68,7 +68,7 @@
|
|||||||
"workbox-precaching": "^7.0.0",
|
"workbox-precaching": "^7.0.0",
|
||||||
"workbox-window": "^7.0.0",
|
"workbox-window": "^7.0.0",
|
||||||
"xstate": "4.38.3",
|
"xstate": "4.38.3",
|
||||||
"yaml": "2.6.0"
|
"yaml": "2.7.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.26.0",
|
"@babel/core": "7.26.0",
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
"@types/classnames": "2.3.4",
|
"@types/classnames": "2.3.4",
|
||||||
"@types/jest": "^29.5.0",
|
"@types/jest": "^29.5.0",
|
||||||
"@types/markdown-it": "14.1.2",
|
"@types/markdown-it": "14.1.2",
|
||||||
"@types/node": "22.10.6",
|
"@types/node": "22.10.7",
|
||||||
"@types/prop-types": "15.7.14",
|
"@types/prop-types": "15.7.14",
|
||||||
"@types/react": "18.3.18",
|
"@types/react": "18.3.18",
|
||||||
"@types/react-linkify": "1.0.4",
|
"@types/react-linkify": "1.0.4",
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "8.13.0",
|
"@typescript-eslint/eslint-plugin": "8.13.0",
|
||||||
"@typescript-eslint/parser": "8.13.0",
|
"@typescript-eslint/parser": "8.13.0",
|
||||||
"babel-loader": "9.2.1",
|
"babel-loader": "9.2.1",
|
||||||
"chromatic": "11.24.0",
|
"chromatic": "11.25.0",
|
||||||
"css-loader": "7.1.2",
|
"css-loader": "7.1.2",
|
||||||
"cypress": "^13.2.0",
|
"cypress": "^13.2.0",
|
||||||
"eslint": "8.57.1",
|
"eslint": "8.57.1",
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
"eslint-plugin-prettier": "^5.0.0",
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
"eslint-plugin-react": "7.37.4",
|
"eslint-plugin-react": "7.37.4",
|
||||||
"eslint-plugin-react-hooks": "4.6.2",
|
"eslint-plugin-react-hooks": "4.6.2",
|
||||||
"eslint-plugin-storybook": "0.11.0",
|
"eslint-plugin-storybook": "0.11.2",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"html-webpack-plugin": "5.6.3",
|
"html-webpack-plugin": "5.6.3",
|
||||||
"install": "^0.13.0",
|
"install": "^0.13.0",
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.31",
|
||||||
"prettier": "3.4.2",
|
"prettier": "3.4.2",
|
||||||
"resolve-url-loader": "^5.0.0",
|
"resolve-url-loader": "^5.0.0",
|
||||||
"sass": "1.80.6",
|
"sass": "1.83.4",
|
||||||
"sass-loader": "^14.0.0",
|
"sass-loader": "^14.0.0",
|
||||||
"storybook": "^8.3.6",
|
"storybook": "^8.3.6",
|
||||||
"storybook-addon-fetch-mock": "^2.0.1",
|
"storybook-addon-fetch-mock": "^2.0.1",
|
||||||
@@ -143,7 +143,7 @@
|
|||||||
"stylelint-config-standard": "^36.0.0",
|
"stylelint-config-standard": "^36.0.0",
|
||||||
"stylelint-config-standard-scss": "^14.0.0",
|
"stylelint-config-standard-scss": "^14.0.0",
|
||||||
"ts-jest": "^29.1.0",
|
"ts-jest": "^29.1.0",
|
||||||
"typescript": "5.6.3"
|
"typescript": "5.7.3"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@emoji-mart/react": {
|
"@emoji-mart/react": {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ package admin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -13,11 +12,13 @@ import (
|
|||||||
"github.com/owncast/owncast/core/chat/events"
|
"github.com/owncast/owncast/core/chat/events"
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
"github.com/owncast/owncast/persistence/authrepository"
|
"github.com/owncast/owncast/persistence/authrepository"
|
||||||
|
"github.com/owncast/owncast/persistence/chatmessagerepository"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
"github.com/owncast/owncast/persistence/configrepository"
|
||||||
"github.com/owncast/owncast/persistence/userrepository"
|
"github.com/owncast/owncast/persistence/userrepository"
|
||||||
"github.com/owncast/owncast/utils"
|
"github.com/owncast/owncast/utils"
|
||||||
"github.com/owncast/owncast/webserver/handlers/generated"
|
"github.com/owncast/owncast/webserver/handlers/generated"
|
||||||
webutils "github.com/owncast/owncast/webserver/utils"
|
webutils "github.com/owncast/owncast/webserver/utils"
|
||||||
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -146,13 +147,20 @@ func UpdateUserEnabled(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
func updateUserStatus(request generated.UpdateUserEnabledJSONBody) error {
|
func updateUserStatus(request generated.UpdateUserEnabledJSONBody) error {
|
||||||
userRepository := userrepository.Get()
|
userRepository := userrepository.Get()
|
||||||
|
chatMessageRepository := chatmessagerepository.Get()
|
||||||
|
|
||||||
if err := userRepository.SetEnabled(*request.UserId, *request.Enabled); err != nil {
|
if err := userRepository.SetEnabled(*request.UserId, *request.Enabled); err != nil {
|
||||||
log.Errorln("error changing user enabled status", err)
|
log.Errorln("error changing user enabled status", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !*request.Enabled {
|
messageIDs, err := chatMessageRepository.GetMessageIdsForUserID(*request.UserId)
|
||||||
if err := chat.SetMessageVisibilityForUserID(*request.UserId, *request.Enabled); err != nil {
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "error fetching user messages")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !*request.Enabled && len(messageIDs) > 0 {
|
||||||
|
if err := chat.SetMessagesVisibility(messageIDs, *request.Enabled); err != nil {
|
||||||
log.Errorln("error changing user messages visibility", err)
|
log.Errorln("error changing user messages visibility", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -247,7 +255,8 @@ func GetModerators(w http.ResponseWriter, r *http.Request) {
|
|||||||
func GetChatMessages(w http.ResponseWriter, r *http.Request) {
|
func GetChatMessages(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
messages := chat.GetChatModerationHistory()
|
chatMessageRepository := chatmessagerepository.Get()
|
||||||
|
messages := chatMessageRepository.GetChatModerationHistory()
|
||||||
webutils.WriteResponse(w, messages)
|
webutils.WriteResponse(w, messages)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,7 +352,8 @@ func SendIntegrationChatMessage(integration models.ExternalAPIUser, w http.Respo
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
chat.SaveUserMessage(event)
|
chatMessageRepository := chatmessagerepository.Get()
|
||||||
|
chatMessageRepository.SaveUserMessage(event)
|
||||||
|
|
||||||
webutils.WriteSimpleResponse(w, true, "sent")
|
webutils.WriteSimpleResponse(w, true, "sent")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/owncast/owncast/config"
|
"github.com/owncast/owncast/config"
|
||||||
"github.com/owncast/owncast/core/chat"
|
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
|
"github.com/owncast/owncast/persistence/chatmessagerepository"
|
||||||
"github.com/owncast/owncast/persistence/configrepository"
|
"github.com/owncast/owncast/persistence/configrepository"
|
||||||
"github.com/owncast/owncast/persistence/userrepository"
|
"github.com/owncast/owncast/persistence/userrepository"
|
||||||
"github.com/owncast/owncast/utils"
|
"github.com/owncast/owncast/utils"
|
||||||
@@ -33,7 +33,8 @@ func getChatMessages(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
messages := chat.GetChatHistory()
|
chatMessageRepository := chatmessagerepository.Get()
|
||||||
|
messages := chatMessageRepository.GetChatHistory()
|
||||||
|
|
||||||
if err := json.NewEncoder(w).Encode(messages); err != nil {
|
if err := json.NewEncoder(w).Encode(messages); err != nil {
|
||||||
log.Debugln(err)
|
log.Debugln(err)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/owncast/owncast/core/chat"
|
"github.com/owncast/owncast/core/chat"
|
||||||
"github.com/owncast/owncast/core/chat/events"
|
"github.com/owncast/owncast/core/chat/events"
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
|
"github.com/owncast/owncast/persistence/chatmessagerepository"
|
||||||
"github.com/owncast/owncast/persistence/userrepository"
|
"github.com/owncast/owncast/persistence/userrepository"
|
||||||
"github.com/owncast/owncast/webserver/utils"
|
"github.com/owncast/owncast/webserver/utils"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -58,7 +59,8 @@ func GetUserDetails(w http.ResponseWriter, r *http.Request) {
|
|||||||
clients[i] = client
|
clients[i] = client
|
||||||
}
|
}
|
||||||
|
|
||||||
messages, err := chat.GetMessagesFromUser(uid)
|
chatMessagesRepository := chatmessagerepository.Get()
|
||||||
|
messages, err := chatMessagesRepository.GetMessagesFromUser(uid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user