mirror of
https://github.com/owncast/owncast.git
synced 2024-11-24 05:38:58 +03:00
Expanded linting + fix warnings (#1396)
* Expand the linters and types of warnings to improve consistency and safety * Fail lint workflow if there are errors * golint has been replaced by revive * Hand-pick some of the default exclude list * Ignore error when trying to delete preview gif * Ignore linter warning opening playlist path * Rename user field Id -> ID * A bunch of renames to address linter warnings * Rename ChatClient -> Client per linter suggestion best practice * Rename ChatServer -> Server per linter suggestion best practice * More linter warning fixes * Add missing comments to all exported functions and properties
This commit is contained in:
parent
70e9f4945f
commit
c6c6f0233d
57 changed files with 331 additions and 186 deletions
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
|
@ -17,7 +17,7 @@ jobs:
|
||||||
uses: golangci/golangci-lint-action@v2
|
uses: golangci/golangci-lint-action@v2
|
||||||
with:
|
with:
|
||||||
# Optional: golangci-lint command line arguments.
|
# Optional: golangci-lint command line arguments.
|
||||||
args: --issues-exit-code=0 --timeout 2m0s
|
args: --timeout 5m0s
|
||||||
# Optional: working directory, useful for monorepos
|
# Optional: working directory, useful for monorepos
|
||||||
# working-directory: somedir
|
# working-directory: somedir
|
||||||
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
||||||
|
|
|
@ -1,14 +1,28 @@
|
||||||
run:
|
run:
|
||||||
tests: false
|
tests: false
|
||||||
|
modules-download-mode: readonly
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
|
# The linter has a default list of ignorable errors. Turning this on will enable that list.
|
||||||
|
exclude-use-default: false
|
||||||
|
|
||||||
|
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
|
||||||
max-issues-per-linter: 0
|
max-issues-per-linter: 0
|
||||||
|
|
||||||
|
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
|
||||||
max-same-issues: 0
|
max-same-issues: 0
|
||||||
|
|
||||||
|
exclude:
|
||||||
|
- Subprocess launch(ed with variable|ing should be audited)
|
||||||
|
- Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*print(f|ln)?|os\.(Un)?Setenv). is not checked
|
||||||
|
- G307 # Allow closing files as a defer without checking error.
|
||||||
|
- composite literal uses unkeyed fields
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
- bodyclose
|
- bodyclose
|
||||||
- dupl
|
- dupl
|
||||||
|
- errcheck
|
||||||
- exportloopref
|
- exportloopref
|
||||||
- goconst
|
- goconst
|
||||||
- godot
|
- godot
|
||||||
|
@ -16,10 +30,13 @@ linters:
|
||||||
- goimports
|
- goimports
|
||||||
- goprintffuncname
|
- goprintffuncname
|
||||||
- gosec
|
- gosec
|
||||||
|
- govet
|
||||||
- misspell
|
- misspell
|
||||||
- prealloc
|
- prealloc
|
||||||
|
- revive
|
||||||
- rowserrcheck
|
- rowserrcheck
|
||||||
- sqlclosecheck
|
- sqlclosecheck
|
||||||
|
- staticcheck
|
||||||
- unconvert
|
- unconvert
|
||||||
- unparam
|
- unparam
|
||||||
- whitespace
|
- whitespace
|
||||||
|
@ -27,4 +44,4 @@ linters:
|
||||||
linters-settings:
|
linters-settings:
|
||||||
govet:
|
govet:
|
||||||
disable:
|
disable:
|
||||||
- composites
|
- composite
|
||||||
|
|
|
@ -22,7 +22,7 @@ var VersionNumber = StaticVersionNumber
|
||||||
// WebServerPort is the port for Owncast's webserver that is used for this execution of the service.
|
// WebServerPort is the port for Owncast's webserver that is used for this execution of the service.
|
||||||
var WebServerPort = 8080
|
var WebServerPort = 8080
|
||||||
|
|
||||||
// Bind WebServer to this IP address. Be secure by default.
|
// WebServerIP is the IP address to bind the web server to. All interfaces by default.
|
||||||
var WebServerIP = "0.0.0.0"
|
var WebServerIP = "0.0.0.0"
|
||||||
|
|
||||||
// InternalHLSListenerPort is the port for HLS writes that is used for this execution of the service.
|
// InternalHLSListenerPort is the port for HLS writes that is used for this execution of the service.
|
||||||
|
@ -34,6 +34,7 @@ var GitCommit = ""
|
||||||
// BuildPlatform is the optional platform this release was built for.
|
// BuildPlatform is the optional platform this release was built for.
|
||||||
var BuildPlatform = "dev"
|
var BuildPlatform = "dev"
|
||||||
|
|
||||||
|
// GetCommit will return an identifier used for identifying the point in time this build took place.
|
||||||
func GetCommit() string {
|
func GetCommit() string {
|
||||||
if GitCommit == "" {
|
if GitCommit == "" {
|
||||||
GitCommit = time.Now().Format("20060102")
|
GitCommit = time.Now().Format("20060102")
|
||||||
|
@ -42,11 +43,12 @@ func GetCommit() string {
|
||||||
return GitCommit
|
return GitCommit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultForbiddenUsernames are a list of usernames forbidden from being used in chat.
|
||||||
var DefaultForbiddenUsernames = []string{
|
var DefaultForbiddenUsernames = []string{
|
||||||
"owncast", "operator", "admin", "system",
|
"owncast", "operator", "admin", "system",
|
||||||
}
|
}
|
||||||
|
|
||||||
// The maximum payload we will allow to to be received via the chat socket.
|
// MaxSocketPayloadSize is the maximum payload we will allow to to be received via the chat socket.
|
||||||
const MaxSocketPayloadSize = 2048
|
const MaxSocketPayloadSize = 2048
|
||||||
|
|
||||||
// GetReleaseString gets the version string.
|
// GetReleaseString gets the version string.
|
||||||
|
|
|
@ -49,6 +49,7 @@ func UpdateMessageVisibility(w http.ResponseWriter, r *http.Request) {
|
||||||
controllers.WriteSimpleResponse(w, true, "changed")
|
controllers.WriteSimpleResponse(w, true, "changed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateUserEnabled enable or disable a single user by ID.
|
||||||
func UpdateUserEnabled(w http.ResponseWriter, r *http.Request) {
|
func UpdateUserEnabled(w http.ResponseWriter, r *http.Request) {
|
||||||
type blockUserRequest struct {
|
type blockUserRequest struct {
|
||||||
UserID string `json:"userId"`
|
UserID string `json:"userId"`
|
||||||
|
@ -77,7 +78,7 @@ func UpdateUserEnabled(w http.ResponseWriter, r *http.Request) {
|
||||||
// Hide/show the user's chat messages if disabling.
|
// Hide/show the user's chat messages if disabling.
|
||||||
// Leave hidden messages hidden to be safe.
|
// Leave hidden messages hidden to be safe.
|
||||||
if !request.Enabled {
|
if !request.Enabled {
|
||||||
if err := chat.SetMessageVisibilityForUserId(request.UserID, request.Enabled); err != nil {
|
if err := chat.SetMessageVisibilityForUserID(request.UserID, request.Enabled); err != nil {
|
||||||
log.Errorln("error changing user messages visibility", err)
|
log.Errorln("error changing user messages visibility", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,13 +86,14 @@ func UpdateUserEnabled(w http.ResponseWriter, r *http.Request) {
|
||||||
// Forcefully disconnect the user from the chat
|
// Forcefully disconnect the user from the chat
|
||||||
if !request.Enabled {
|
if !request.Enabled {
|
||||||
chat.DisconnectUser(request.UserID)
|
chat.DisconnectUser(request.UserID)
|
||||||
disconnectedUser := user.GetUserById(request.UserID)
|
disconnectedUser := user.GetUserByID(request.UserID)
|
||||||
_ = chat.SendSystemAction(fmt.Sprintf("**%s** has been removed from chat.", disconnectedUser.DisplayName), true)
|
_ = chat.SendSystemAction(fmt.Sprintf("**%s** has been removed from chat.", disconnectedUser.DisplayName), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
controllers.WriteSimpleResponse(w, true, fmt.Sprintf("%s enabled: %t", request.UserID, request.Enabled))
|
controllers.WriteSimpleResponse(w, true, fmt.Sprintf("%s enabled: %t", request.UserID, request.Enabled))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDisabledUsers will return all the disabled users.
|
||||||
func GetDisabledUsers(w http.ResponseWriter, r *http.Request) {
|
func GetDisabledUsers(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
@ -130,6 +132,7 @@ func SendUserMessage(integration user.ExternalAPIUser, w http.ResponseWriter, r
|
||||||
controllers.BadRequestHandler(w, errors.New("no longer supported. see /api/integrations/chat/send"))
|
controllers.BadRequestHandler(w, errors.New("no longer supported. see /api/integrations/chat/send"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendIntegrationChatMessage will send a chat message on behalf of an external chat integration.
|
||||||
func SendIntegrationChatMessage(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) {
|
func SendIntegrationChatMessage(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
@ -155,7 +158,7 @@ func SendIntegrationChatMessage(integration user.ExternalAPIUser, w http.Respons
|
||||||
}
|
}
|
||||||
|
|
||||||
event.User = &user.User{
|
event.User = &user.User{
|
||||||
Id: integration.Id,
|
ID: integration.ID,
|
||||||
DisplayName: name,
|
DisplayName: name,
|
||||||
DisplayColor: integration.DisplayColor,
|
DisplayColor: integration.DisplayColor,
|
||||||
CreatedAt: integration.CreatedAt,
|
CreatedAt: integration.CreatedAt,
|
||||||
|
|
|
@ -72,6 +72,7 @@ func SetStreamTitle(w http.ResponseWriter, r *http.Request) {
|
||||||
controllers.WriteSimpleResponse(w, true, "changed")
|
controllers.WriteSimpleResponse(w, true, "changed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExternalSetStreamTitle will change the stream title on behalf of an external integration API request.
|
||||||
func ExternalSetStreamTitle(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) {
|
func ExternalSetStreamTitle(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) {
|
||||||
SetStreamTitle(w, r)
|
SetStreamTitle(w, r)
|
||||||
}
|
}
|
||||||
|
@ -298,11 +299,12 @@ func SetWebServerPort(w http.ResponseWriter, r *http.Request) {
|
||||||
if err := data.SetHTTPPortNumber(port); err != nil {
|
if err := data.SetHTTPPortNumber(port); err != nil {
|
||||||
controllers.WriteSimpleResponse(w, false, err.Error())
|
controllers.WriteSimpleResponse(w, false, err.Error())
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
controllers.WriteSimpleResponse(w, true, "HTTP port set")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
controllers.WriteSimpleResponse(w, true, "HTTP port set")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
controllers.WriteSimpleResponse(w, false, "Invalid type or value, port must be a number")
|
controllers.WriteSimpleResponse(w, false, "Invalid type or value, port must be a number")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,14 +324,14 @@ func SetWebServerIP(w http.ResponseWriter, r *http.Request) {
|
||||||
if err := data.SetHTTPListenAddress(ip.String()); err != nil {
|
if err := data.SetHTTPListenAddress(ip.String()); err != nil {
|
||||||
controllers.WriteSimpleResponse(w, false, err.Error())
|
controllers.WriteSimpleResponse(w, false, err.Error())
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
controllers.WriteSimpleResponse(w, true, "HTTP listen address set")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
controllers.WriteSimpleResponse(w, false, "Invalid IP address")
|
controllers.WriteSimpleResponse(w, true, "HTTP listen address set")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
controllers.WriteSimpleResponse(w, false, "Invalid IP address")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
controllers.WriteSimpleResponse(w, false, "Invalid type or value, IP address must be a string")
|
controllers.WriteSimpleResponse(w, false, "Invalid type or value, IP address must be a string")
|
||||||
}
|
}
|
||||||
|
@ -427,7 +429,7 @@ func SetS3Configuration(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if newS3Config.Value.Enabled {
|
if newS3Config.Value.Enabled {
|
||||||
if newS3Config.Value.Endpoint == "" || !utils.IsValidUrl((newS3Config.Value.Endpoint)) {
|
if newS3Config.Value.Endpoint == "" || !utils.IsValidURL((newS3Config.Value.Endpoint)) {
|
||||||
controllers.WriteSimpleResponse(w, false, "s3 support requires an endpoint")
|
controllers.WriteSimpleResponse(w, false, "s3 support requires an endpoint")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ func RegisterAnonymousChatUser(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type registerAnonymousUserResponse struct {
|
type registerAnonymousUserResponse struct {
|
||||||
Id string `json:"id"`
|
ID string `json:"id"`
|
||||||
AccessToken string `json:"accessToken"`
|
AccessToken string `json:"accessToken"`
|
||||||
DisplayName string `json:"displayName"`
|
DisplayName string `json:"displayName"`
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ func RegisterAnonymousChatUser(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
response := registerAnonymousUserResponse{
|
response := registerAnonymousUserResponse{
|
||||||
Id: newUser.Id,
|
ID: newUser.ID,
|
||||||
AccessToken: newUser.AccessToken,
|
AccessToken: newUser.AccessToken,
|
||||||
DisplayName: newUser.DisplayName,
|
DisplayName: newUser.DisplayName,
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// Use this as an opportunity to mark this viewer as active.
|
// Use this as an opportunity to mark this viewer as active.
|
||||||
id := utils.GenerateClientIDFromRequest(r)
|
id := utils.GenerateClientIDFromRequest(r)
|
||||||
core.SetViewerIdActive(id)
|
core.SetViewerIDActive(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a cache control max-age header
|
// Set a cache control max-age header
|
||||||
|
@ -86,8 +86,8 @@ func handleScraperMetadataPage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
scheme := "http"
|
scheme := "http"
|
||||||
|
|
||||||
if siteUrl := data.GetServerURL(); siteUrl != "" {
|
if siteURL := data.GetServerURL(); siteURL != "" {
|
||||||
if parsed, err := url.Parse(siteUrl); err == nil && parsed.Scheme != "" {
|
if parsed, err := url.Parse(siteURL); err == nil && parsed.Scheme != "" {
|
||||||
scheme = parsed.Scheme
|
scheme = parsed.Scheme
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,5 +94,5 @@ func writeBytesAsImage(data []byte, contentType string, w http.ResponseWriter, c
|
||||||
}
|
}
|
||||||
|
|
||||||
func getImage(path string) ([]byte, error) {
|
func getImage(path string) ([]byte, error) {
|
||||||
return ioutil.ReadFile(path)
|
return ioutil.ReadFile(path) // nolint
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,8 @@ import (
|
||||||
"github.com/owncast/owncast/utils"
|
"github.com/owncast/owncast/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Ping is fired by a client to show they are still an active viewer.
|
||||||
func Ping(w http.ResponseWriter, r *http.Request) {
|
func Ping(w http.ResponseWriter, r *http.Request) {
|
||||||
id := utils.GenerateClientIDFromRequest(r)
|
id := utils.GenerateClientIDFromRequest(r)
|
||||||
core.SetViewerIdActive(id)
|
core.SetViewerIDActive(id)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
var getStatus func() models.Status
|
var getStatus func() models.Status
|
||||||
|
|
||||||
|
// Start begins the chat server.
|
||||||
func Start(getStatusFunc func() models.Status) error {
|
func Start(getStatusFunc func() models.Status) error {
|
||||||
setupPersistence()
|
setupPersistence()
|
||||||
|
|
||||||
|
@ -26,11 +27,11 @@ func Start(getStatusFunc func() models.Status) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetClientsForUser will return chat connections that are owned by a specific user.
|
// GetClientsForUser will return chat connections that are owned by a specific user.
|
||||||
func GetClientsForUser(userID string) ([]*ChatClient, error) {
|
func GetClientsForUser(userID string) ([]*Client, error) {
|
||||||
clients := map[string][]*ChatClient{}
|
clients := map[string][]*Client{}
|
||||||
|
|
||||||
for _, client := range _server.clients {
|
for _, client := range _server.clients {
|
||||||
clients[client.User.Id] = append(clients[client.User.Id], client)
|
clients[client.User.ID] = append(clients[client.User.ID], client)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, exists := clients[userID]; !exists {
|
if _, exists := clients[userID]; !exists {
|
||||||
|
@ -40,8 +41,9 @@ func GetClientsForUser(userID string) ([]*ChatClient, error) {
|
||||||
return clients[userID], nil
|
return clients[userID], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetClients() []*ChatClient {
|
// GetClients will return all the current chat clients connected.
|
||||||
clients := []*ChatClient{}
|
func GetClients() []*Client {
|
||||||
|
clients := []*Client{}
|
||||||
|
|
||||||
// Convert the keyed map to a slice.
|
// Convert the keyed map to a slice.
|
||||||
for _, client := range _server.clients {
|
for _, client := range _server.clients {
|
||||||
|
@ -55,6 +57,7 @@ func GetClients() []*ChatClient {
|
||||||
return clients
|
return clients
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendSystemMessage will send a message string as a system message to all clients.
|
||||||
func SendSystemMessage(text string, ephemeral bool) error {
|
func SendSystemMessage(text string, ephemeral bool) error {
|
||||||
message := events.SystemMessageEvent{
|
message := events.SystemMessageEvent{
|
||||||
MessageEvent: events.MessageEvent{
|
MessageEvent: events.MessageEvent{
|
||||||
|
@ -69,12 +72,13 @@ func SendSystemMessage(text string, ephemeral bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ephemeral {
|
if !ephemeral {
|
||||||
saveEvent(message.Id, "system", message.Body, message.GetMessageType(), nil, message.Timestamp)
|
saveEvent(message.ID, "system", message.Body, message.GetMessageType(), nil, message.Timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendSystemAction will send a system action string as an action event to all clients.
|
||||||
func SendSystemAction(text string, ephemeral bool) error {
|
func SendSystemAction(text string, ephemeral bool) error {
|
||||||
message := events.ActionEvent{
|
message := events.ActionEvent{
|
||||||
MessageEvent: events.MessageEvent{
|
MessageEvent: events.MessageEvent{
|
||||||
|
@ -90,20 +94,23 @@ func SendSystemAction(text string, ephemeral bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ephemeral {
|
if !ephemeral {
|
||||||
saveEvent(message.Id, "action", message.Body, message.GetMessageType(), nil, message.Timestamp)
|
saveEvent(message.ID, "action", message.Body, message.GetMessageType(), nil, message.Timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendAllWelcomeMessage will send the chat message to all connected clients.
|
||||||
func SendAllWelcomeMessage() {
|
func SendAllWelcomeMessage() {
|
||||||
_server.sendAllWelcomeMessage()
|
_server.sendAllWelcomeMessage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Broadcast will send all connected clients the outbound object provided.
|
||||||
func Broadcast(event events.OutboundEvent) error {
|
func Broadcast(event events.OutboundEvent) error {
|
||||||
return _server.Broadcast(event.GetBroadcastPayload())
|
return _server.Broadcast(event.GetBroadcastPayload())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleClientConnection handles a single inbound websocket connection.
|
||||||
func HandleClientConnection(w http.ResponseWriter, r *http.Request) {
|
func HandleClientConnection(w http.ResponseWriter, r *http.Request) {
|
||||||
_server.HandleClientConnection(w, r)
|
_server.HandleClientConnection(w, r)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,12 +15,13 @@ import (
|
||||||
"github.com/owncast/owncast/geoip"
|
"github.com/owncast/owncast/geoip"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ChatClient struct {
|
// Client represents a single chat client.
|
||||||
|
type Client struct {
|
||||||
id uint
|
id uint
|
||||||
accessToken string
|
accessToken string
|
||||||
conn *websocket.Conn
|
conn *websocket.Conn
|
||||||
User *user.User `json:"user"`
|
User *user.User `json:"user"`
|
||||||
server *ChatServer
|
server *Server
|
||||||
ipAddress string `json:"-"`
|
ipAddress string `json:"-"`
|
||||||
// Buffered channel of outbound messages.
|
// Buffered channel of outbound messages.
|
||||||
send chan []byte
|
send chan []byte
|
||||||
|
@ -35,7 +36,7 @@ type ChatClient struct {
|
||||||
|
|
||||||
type chatClientEvent struct {
|
type chatClientEvent struct {
|
||||||
data []byte
|
data []byte
|
||||||
client *ChatClient
|
client *Client
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -64,7 +65,7 @@ var (
|
||||||
space = []byte{' '}
|
space = []byte{' '}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *ChatClient) sendConnectedClientInfo() {
|
func (c *Client) sendConnectedClientInfo() {
|
||||||
payload := events.EventPayload{
|
payload := events.EventPayload{
|
||||||
"type": events.ConnectedUserInfo,
|
"type": events.ConnectedUserInfo,
|
||||||
"user": c.User,
|
"user": c.User,
|
||||||
|
@ -73,7 +74,7 @@ func (c *ChatClient) sendConnectedClientInfo() {
|
||||||
c.sendPayload(payload)
|
c.sendPayload(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ChatClient) readPump() {
|
func (c *Client) readPump() {
|
||||||
// Allow 3 messages every two seconds.
|
// Allow 3 messages every two seconds.
|
||||||
limit := rate.Every(2 * time.Second / 3)
|
limit := rate.Every(2 * time.Second / 3)
|
||||||
c.rateLimiter = rate.NewLimiter(limit, 1)
|
c.rateLimiter = rate.NewLimiter(limit, 1)
|
||||||
|
@ -122,11 +123,11 @@ func (c *ChatClient) readPump() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ChatClient) writePump() {
|
func (c *Client) writePump() {
|
||||||
ticker := time.NewTicker(pingPeriod)
|
ticker := time.NewTicker(pingPeriod)
|
||||||
defer func() {
|
defer func() {
|
||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
c.conn.Close()
|
_ = c.conn.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
@ -167,14 +168,14 @@ func (c *ChatClient) writePump() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ChatClient) handleEvent(data []byte) {
|
func (c *Client) handleEvent(data []byte) {
|
||||||
c.server.inbound <- chatClientEvent{data: data, client: c}
|
c.server.inbound <- chatClientEvent{data: data, client: c}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ChatClient) close() {
|
func (c *Client) close() {
|
||||||
log.Traceln("client closed:", c.User.DisplayName, c.id, c.ipAddress)
|
log.Traceln("client closed:", c.User.DisplayName, c.id, c.ipAddress)
|
||||||
|
|
||||||
c.conn.Close()
|
_ = c.conn.Close()
|
||||||
c.server.unregister <- c.id
|
c.server.unregister <- c.id
|
||||||
if c.send != nil {
|
if c.send != nil {
|
||||||
close(c.send)
|
close(c.send)
|
||||||
|
@ -182,18 +183,18 @@ func (c *ChatClient) close() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ChatClient) passesRateLimit() bool {
|
func (c *Client) passesRateLimit() bool {
|
||||||
return c.rateLimiter.Allow() && !c.inTimeout
|
return c.rateLimiter.Allow() && !c.inTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ChatClient) startChatRejectionTimeout() {
|
func (c *Client) startChatRejectionTimeout() {
|
||||||
if c.timeoutTimer != nil {
|
if c.timeoutTimer != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.inTimeout = true
|
c.inTimeout = true
|
||||||
c.timeoutTimer = time.NewTimer(10 * time.Second)
|
c.timeoutTimer = time.NewTimer(10 * time.Second)
|
||||||
go func(c *ChatClient) {
|
go func(c *Client) {
|
||||||
for range c.timeoutTimer.C {
|
for range c.timeoutTimer.C {
|
||||||
c.inTimeout = false
|
c.inTimeout = false
|
||||||
c.timeoutTimer = nil
|
c.timeoutTimer = nil
|
||||||
|
@ -203,7 +204,7 @@ func (c *ChatClient) startChatRejectionTimeout() {
|
||||||
c.sendAction("You are temporarily blocked from sending chat messages due to perceived flooding.")
|
c.sendAction("You are temporarily blocked from sending chat messages due to perceived flooding.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ChatClient) sendPayload(payload events.EventPayload) {
|
func (c *Client) sendPayload(payload events.EventPayload) {
|
||||||
var data []byte
|
var data []byte
|
||||||
data, err := json.Marshal(payload)
|
data, err := json.Marshal(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -216,7 +217,7 @@ func (c *ChatClient) sendPayload(payload events.EventPayload) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ChatClient) sendAction(message string) {
|
func (c *Client) sendAction(message string) {
|
||||||
clientMessage := events.ActionEvent{
|
clientMessage := events.ActionEvent{
|
||||||
MessageEvent: events.MessageEvent{
|
MessageEvent: events.MessageEvent{
|
||||||
Body: message,
|
Body: message,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// nolint:goimports
|
||||||
// +build !freebsd
|
// +build !freebsd
|
||||||
|
|
||||||
package chat
|
package chat
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *ChatServer) userNameChanged(eventData chatClientEvent) {
|
func (s *Server) userNameChanged(eventData chatClientEvent) {
|
||||||
var receivedEvent events.NameChangeEvent
|
var receivedEvent events.NameChangeEvent
|
||||||
if err := json.Unmarshal(eventData.data, &receivedEvent); err != nil {
|
if err := json.Unmarshal(eventData.data, &receivedEvent); err != nil {
|
||||||
log.Errorln("error unmarshalling to NameChangeEvent", err)
|
log.Errorln("error unmarshalling to NameChangeEvent", err)
|
||||||
|
@ -43,7 +43,7 @@ func (s *ChatServer) userNameChanged(eventData chatClientEvent) {
|
||||||
oldName := savedUser.DisplayName
|
oldName := savedUser.DisplayName
|
||||||
|
|
||||||
// Save the new name
|
// Save the new name
|
||||||
user.ChangeUsername(eventData.client.User.Id, receivedEvent.NewName)
|
user.ChangeUsername(eventData.client.User.ID, receivedEvent.NewName)
|
||||||
|
|
||||||
// Update the connected clients associated user with the new name
|
// Update the connected clients associated user with the new name
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
@ -69,7 +69,7 @@ func (s *ChatServer) userNameChanged(eventData chatClientEvent) {
|
||||||
webhooks.SendChatEventUsernameChanged(receivedEvent)
|
webhooks.SendChatEventUsernameChanged(receivedEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ChatServer) userMessageSent(eventData chatClientEvent) {
|
func (s *Server) userMessageSent(eventData chatClientEvent) {
|
||||||
var event events.UserMessageEvent
|
var event events.UserMessageEvent
|
||||||
if err := json.Unmarshal(eventData.data, &event); err != nil {
|
if err := json.Unmarshal(eventData.data, &event); err != nil {
|
||||||
log.Errorln("error unmarshalling to UserMessageEvent", err)
|
log.Errorln("error unmarshalling to UserMessageEvent", err)
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
package events
|
package events
|
||||||
|
|
||||||
|
// ActionEvent represents an action that took place, not a chat message.
|
||||||
type ActionEvent struct {
|
type ActionEvent struct {
|
||||||
Event
|
Event
|
||||||
MessageEvent
|
MessageEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActionEvent will return the object to send to all chat users.
|
// GetBroadcastPayload will return the object to send to all chat users.
|
||||||
func (e *ActionEvent) GetBroadcastPayload() EventPayload {
|
func (e *ActionEvent) GetBroadcastPayload() EventPayload {
|
||||||
return EventPayload{
|
return EventPayload{
|
||||||
"id": e.Id,
|
"id": e.ID,
|
||||||
"timestamp": e.Timestamp,
|
"timestamp": e.Timestamp,
|
||||||
"body": e.Body,
|
"body": e.Body,
|
||||||
"type": e.GetMessageType(),
|
"type": e.GetMessageType(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMessageType will return the type of message.
|
||||||
func (e *ActionEvent) GetMessageType() EventType {
|
func (e *ActionEvent) GetMessageType() EventType {
|
||||||
return ChatActionSent
|
return ChatActionSent
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
// EventPayload is a generic key/value map for sending out to chat clients.
|
// EventPayload is a generic key/value map for sending out to chat clients.
|
||||||
type EventPayload map[string]interface{}
|
type EventPayload map[string]interface{}
|
||||||
|
|
||||||
|
// OutboundEvent represents an event that is sent out to all listeners of the chat server.
|
||||||
type OutboundEvent interface {
|
type OutboundEvent interface {
|
||||||
GetBroadcastPayload() EventPayload
|
GetBroadcastPayload() EventPayload
|
||||||
GetMessageType() EventType
|
GetMessageType() EventType
|
||||||
|
@ -28,10 +29,11 @@ type OutboundEvent interface {
|
||||||
// Event is any kind of event. A type is required to be specified.
|
// Event is any kind of event. A type is required to be specified.
|
||||||
type Event struct {
|
type Event struct {
|
||||||
Type EventType `json:"type,omitempty"`
|
Type EventType `json:"type,omitempty"`
|
||||||
Id string `json:"id"`
|
ID string `json:"id"`
|
||||||
Timestamp time.Time `json:"timestamp"`
|
Timestamp time.Time `json:"timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserEvent is an event with an associated user.
|
||||||
type UserEvent struct {
|
type UserEvent struct {
|
||||||
User *user.User `json:"user"`
|
User *user.User `json:"user"`
|
||||||
HiddenAt *time.Time `json:"hiddenAt,omitempty"`
|
HiddenAt *time.Time `json:"hiddenAt,omitempty"`
|
||||||
|
@ -44,6 +46,7 @@ type MessageEvent struct {
|
||||||
RawBody string `json:"-"`
|
RawBody string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SystemActionEvent is an event that represents an action that took place, not a chat message.
|
||||||
type SystemActionEvent struct {
|
type SystemActionEvent struct {
|
||||||
Event
|
Event
|
||||||
MessageEvent
|
MessageEvent
|
||||||
|
@ -51,13 +54,13 @@ type SystemActionEvent struct {
|
||||||
|
|
||||||
// SetDefaults will set default properties of all inbound events.
|
// SetDefaults will set default properties of all inbound events.
|
||||||
func (e *Event) SetDefaults() {
|
func (e *Event) SetDefaults() {
|
||||||
e.Id = shortid.MustGenerate()
|
e.ID = shortid.MustGenerate()
|
||||||
e.Timestamp = time.Now()
|
e.Timestamp = time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDefaults will set default properties of all inbound events.
|
// SetDefaults will set default properties of all inbound events.
|
||||||
func (e *UserMessageEvent) SetDefaults() {
|
func (e *UserMessageEvent) SetDefaults() {
|
||||||
e.Id = shortid.MustGenerate()
|
e.ID = shortid.MustGenerate()
|
||||||
e.Timestamp = time.Now()
|
e.Timestamp = time.Now()
|
||||||
e.RenderAndSanitizeMessageBody()
|
e.RenderAndSanitizeMessageBody()
|
||||||
}
|
}
|
||||||
|
@ -92,6 +95,7 @@ func RenderAndSanitize(raw string) string {
|
||||||
return strings.TrimSpace(safe)
|
return strings.TrimSpace(safe)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RenderMarkdown will return HTML rendered from the string body of a chat message.
|
||||||
func RenderMarkdown(raw string) string {
|
func RenderMarkdown(raw string) string {
|
||||||
markdown := goldmark.New(
|
markdown := goldmark.New(
|
||||||
goldmark.WithRendererOptions(
|
goldmark.WithRendererOptions(
|
||||||
|
|
|
@ -27,8 +27,11 @@ const (
|
||||||
// ConnectedUserInfo is a private event to a user letting them know their user details.
|
// ConnectedUserInfo is a private event to a user letting them know their user details.
|
||||||
ConnectedUserInfo EventType = "CONNECTED_USER_INFO"
|
ConnectedUserInfo EventType = "CONNECTED_USER_INFO"
|
||||||
// ChatActionSent is a generic chat action that can be used for anything that doesn't need specific handling or formatting.
|
// ChatActionSent is a generic chat action that can be used for anything that doesn't need specific handling or formatting.
|
||||||
ChatActionSent EventType = "CHAT_ACTION"
|
ChatActionSent EventType = "CHAT_ACTION"
|
||||||
ErrorNeedsRegistration EventType = "ERROR_NEEDS_REGISTRATION"
|
// ErrorNeedsRegistration is an error returned when the client needs to perform registration.
|
||||||
|
ErrorNeedsRegistration EventType = "ERROR_NEEDS_REGISTRATION"
|
||||||
|
// ErrorMaxConnectionsExceeded is an error returned when the server determined it should not handle more connections.
|
||||||
ErrorMaxConnectionsExceeded EventType = "ERROR_MAX_CONNECTIONS_EXCEEDED"
|
ErrorMaxConnectionsExceeded EventType = "ERROR_MAX_CONNECTIONS_EXCEEDED"
|
||||||
ErrorUserDisabled EventType = "ERROR_USER_DISABLED"
|
// ErrorUserDisabled is an error returned when the connecting user has been previously banned/disabled.
|
||||||
|
ErrorUserDisabled EventType = "ERROR_USER_DISABLED"
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,7 +7,7 @@ type NameChangeEvent struct {
|
||||||
NewName string `json:"newName"`
|
NewName string `json:"newName"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NameChangeEventBroadcast is fired when a user changes their chat display name.
|
// NameChangeBroadcast represents a user changing their chat display name.
|
||||||
type NameChangeBroadcast struct {
|
type NameChangeBroadcast struct {
|
||||||
Event
|
Event
|
||||||
UserEvent
|
UserEvent
|
||||||
|
@ -17,7 +17,7 @@ type NameChangeBroadcast struct {
|
||||||
// GetBroadcastPayload will return the object to send to all chat users.
|
// GetBroadcastPayload will return the object to send to all chat users.
|
||||||
func (e *NameChangeBroadcast) GetBroadcastPayload() EventPayload {
|
func (e *NameChangeBroadcast) GetBroadcastPayload() EventPayload {
|
||||||
return EventPayload{
|
return EventPayload{
|
||||||
"id": e.Id,
|
"id": e.ID,
|
||||||
"timestamp": e.Timestamp,
|
"timestamp": e.Timestamp,
|
||||||
"user": e.User,
|
"user": e.User,
|
||||||
"oldName": e.Oldname,
|
"oldName": e.Oldname,
|
||||||
|
|
|
@ -8,10 +8,10 @@ type SystemMessageEvent struct {
|
||||||
MessageEvent
|
MessageEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
// SystemMessageEvent will return the object to send to all chat users.
|
// GetBroadcastPayload will return the object to send to all chat users.
|
||||||
func (e *SystemMessageEvent) GetBroadcastPayload() EventPayload {
|
func (e *SystemMessageEvent) GetBroadcastPayload() EventPayload {
|
||||||
return EventPayload{
|
return EventPayload{
|
||||||
"id": e.Id,
|
"id": e.ID,
|
||||||
"timestamp": e.Timestamp,
|
"timestamp": e.Timestamp,
|
||||||
"body": e.Body,
|
"body": e.Body,
|
||||||
"type": SystemMessageSent,
|
"type": SystemMessageSent,
|
||||||
|
@ -21,6 +21,7 @@ func (e *SystemMessageEvent) GetBroadcastPayload() EventPayload {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMessageType will return the event type for this message.
|
||||||
func (e *SystemMessageEvent) GetMessageType() EventType {
|
func (e *SystemMessageEvent) GetMessageType() EventType {
|
||||||
return SystemMessageSent
|
return SystemMessageSent
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ type UserDisabledEvent struct {
|
||||||
func (e *UserDisabledEvent) GetBroadcastPayload() EventPayload {
|
func (e *UserDisabledEvent) GetBroadcastPayload() EventPayload {
|
||||||
return EventPayload{
|
return EventPayload{
|
||||||
"type": ErrorUserDisabled,
|
"type": ErrorUserDisabled,
|
||||||
"id": e.Id,
|
"id": e.ID,
|
||||||
"timestamp": e.Timestamp,
|
"timestamp": e.Timestamp,
|
||||||
"user": e.User,
|
"user": e.User,
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ type UserJoinedEvent struct {
|
||||||
func (e *UserJoinedEvent) GetBroadcastPayload() EventPayload {
|
func (e *UserJoinedEvent) GetBroadcastPayload() EventPayload {
|
||||||
return EventPayload{
|
return EventPayload{
|
||||||
"type": UserJoined,
|
"type": UserJoined,
|
||||||
"id": e.Id,
|
"id": e.ID,
|
||||||
"timestamp": e.Timestamp,
|
"timestamp": e.Timestamp,
|
||||||
"user": e.User,
|
"user": e.User,
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ type UserMessageEvent struct {
|
||||||
// GetBroadcastPayload will return the object to send to all chat users.
|
// GetBroadcastPayload will return the object to send to all chat users.
|
||||||
func (e *UserMessageEvent) GetBroadcastPayload() EventPayload {
|
func (e *UserMessageEvent) GetBroadcastPayload() EventPayload {
|
||||||
return EventPayload{
|
return EventPayload{
|
||||||
"id": e.Id,
|
"id": e.ID,
|
||||||
"timestamp": e.Timestamp,
|
"timestamp": e.Timestamp,
|
||||||
"body": e.Body,
|
"body": e.Body,
|
||||||
"user": e.User,
|
"user": e.User,
|
||||||
|
@ -19,6 +19,7 @@ func (e *UserMessageEvent) GetBroadcastPayload() EventPayload {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMessageType will return the event type for this message.
|
||||||
func (e *UserMessageEvent) GetMessageType() EventType {
|
func (e *UserMessageEvent) GetMessageType() EventType {
|
||||||
return MessageSent
|
return MessageSent
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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 {
|
if err := saveMessageVisibility(messageIDs, visibility); err != nil {
|
||||||
|
@ -17,7 +18,7 @@ func SetMessagesVisibility(messageIDs []string, visibility bool) error {
|
||||||
// Note: Our client expects a single message at a time, so we can't just
|
// Note: Our client expects a single message at a time, so we can't just
|
||||||
// send an array of messages in a single update.
|
// send an array of messages in a single update.
|
||||||
for _, id := range messageIDs {
|
for _, id := range messageIDs {
|
||||||
message, err := getMessageById(id)
|
message, err := getMessageByID(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
"github.com/owncast/owncast/core/chat/events"
|
"github.com/owncast/owncast/core/chat/events"
|
||||||
"github.com/owncast/owncast/core/data"
|
"github.com/owncast/owncast/core/data"
|
||||||
"github.com/owncast/owncast/core/user"
|
"github.com/owncast/owncast/core/user"
|
||||||
|
@ -33,11 +32,12 @@ func setupPersistence() {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaveUserMessage will save a single chat event to the messages database.
|
||||||
func SaveUserMessage(event events.UserMessageEvent) {
|
func SaveUserMessage(event events.UserMessageEvent) {
|
||||||
saveEvent(event.Id, event.User.Id, event.Body, event.Type, event.HiddenAt, event.Timestamp)
|
saveEvent(event.ID, event.User.ID, event.Body, event.Type, event.HiddenAt, event.Timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveEvent(id string, userId string, body string, eventType string, hidden *time.Time, timestamp time.Time) {
|
func saveEvent(id string, userID string, body string, eventType string, hidden *time.Time, timestamp time.Time) {
|
||||||
defer func() {
|
defer func() {
|
||||||
_historyCache = nil
|
_historyCache = nil
|
||||||
}()
|
}()
|
||||||
|
@ -61,7 +61,7 @@ func saveEvent(id string, userId string, body string, eventType string, hidden *
|
||||||
|
|
||||||
defer stmt.Close()
|
defer stmt.Close()
|
||||||
|
|
||||||
if _, err = stmt.Exec(id, userId, body, eventType, hidden, timestamp); err != nil {
|
if _, err = stmt.Exec(id, userID, body, eventType, hidden, timestamp); err != nil {
|
||||||
log.Errorln("error saving", eventType, err)
|
log.Errorln("error saving", eventType, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ func getChat(query string) []events.UserMessageEvent {
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var id string
|
var id string
|
||||||
var userId string
|
var userID string
|
||||||
var body string
|
var body string
|
||||||
var messageType models.EventType
|
var messageType models.EventType
|
||||||
var hiddenAt *time.Time
|
var hiddenAt *time.Time
|
||||||
|
@ -96,7 +96,7 @@ func getChat(query string) []events.UserMessageEvent {
|
||||||
var userNameChangedAt *time.Time
|
var userNameChangedAt *time.Time
|
||||||
|
|
||||||
// Convert a database row into a chat event
|
// Convert a database row into a chat event
|
||||||
err = rows.Scan(&id, &userId, &body, &messageType, &hiddenAt, ×tamp, &userDisplayName, &userDisplayColor, &userCreatedAt, &userDisabledAt, &previousUsernames, &userNameChangedAt)
|
err = rows.Scan(&id, &userID, &body, &messageType, &hiddenAt, ×tamp, &userDisplayName, &userDisplayColor, &userCreatedAt, &userDisabledAt, &previousUsernames, &userNameChangedAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("There is a problem converting query to chat objects. Please report this:", query)
|
log.Errorln("There is a problem converting query to chat objects. Please report this:", query)
|
||||||
break
|
break
|
||||||
|
@ -120,7 +120,7 @@ func getChat(query string) []events.UserMessageEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
user := user.User{
|
user := user.User{
|
||||||
Id: userId,
|
ID: userID,
|
||||||
AccessToken: "",
|
AccessToken: "",
|
||||||
DisplayName: *userDisplayName,
|
DisplayName: *userDisplayName,
|
||||||
DisplayColor: *userDisplayColor,
|
DisplayColor: *userDisplayColor,
|
||||||
|
@ -133,7 +133,7 @@ func getChat(query string) []events.UserMessageEvent {
|
||||||
message := events.UserMessageEvent{
|
message := events.UserMessageEvent{
|
||||||
Event: events.Event{
|
Event: events.Event{
|
||||||
Type: messageType,
|
Type: messageType,
|
||||||
Id: id,
|
ID: id,
|
||||||
Timestamp: timestamp,
|
Timestamp: timestamp,
|
||||||
},
|
},
|
||||||
UserEvent: events.UserEvent{
|
UserEvent: events.UserEvent{
|
||||||
|
@ -154,6 +154,7 @@ func getChat(query string) []events.UserMessageEvent {
|
||||||
|
|
||||||
var _historyCache *[]events.UserMessageEvent
|
var _historyCache *[]events.UserMessageEvent
|
||||||
|
|
||||||
|
// GetChatModerationHistory will return all the chat messages suitable for moderation purposes.
|
||||||
func GetChatModerationHistory() []events.UserMessageEvent {
|
func GetChatModerationHistory() []events.UserMessageEvent {
|
||||||
if _historyCache != nil {
|
if _historyCache != nil {
|
||||||
return *_historyCache
|
return *_historyCache
|
||||||
|
@ -168,6 +169,7 @@ func GetChatModerationHistory() []events.UserMessageEvent {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetChatHistory will return all the chat messages suitable for returning as user-facing chat history.
|
||||||
func GetChatHistory() []events.UserMessageEvent {
|
func GetChatHistory() []events.UserMessageEvent {
|
||||||
// Get all visible messages
|
// Get all visible messages
|
||||||
var query = fmt.Sprintf("SELECT messages.id, user_id, body, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at FROM messages, users WHERE messages.user_id = users.id AND hidden_at IS NULL AND disabled_at IS NULL ORDER BY timestamp DESC LIMIT %d", maxBacklogNumber)
|
var query = fmt.Sprintf("SELECT messages.id, user_id, body, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at FROM messages, users WHERE messages.user_id = users.id AND hidden_at IS NULL AND disabled_at IS NULL ORDER BY timestamp DESC LIMIT %d", maxBacklogNumber)
|
||||||
|
@ -181,9 +183,9 @@ func GetChatHistory() []events.UserMessageEvent {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMessageVisibilityForUserId will bulk change the visibility of messages for a user
|
// SetMessageVisibilityForUserID will bulk change the visibility of messages for a user
|
||||||
// and then send out visibility changed events to chat clients.
|
// and then send out visibility changed events to chat clients.
|
||||||
func SetMessageVisibilityForUserId(userID string, visible bool) error {
|
func SetMessageVisibilityForUserID(userID string, visible bool) error {
|
||||||
defer func() {
|
defer func() {
|
||||||
_historyCache = nil
|
_historyCache = nil
|
||||||
}()
|
}()
|
||||||
|
@ -198,7 +200,7 @@ func SetMessageVisibilityForUserId(userID string, visible bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, message := range messages {
|
for _, message := range messages {
|
||||||
ids = append(ids, message.Id)
|
ids = append(ids, message.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tell the clients to hide/show these messages.
|
// Tell the clients to hide/show these messages.
|
||||||
|
@ -250,29 +252,29 @@ func saveMessageVisibility(messageIDs []string, visible bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMessageById(messageID string) (*events.UserMessageEvent, error) {
|
func getMessageByID(messageID string) (*events.UserMessageEvent, error) {
|
||||||
var query = "SELECT * FROM messages WHERE id = ?"
|
var query = "SELECT * FROM messages WHERE id = ?"
|
||||||
row := _datastore.DB.QueryRow(query, messageID)
|
row := _datastore.DB.QueryRow(query, messageID)
|
||||||
|
|
||||||
var id string
|
var id string
|
||||||
var userId string
|
var userID string
|
||||||
var body string
|
var body string
|
||||||
var eventType models.EventType
|
var eventType models.EventType
|
||||||
var hiddenAt *time.Time
|
var hiddenAt *time.Time
|
||||||
var timestamp time.Time
|
var timestamp time.Time
|
||||||
|
|
||||||
err := row.Scan(&id, &userId, &body, &eventType, &hiddenAt, ×tamp)
|
err := row.Scan(&id, &userID, &body, &eventType, &hiddenAt, ×tamp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
user := user.GetUserById(userId)
|
user := user.GetUserByID(userID)
|
||||||
|
|
||||||
return &events.UserMessageEvent{
|
return &events.UserMessageEvent{
|
||||||
events.Event{
|
events.Event{
|
||||||
Type: eventType,
|
Type: eventType,
|
||||||
Id: id,
|
ID: id,
|
||||||
Timestamp: timestamp,
|
Timestamp: timestamp,
|
||||||
},
|
},
|
||||||
events.UserEvent{
|
events.UserEvent{
|
||||||
|
|
|
@ -18,12 +18,13 @@ import (
|
||||||
"github.com/owncast/owncast/utils"
|
"github.com/owncast/owncast/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _server *ChatServer
|
var _server *Server
|
||||||
|
|
||||||
type ChatServer struct {
|
// Server represents an instance of the chat server.
|
||||||
|
type Server struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
seq uint
|
seq uint
|
||||||
clients map[uint]*ChatClient
|
clients map[uint]*Client
|
||||||
maxSocketConnectionLimit int64
|
maxSocketConnectionLimit int64
|
||||||
|
|
||||||
// send outbound message payload to all clients
|
// send outbound message payload to all clients
|
||||||
|
@ -36,12 +37,13 @@ type ChatServer struct {
|
||||||
unregister chan uint // the ChatClient id
|
unregister chan uint // the ChatClient id
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewChat() *ChatServer {
|
// NewChat will return a new instance of the chat server.
|
||||||
|
func NewChat() *Server {
|
||||||
maximumConcurrentConnectionLimit := getMaximumConcurrentConnectionLimit()
|
maximumConcurrentConnectionLimit := getMaximumConcurrentConnectionLimit()
|
||||||
setSystemConcurrentConnectionLimit(maximumConcurrentConnectionLimit)
|
setSystemConcurrentConnectionLimit(maximumConcurrentConnectionLimit)
|
||||||
|
|
||||||
server := &ChatServer{
|
server := &Server{
|
||||||
clients: map[uint]*ChatClient{},
|
clients: map[uint]*Client{},
|
||||||
outbound: make(chan []byte),
|
outbound: make(chan []byte),
|
||||||
inbound: make(chan chatClientEvent),
|
inbound: make(chan chatClientEvent),
|
||||||
unregister: make(chan uint),
|
unregister: make(chan uint),
|
||||||
|
@ -51,13 +53,14 @@ func NewChat() *ChatServer {
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ChatServer) Run() {
|
// Run will start the chat server.
|
||||||
|
func (s *Server) Run() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case clientId := <-s.unregister:
|
case clientID := <-s.unregister:
|
||||||
if _, ok := s.clients[clientId]; ok {
|
if _, ok := s.clients[clientID]; ok {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
delete(s.clients, clientId)
|
delete(s.clients, clientID)
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,8 +71,8 @@ func (s *ChatServer) Run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Addclient registers new connection as a User.
|
// Addclient registers new connection as a User.
|
||||||
func (s *ChatServer) Addclient(conn *websocket.Conn, user *user.User, accessToken string, userAgent string, ipAddress string) *ChatClient {
|
func (s *Server) Addclient(conn *websocket.Conn, user *user.User, accessToken string, userAgent string, ipAddress string) *Client {
|
||||||
client := &ChatClient{
|
client := &Client{
|
||||||
server: s,
|
server: s,
|
||||||
conn: conn,
|
conn: conn,
|
||||||
User: user,
|
User: user,
|
||||||
|
@ -101,14 +104,14 @@ func (s *ChatServer) Addclient(conn *websocket.Conn, user *user.User, accessToke
|
||||||
}
|
}
|
||||||
|
|
||||||
// Asynchronously, optionally, fetch GeoIP data.
|
// Asynchronously, optionally, fetch GeoIP data.
|
||||||
go func(client *ChatClient) {
|
go func(client *Client) {
|
||||||
client.Geo = geoip.GetGeoFromIP(ipAddress)
|
client.Geo = geoip.GetGeoFromIP(ipAddress)
|
||||||
}(client)
|
}(client)
|
||||||
|
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ChatServer) sendUserJoinedMessage(c *ChatClient) {
|
func (s *Server) sendUserJoinedMessage(c *Client) {
|
||||||
userJoinedEvent := events.UserJoinedEvent{}
|
userJoinedEvent := events.UserJoinedEvent{}
|
||||||
userJoinedEvent.SetDefaults()
|
userJoinedEvent.SetDefaults()
|
||||||
userJoinedEvent.User = c.User
|
userJoinedEvent.User = c.User
|
||||||
|
@ -121,7 +124,8 @@ func (s *ChatServer) sendUserJoinedMessage(c *ChatClient) {
|
||||||
webhooks.SendChatEventUserJoined(userJoinedEvent)
|
webhooks.SendChatEventUserJoined(userJoinedEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ChatServer) ClientClosed(c *ChatClient) {
|
// ClientClosed is fired when a client disconnects or connection is dropped.
|
||||||
|
func (s *Server) ClientClosed(c *Client) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
c.close()
|
c.close()
|
||||||
|
@ -132,7 +136,8 @@ func (s *ChatServer) ClientClosed(c *ChatClient) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ChatServer) HandleClientConnection(w http.ResponseWriter, r *http.Request) {
|
// HandleClientConnection is fired when a single client connects to the websocket.
|
||||||
|
func (s *Server) HandleClientConnection(w http.ResponseWriter, r *http.Request) {
|
||||||
if data.GetChatDisabled() {
|
if data.GetChatDisabled() {
|
||||||
_, _ = w.Write([]byte(events.ChatDisabled))
|
_, _ = w.Write([]byte(events.ChatDisabled))
|
||||||
return
|
return
|
||||||
|
@ -155,7 +160,7 @@ func (s *ChatServer) HandleClientConnection(w http.ResponseWriter, r *http.Reque
|
||||||
if accessToken == "" {
|
if accessToken == "" {
|
||||||
log.Errorln("Access token is required")
|
log.Errorln("Access token is required")
|
||||||
// Return HTTP status code
|
// Return HTTP status code
|
||||||
conn.Close()
|
_ = conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,17 +171,17 @@ func (s *ChatServer) HandleClientConnection(w http.ResponseWriter, r *http.Reque
|
||||||
"type": events.ErrorNeedsRegistration,
|
"type": events.ErrorNeedsRegistration,
|
||||||
})
|
})
|
||||||
// Send error that registration is required
|
// Send error that registration is required
|
||||||
conn.Close()
|
_ = conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// User is disabled therefore we should disconnect.
|
// User is disabled therefore we should disconnect.
|
||||||
if user.DisabledAt != nil {
|
if user.DisabledAt != nil {
|
||||||
log.Traceln("Disabled user", user.Id, user.DisplayName, "rejected")
|
log.Traceln("Disabled user", user.ID, user.DisplayName, "rejected")
|
||||||
_ = conn.WriteJSON(events.EventPayload{
|
_ = conn.WriteJSON(events.EventPayload{
|
||||||
"type": events.ErrorUserDisabled,
|
"type": events.ErrorUserDisabled,
|
||||||
})
|
})
|
||||||
conn.Close()
|
_ = conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,7 +192,7 @@ func (s *ChatServer) HandleClientConnection(w http.ResponseWriter, r *http.Reque
|
||||||
}
|
}
|
||||||
|
|
||||||
// Broadcast sends message to all connected clients.
|
// Broadcast sends message to all connected clients.
|
||||||
func (s *ChatServer) Broadcast(payload events.EventPayload) error {
|
func (s *Server) Broadcast(payload events.EventPayload) error {
|
||||||
data, err := json.Marshal(payload)
|
data, err := json.Marshal(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -212,7 +217,8 @@ func (s *ChatServer) Broadcast(payload events.EventPayload) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ChatServer) Send(payload events.EventPayload, client *ChatClient) {
|
// Send will send a single payload to a single connected client.
|
||||||
|
func (s *Server) Send(payload events.EventPayload, client *Client) {
|
||||||
data, err := json.Marshal(payload)
|
data, err := json.Marshal(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
|
@ -223,7 +229,7 @@ func (s *ChatServer) Send(payload events.EventPayload, client *ChatClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisconnectUser will forcefully disconnect all clients belonging to a user by ID.
|
// DisconnectUser will forcefully disconnect all clients belonging to a user by ID.
|
||||||
func (s *ChatServer) DisconnectUser(userID string) {
|
func (s *Server) DisconnectUser(userID string) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
clients, err := GetClientsForUser(userID)
|
clients, err := GetClientsForUser(userID)
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
@ -234,9 +240,9 @@ func (s *ChatServer) DisconnectUser(userID string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
log.Traceln("Disconnecting client", client.User.Id, "owned by", client.User.DisplayName)
|
log.Traceln("Disconnecting client", client.User.ID, "owned by", client.User.DisplayName)
|
||||||
|
|
||||||
go func(client *ChatClient) {
|
go func(client *Client) {
|
||||||
event := events.UserDisabledEvent{}
|
event := events.UserDisabledEvent{}
|
||||||
event.SetDefaults()
|
event.SetDefaults()
|
||||||
|
|
||||||
|
@ -257,7 +263,7 @@ func (s *ChatServer) DisconnectUser(userID string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ChatServer) eventReceived(event chatClientEvent) {
|
func (s *Server) eventReceived(event chatClientEvent) {
|
||||||
var typecheck map[string]interface{}
|
var typecheck map[string]interface{}
|
||||||
if err := json.Unmarshal(event.data, &typecheck); err != nil {
|
if err := json.Unmarshal(event.data, &typecheck); err != nil {
|
||||||
log.Debugln(err)
|
log.Debugln(err)
|
||||||
|
@ -277,7 +283,7 @@ func (s *ChatServer) eventReceived(event chatClientEvent) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ChatServer) sendWelcomeMessageToClient(c *ChatClient) {
|
func (s *Server) sendWelcomeMessageToClient(c *Client) {
|
||||||
// Add an artificial delay so people notice this message come in.
|
// Add an artificial delay so people notice this message come in.
|
||||||
time.Sleep(7 * time.Second)
|
time.Sleep(7 * time.Second)
|
||||||
|
|
||||||
|
@ -288,7 +294,7 @@ func (s *ChatServer) sendWelcomeMessageToClient(c *ChatClient) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ChatServer) sendAllWelcomeMessage() {
|
func (s *Server) sendAllWelcomeMessage() {
|
||||||
welcomeMessage := utils.RenderSimpleMarkdown(data.GetServerWelcomeMessage())
|
welcomeMessage := utils.RenderSimpleMarkdown(data.GetServerWelcomeMessage())
|
||||||
|
|
||||||
if welcomeMessage != "" {
|
if welcomeMessage != "" {
|
||||||
|
@ -303,7 +309,7 @@ func (s *ChatServer) sendAllWelcomeMessage() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ChatServer) sendSystemMessageToClient(c *ChatClient, message string) {
|
func (s *Server) sendSystemMessageToClient(c *Client, message string) {
|
||||||
clientMessage := events.SystemMessageEvent{
|
clientMessage := events.SystemMessageEvent{
|
||||||
Event: events.Event{},
|
Event: events.Event{},
|
||||||
MessageEvent: events.MessageEvent{
|
MessageEvent: events.MessageEvent{
|
||||||
|
@ -314,7 +320,7 @@ func (s *ChatServer) sendSystemMessageToClient(c *ChatClient, message string) {
|
||||||
s.Send(clientMessage.GetBroadcastPayload(), c)
|
s.Send(clientMessage.GetBroadcastPayload(), c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ChatServer) sendActionToClient(c *ChatClient, message string) {
|
func (s *Server) sendActionToClient(c *Client, message string) {
|
||||||
clientMessage := events.ActionEvent{
|
clientMessage := events.ActionEvent{
|
||||||
MessageEvent: events.MessageEvent{
|
MessageEvent: events.MessageEvent{
|
||||||
Body: message,
|
Body: message,
|
||||||
|
|
|
@ -112,7 +112,7 @@ func transitionToOfflineVideoStreamContent() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the preview Gif
|
// Delete the preview Gif
|
||||||
os.Remove(path.Join(config.WebRoot, "preview.gif"))
|
_ = os.Remove(path.Join(config.WebRoot, "preview.gif"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func resetDirectories() {
|
func resetDirectories() {
|
||||||
|
|
|
@ -516,6 +516,7 @@ func SetVideoCodec(codec string) error {
|
||||||
return _datastore.SetString(videoCodecKey, codec)
|
return _datastore.SetString(videoCodecKey, codec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetVideoCodec returns the codec to use for transcoding video.
|
||||||
func GetVideoCodec() string {
|
func GetVideoCodec() string {
|
||||||
codec, err := _datastore.GetString(videoCodecKey)
|
codec, err := _datastore.GetString(videoCodecKey)
|
||||||
if codec == "" || err != nil {
|
if codec == "" || err != nil {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CreateMessagesTable will create the chat messages table if needed.
|
||||||
func CreateMessagesTable(db *sql.DB) {
|
func CreateMessagesTable(db *sql.DB) {
|
||||||
createTableSQL := `CREATE TABLE IF NOT EXISTS messages (
|
createTableSQL := `CREATE TABLE IF NOT EXISTS messages (
|
||||||
"id" string NOT NULL,
|
"id" string NOT NULL,
|
||||||
|
|
|
@ -64,7 +64,7 @@ func migrateToSchema1(db *sql.DB) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastUsed *time.Time = nil
|
var lastUsed *time.Time
|
||||||
if lastUsedString != nil {
|
if lastUsedString != nil {
|
||||||
lastUsedTime, _ := time.Parse(time.RFC3339, *lastUsedString)
|
lastUsedTime, _ := time.Parse(time.RFC3339, *lastUsedString)
|
||||||
lastUsed = &lastUsedTime
|
lastUsed = &lastUsedTime
|
||||||
|
|
|
@ -144,6 +144,7 @@ func (ds *Datastore) Reset() {
|
||||||
PopulateDefaults()
|
PopulateDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDatastore returns the shared instance of the owncast datastore.
|
||||||
func GetDatastore() *Datastore {
|
func GetDatastore() *Datastore {
|
||||||
return _datastore
|
return _datastore
|
||||||
}
|
}
|
||||||
|
|
|
@ -169,7 +169,7 @@ func GetWebhooks() ([]models.Webhook, error) { //nolint
|
||||||
return webhooks, err
|
return webhooks, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastUsed *time.Time = nil
|
var lastUsed *time.Time
|
||||||
if lastUsedString != nil {
|
if lastUsedString != nil {
|
||||||
lastUsedTime, _ := time.Parse(time.RFC3339, *lastUsedString)
|
lastUsedTime, _ := time.Parse(time.RFC3339, *lastUsedString)
|
||||||
lastUsed = &lastUsedTime
|
lastUsed = &lastUsedTime
|
||||||
|
|
|
@ -60,6 +60,7 @@ func Start(setStreamAsConnected func(*io.PipeReader), setBroadcaster func(models
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleConn is fired when an inbound RTMP connection takes place.
|
||||||
func HandleConn(c *rtmp.Conn, nc net.Conn) {
|
func HandleConn(c *rtmp.Conn, nc net.Conn) {
|
||||||
c.LogTagEvent = func(isRead bool, t flvio.Tag) {
|
c.LogTagEvent = func(isRead bool, t flvio.Tag) {
|
||||||
if t.Type == flvio.TAG_AMF0 {
|
if t.Type == flvio.TAG_AMF0 {
|
||||||
|
@ -70,13 +71,13 @@ func HandleConn(c *rtmp.Conn, nc net.Conn) {
|
||||||
|
|
||||||
if _hasInboundRTMPConnection {
|
if _hasInboundRTMPConnection {
|
||||||
log.Errorln("stream already running; can not overtake an existing stream")
|
log.Errorln("stream already running; can not overtake an existing stream")
|
||||||
nc.Close()
|
_ = nc.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !secretMatch(data.GetStreamKey(), c.URL.Path) {
|
if !secretMatch(data.GetStreamKey(), c.URL.Path) {
|
||||||
log.Errorln("invalid streaming key; rejecting incoming stream")
|
log.Errorln("invalid streaming key; rejecting incoming stream")
|
||||||
nc.Close()
|
_ = nc.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,8 +130,8 @@ func handleDisconnect(conn net.Conn) {
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infoln("Inbound stream disconnected.")
|
log.Infoln("Inbound stream disconnected.")
|
||||||
conn.Close()
|
_ = conn.Close()
|
||||||
_pipe.Close()
|
_ = _pipe.Close()
|
||||||
_hasInboundRTMPConnection = false
|
_hasInboundRTMPConnection = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,8 +61,8 @@ func RemoveChatClient(clientID string) {
|
||||||
l.Unlock()
|
l.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetViewerIdActive sets a client as active and connected.
|
// SetViewerIDActive sets a client as active and connected.
|
||||||
func SetViewerIdActive(id string) {
|
func SetViewerIDActive(id string) {
|
||||||
l.Lock()
|
l.Lock()
|
||||||
defer l.Unlock()
|
defer l.Unlock()
|
||||||
|
|
||||||
|
@ -81,10 +81,10 @@ func pruneViewerCount() {
|
||||||
l.Lock()
|
l.Lock()
|
||||||
defer l.Unlock()
|
defer l.Unlock()
|
||||||
|
|
||||||
for viewerId := range _stats.Viewers {
|
for viewerID := range _stats.Viewers {
|
||||||
viewerLastSeenTime := _stats.Viewers[viewerId]
|
viewerLastSeenTime := _stats.Viewers[viewerID]
|
||||||
if time.Since(viewerLastSeenTime) < _activeViewerPurgeTimeout {
|
if time.Since(viewerLastSeenTime) < _activeViewerPurgeTimeout {
|
||||||
viewers[viewerId] = viewerLastSeenTime
|
viewers[viewerID] = viewerLastSeenTime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ func GetStatus() models.Status {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCurrentBroadcast will return the currently active broadcast.
|
||||||
func GetCurrentBroadcast() *models.CurrentBroadcast {
|
func GetCurrentBroadcast() *models.CurrentBroadcast {
|
||||||
return _currentBroadcast
|
return _currentBroadcast
|
||||||
}
|
}
|
||||||
|
@ -38,6 +39,7 @@ func setBroadcaster(broadcaster models.Broadcaster) {
|
||||||
_broadcaster = &broadcaster
|
_broadcaster = &broadcaster
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBroadcaster will return the details of the currently active broadcaster.
|
||||||
func GetBroadcaster() *models.Broadcaster {
|
func GetBroadcaster() *models.Broadcaster {
|
||||||
return _broadcaster
|
return _broadcaster
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/owncast/owncast/utils"
|
"github.com/owncast/owncast/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// LocalStorage represents an instance of the local storage provider for HLS video.
|
||||||
type LocalStorage struct {
|
type LocalStorage struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ import (
|
||||||
// then keep a reference to it here.
|
// then keep a reference to it here.
|
||||||
var _queuedPlaylistUpdates = make(map[string]string)
|
var _queuedPlaylistUpdates = make(map[string]string)
|
||||||
|
|
||||||
// S3Storage is the s3 implementation of the ChunkStorageProvider.
|
// S3Storage is the s3 implementation of a storage provider.
|
||||||
type S3Storage struct {
|
type S3Storage struct {
|
||||||
sess *session.Session
|
sess *session.Session
|
||||||
host string
|
host string
|
||||||
|
@ -124,7 +124,7 @@ func (s *S3Storage) MasterPlaylistWritten(localFilePath string) {
|
||||||
|
|
||||||
// Save saves the file to the s3 bucket.
|
// Save saves the file to the s3 bucket.
|
||||||
func (s *S3Storage) Save(filePath string, retryCount int) (string, error) {
|
func (s *S3Storage) Save(filePath string, retryCount int) (string, error) {
|
||||||
file, err := os.Open(filePath)
|
file, err := os.Open(filePath) // nolint
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -153,10 +153,10 @@ func (s *S3Storage) Save(filePath string, retryCount int) (string, error) {
|
||||||
if retryCount < 4 {
|
if retryCount < 4 {
|
||||||
log.Traceln("Retrying...")
|
log.Traceln("Retrying...")
|
||||||
return s.Save(filePath, retryCount+1)
|
return s.Save(filePath, retryCount+1)
|
||||||
} else {
|
|
||||||
log.Warnln("Giving up on", filePath, err)
|
|
||||||
return "", fmt.Errorf("Giving up on %s", filePath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Warnln("Giving up on", filePath, err)
|
||||||
|
return "", fmt.Errorf("Giving up on %s", filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.Location, nil
|
return response.Location, nil
|
||||||
|
@ -185,7 +185,7 @@ func (s *S3Storage) connectAWS() *session.Session {
|
||||||
|
|
||||||
// rewriteRemotePlaylist will take a local playlist and rewrite it to have absolute URLs to remote locations.
|
// rewriteRemotePlaylist will take a local playlist and rewrite it to have absolute URLs to remote locations.
|
||||||
func (s *S3Storage) rewriteRemotePlaylist(filePath string) error {
|
func (s *S3Storage) rewriteRemotePlaylist(filePath string) error {
|
||||||
f, err := os.Open(filePath)
|
f, err := os.Open(filePath) // nolint
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,7 +110,7 @@ func SetStreamAsDisconnected() {
|
||||||
log.Warnln(err)
|
log.Warnln(err)
|
||||||
}
|
}
|
||||||
if utils.DoesFileExists(playlistFilePath) {
|
if utils.DoesFileExists(playlistFilePath) {
|
||||||
f, err := os.OpenFile(playlistFilePath, os.O_CREATE|os.O_RDWR, os.ModePerm)
|
f, err := os.OpenFile(playlistFilePath, os.O_CREATE|os.O_RDWR, os.ModePerm) //nolint
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,35 +29,43 @@ var supportedCodecs = map[string]string{
|
||||||
(&NvencCodec{}).Name(): "NVIDIA nvenc",
|
(&NvencCodec{}).Name(): "NVIDIA nvenc",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Libx264Codec represents an instance of the Libx264 Codec.
|
||||||
type Libx264Codec struct {
|
type Libx264Codec struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the codec name.
|
||||||
func (c *Libx264Codec) Name() string {
|
func (c *Libx264Codec) Name() string {
|
||||||
return "libx264"
|
return "libx264"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DisplayName returns the human readable name of the codec.
|
||||||
func (c *Libx264Codec) DisplayName() string {
|
func (c *Libx264Codec) DisplayName() string {
|
||||||
return "x264"
|
return "x264"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GlobalFlags are the global flags used with this codec in the transcoder.
|
||||||
func (c *Libx264Codec) GlobalFlags() string {
|
func (c *Libx264Codec) GlobalFlags() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PixelFormat is the pixel format required for this codec.
|
||||||
func (c *Libx264Codec) PixelFormat() string {
|
func (c *Libx264Codec) PixelFormat() string {
|
||||||
return "yuv420p" //nolint:goconst
|
return "yuv420p" //nolint:goconst
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtraArguments are the extra arguments used with this codec in the transcoder.
|
||||||
func (c *Libx264Codec) ExtraArguments() string {
|
func (c *Libx264Codec) ExtraArguments() string {
|
||||||
return strings.Join([]string{
|
return strings.Join([]string{
|
||||||
"-tune", "zerolatency", // Option used for good for fast encoding and low-latency streaming (always includes iframes in each segment)
|
"-tune", "zerolatency", // Option used for good for fast encoding and low-latency streaming (always includes iframes in each segment)
|
||||||
}, " ")
|
}, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtraFilters are the extra filters required for this codec in the transcoder.
|
||||||
func (c *Libx264Codec) ExtraFilters() string {
|
func (c *Libx264Codec) ExtraFilters() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VariantFlags returns a string representing a single variant processed by this codec.
|
||||||
func (c *Libx264Codec) VariantFlags(v *HLSVariant) string {
|
func (c *Libx264Codec) VariantFlags(v *HLSVariant) string {
|
||||||
bufferSize := int(float64(v.videoBitrate) * 1.2) // How often it checks the bitrate of encoded segments to see if it's too high/low.
|
bufferSize := int(float64(v.videoBitrate) * 1.2) // How often it checks the bitrate of encoded segments to see if it's too high/low.
|
||||||
|
|
||||||
|
@ -68,6 +76,7 @@ func (c *Libx264Codec) VariantFlags(v *HLSVariant) string {
|
||||||
}, " ")
|
}, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPresetForLevel returns the string preset for this codec given an integer level.
|
||||||
func (c *Libx264Codec) GetPresetForLevel(l int) string {
|
func (c *Libx264Codec) GetPresetForLevel(l int) string {
|
||||||
presetMapping := []string{
|
presetMapping := []string{
|
||||||
"ultrafast",
|
"ultrafast",
|
||||||
|
@ -84,39 +93,48 @@ func (c *Libx264Codec) GetPresetForLevel(l int) string {
|
||||||
return presetMapping[l]
|
return presetMapping[l]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OmxCodec represents an instance of the Omx codec.
|
||||||
type OmxCodec struct {
|
type OmxCodec struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the codec name.
|
||||||
func (c *OmxCodec) Name() string {
|
func (c *OmxCodec) Name() string {
|
||||||
return "h264_omx"
|
return "h264_omx"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DisplayName returns the human readable name of the codec.
|
||||||
func (c *OmxCodec) DisplayName() string {
|
func (c *OmxCodec) DisplayName() string {
|
||||||
return "OpenMAX (omx)"
|
return "OpenMAX (omx)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GlobalFlags are the global flags used with this codec in the transcoder.
|
||||||
func (c *OmxCodec) GlobalFlags() string {
|
func (c *OmxCodec) GlobalFlags() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PixelFormat is the pixel format required for this codec.
|
||||||
func (c *OmxCodec) PixelFormat() string {
|
func (c *OmxCodec) PixelFormat() string {
|
||||||
return "yuv420p"
|
return "yuv420p"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtraArguments are the extra arguments used with this codec in the transcoder.
|
||||||
func (c *OmxCodec) ExtraArguments() string {
|
func (c *OmxCodec) ExtraArguments() string {
|
||||||
return strings.Join([]string{
|
return strings.Join([]string{
|
||||||
"-tune", "zerolatency", // Option used for good for fast encoding and low-latency streaming (always includes iframes in each segment)
|
"-tune", "zerolatency", // Option used for good for fast encoding and low-latency streaming (always includes iframes in each segment)
|
||||||
}, " ")
|
}, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtraFilters are the extra filters required for this codec in the transcoder.
|
||||||
func (c *OmxCodec) ExtraFilters() string {
|
func (c *OmxCodec) ExtraFilters() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VariantFlags returns a string representing a single variant processed by this codec.
|
||||||
func (c *OmxCodec) VariantFlags(v *HLSVariant) string {
|
func (c *OmxCodec) VariantFlags(v *HLSVariant) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPresetForLevel returns the string preset for this codec given an integer level.
|
||||||
func (c *OmxCodec) GetPresetForLevel(l int) string {
|
func (c *OmxCodec) GetPresetForLevel(l int) string {
|
||||||
presetMapping := []string{
|
presetMapping := []string{
|
||||||
"ultrafast",
|
"ultrafast",
|
||||||
|
@ -133,17 +151,21 @@ func (c *OmxCodec) GetPresetForLevel(l int) string {
|
||||||
return presetMapping[l]
|
return presetMapping[l]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VaapiCodec represents an instance of the Vaapi codec.
|
||||||
type VaapiCodec struct {
|
type VaapiCodec struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the codec name.
|
||||||
func (c *VaapiCodec) Name() string {
|
func (c *VaapiCodec) Name() string {
|
||||||
return "h264_vaapi"
|
return "h264_vaapi"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DisplayName returns the human readable name of the codec.
|
||||||
func (c *VaapiCodec) DisplayName() string {
|
func (c *VaapiCodec) DisplayName() string {
|
||||||
return "VA-API"
|
return "VA-API"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GlobalFlags are the global flags used with this codec in the transcoder.
|
||||||
func (c *VaapiCodec) GlobalFlags() string {
|
func (c *VaapiCodec) GlobalFlags() string {
|
||||||
flags := []string{
|
flags := []string{
|
||||||
"-vaapi_device", "/dev/dri/renderD128",
|
"-vaapi_device", "/dev/dri/renderD128",
|
||||||
|
@ -152,22 +174,27 @@ func (c *VaapiCodec) GlobalFlags() string {
|
||||||
return strings.Join(flags, " ")
|
return strings.Join(flags, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PixelFormat is the pixel format required for this codec.
|
||||||
func (c *VaapiCodec) PixelFormat() string {
|
func (c *VaapiCodec) PixelFormat() string {
|
||||||
return "vaapi_vld"
|
return "vaapi_vld"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtraFilters are the extra filters required for this codec in the transcoder.
|
||||||
func (c *VaapiCodec) ExtraFilters() string {
|
func (c *VaapiCodec) ExtraFilters() string {
|
||||||
return "format=nv12,hwupload"
|
return "format=nv12,hwupload"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtraArguments are the extra arguments used with this codec in the transcoder.
|
||||||
func (c *VaapiCodec) ExtraArguments() string {
|
func (c *VaapiCodec) ExtraArguments() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VariantFlags returns a string representing a single variant processed by this codec.
|
||||||
func (c *VaapiCodec) VariantFlags(v *HLSVariant) string {
|
func (c *VaapiCodec) VariantFlags(v *HLSVariant) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPresetForLevel returns the string preset for this codec given an integer level.
|
||||||
func (c *VaapiCodec) GetPresetForLevel(l int) string {
|
func (c *VaapiCodec) GetPresetForLevel(l int) string {
|
||||||
presetMapping := []string{
|
presetMapping := []string{
|
||||||
"ultrafast",
|
"ultrafast",
|
||||||
|
@ -184,17 +211,21 @@ func (c *VaapiCodec) GetPresetForLevel(l int) string {
|
||||||
return presetMapping[l]
|
return presetMapping[l]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NvencCodec represents an instance of the Nvenc Codec.
|
||||||
type NvencCodec struct {
|
type NvencCodec struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the codec name.
|
||||||
func (c *NvencCodec) Name() string {
|
func (c *NvencCodec) Name() string {
|
||||||
return "h264_nvenc"
|
return "h264_nvenc"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DisplayName returns the human readable name of the codec.
|
||||||
func (c *NvencCodec) DisplayName() string {
|
func (c *NvencCodec) DisplayName() string {
|
||||||
return "nvidia nvenc"
|
return "nvidia nvenc"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GlobalFlags are the global flags used with this codec in the transcoder.
|
||||||
func (c *NvencCodec) GlobalFlags() string {
|
func (c *NvencCodec) GlobalFlags() string {
|
||||||
flags := []string{
|
flags := []string{
|
||||||
"-hwaccel cuda",
|
"-hwaccel cuda",
|
||||||
|
@ -203,23 +234,28 @@ func (c *NvencCodec) GlobalFlags() string {
|
||||||
return strings.Join(flags, " ")
|
return strings.Join(flags, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PixelFormat is the pixel format required for this codec.
|
||||||
func (c *NvencCodec) PixelFormat() string {
|
func (c *NvencCodec) PixelFormat() string {
|
||||||
return "yuv420p"
|
return "yuv420p"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtraArguments are the extra arguments used with this codec in the transcoder.
|
||||||
func (c *NvencCodec) ExtraArguments() string {
|
func (c *NvencCodec) ExtraArguments() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtraFilters are the extra filters required for this codec in the transcoder.
|
||||||
func (c *NvencCodec) ExtraFilters() string {
|
func (c *NvencCodec) ExtraFilters() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VariantFlags returns a string representing a single variant processed by this codec.
|
||||||
func (c *NvencCodec) VariantFlags(v *HLSVariant) string {
|
func (c *NvencCodec) VariantFlags(v *HLSVariant) string {
|
||||||
tuning := "ll" // low latency
|
tuning := "ll" // low latency
|
||||||
return fmt.Sprintf("-tune:v:%d %s", v.index, tuning)
|
return fmt.Sprintf("-tune:v:%d %s", v.index, tuning)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPresetForLevel returns the string preset for this codec given an integer level.
|
||||||
func (c *NvencCodec) GetPresetForLevel(l int) string {
|
func (c *NvencCodec) GetPresetForLevel(l int) string {
|
||||||
presetMapping := []string{
|
presetMapping := []string{
|
||||||
"p1",
|
"p1",
|
||||||
|
@ -236,37 +272,46 @@ func (c *NvencCodec) GetPresetForLevel(l int) string {
|
||||||
return presetMapping[l]
|
return presetMapping[l]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QuicksyncCodec represents an instance of the Intel Quicksync Codec.
|
||||||
type QuicksyncCodec struct {
|
type QuicksyncCodec struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the codec name.
|
||||||
func (c *QuicksyncCodec) Name() string {
|
func (c *QuicksyncCodec) Name() string {
|
||||||
return "h264_qsv"
|
return "h264_qsv"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DisplayName returns the human readable name of the codec.
|
||||||
func (c *QuicksyncCodec) DisplayName() string {
|
func (c *QuicksyncCodec) DisplayName() string {
|
||||||
return "Intel QuickSync"
|
return "Intel QuickSync"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GlobalFlags are the global flags used with this codec in the transcoder.
|
||||||
func (c *QuicksyncCodec) GlobalFlags() string {
|
func (c *QuicksyncCodec) GlobalFlags() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PixelFormat is the pixel format required for this codec.
|
||||||
func (c *QuicksyncCodec) PixelFormat() string {
|
func (c *QuicksyncCodec) PixelFormat() string {
|
||||||
return "nv12"
|
return "nv12"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtraArguments are the extra arguments used with this codec in the transcoder.
|
||||||
func (c *QuicksyncCodec) ExtraArguments() string {
|
func (c *QuicksyncCodec) ExtraArguments() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtraFilters are the extra filters required for this codec in the transcoder.
|
||||||
func (c *QuicksyncCodec) ExtraFilters() string {
|
func (c *QuicksyncCodec) ExtraFilters() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VariantFlags returns a string representing a single variant processed by this codec.
|
||||||
func (c *QuicksyncCodec) VariantFlags(v *HLSVariant) string {
|
func (c *QuicksyncCodec) VariantFlags(v *HLSVariant) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPresetForLevel returns the string preset for this codec given an integer level.
|
||||||
func (c *QuicksyncCodec) GetPresetForLevel(l int) string {
|
func (c *QuicksyncCodec) GetPresetForLevel(l int) string {
|
||||||
presetMapping := []string{
|
presetMapping := []string{
|
||||||
"ultrafast",
|
"ultrafast",
|
||||||
|
@ -283,36 +328,45 @@ func (c *QuicksyncCodec) GetPresetForLevel(l int) string {
|
||||||
return presetMapping[l]
|
return presetMapping[l]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Video4Linux represents an instance of the V4L Codec.
|
||||||
type Video4Linux struct{}
|
type Video4Linux struct{}
|
||||||
|
|
||||||
|
// Name returns the codec name.
|
||||||
func (c *Video4Linux) Name() string {
|
func (c *Video4Linux) Name() string {
|
||||||
return "h264_v4l2m2m"
|
return "h264_v4l2m2m"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DisplayName returns the human readable name of the codec.
|
||||||
func (c *Video4Linux) DisplayName() string {
|
func (c *Video4Linux) DisplayName() string {
|
||||||
return "Video4Linux"
|
return "Video4Linux"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GlobalFlags are the global flags used with this codec in the transcoder.
|
||||||
func (c *Video4Linux) GlobalFlags() string {
|
func (c *Video4Linux) GlobalFlags() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PixelFormat is the pixel format required for this codec.
|
||||||
func (c *Video4Linux) PixelFormat() string {
|
func (c *Video4Linux) PixelFormat() string {
|
||||||
return "nv21"
|
return "nv21"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtraArguments are the extra arguments used with this codec in the transcoder.
|
||||||
func (c *Video4Linux) ExtraArguments() string {
|
func (c *Video4Linux) ExtraArguments() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtraFilters are the extra filters required for this codec in the transcoder.
|
||||||
func (c *Video4Linux) ExtraFilters() string {
|
func (c *Video4Linux) ExtraFilters() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VariantFlags returns a string representing a single variant processed by this codec.
|
||||||
func (c *Video4Linux) VariantFlags(v *HLSVariant) string {
|
func (c *Video4Linux) VariantFlags(v *HLSVariant) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPresetForLevel returns the string preset for this codec given an integer level.
|
||||||
func (c *Video4Linux) GetPresetForLevel(l int) string {
|
func (c *Video4Linux) GetPresetForLevel(l int) string {
|
||||||
presetMapping := []string{
|
presetMapping := []string{
|
||||||
"ultrafast",
|
"ultrafast",
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
|
|
||||||
var _timer *time.Ticker
|
var _timer *time.Ticker
|
||||||
|
|
||||||
|
// StopThumbnailGenerator will stop the periodic generating of a thumbnail from video.
|
||||||
func StopThumbnailGenerator() {
|
func StopThumbnailGenerator() {
|
||||||
if _timer != nil {
|
if _timer != nil {
|
||||||
_timer.Stop()
|
_timer.Stop()
|
||||||
|
@ -98,11 +99,11 @@ func fireThumbnailGenerator(segmentPath string, variantIndex int) error {
|
||||||
ffmpegCmd := strings.Join(thumbnailCmdFlags, " ")
|
ffmpegCmd := strings.Join(thumbnailCmdFlags, " ")
|
||||||
if _, err := exec.Command("sh", "-c", ffmpegCmd).Output(); err != nil {
|
if _, err := exec.Command("sh", "-c", ffmpegCmd).Output(); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
}
|
||||||
// rename temp file
|
|
||||||
if err := os.Rename(outputFileTemp, outputFile); err != nil {
|
// rename temp file
|
||||||
log.Errorln(err)
|
if err := os.Rename(outputFileTemp, outputFile); err != nil {
|
||||||
}
|
log.Errorln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If YP support is enabled also create an animated GIF preview
|
// If YP support is enabled also create an animated GIF preview
|
||||||
|
|
|
@ -76,6 +76,7 @@ func (v *VideoSize) getString() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop will stop the transcoder and kill all processing.
|
||||||
func (t *Transcoder) Stop() {
|
func (t *Transcoder) Stop() {
|
||||||
log.Traceln("Transcoder STOP requested.")
|
log.Traceln("Transcoder STOP requested.")
|
||||||
err := _commandExec.Process.Kill()
|
err := _commandExec.Process.Kill()
|
||||||
|
@ -410,15 +411,17 @@ func (t *Transcoder) SetAppendToStream(append bool) {
|
||||||
t.appendToStream = append
|
t.appendToStream = append
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetIdentifer enables appending a unique identifier to segment file name.
|
// SetIdentifier enables appending a unique identifier to segment file name.
|
||||||
func (t *Transcoder) SetIdentifier(output string) {
|
func (t *Transcoder) SetIdentifier(output string) {
|
||||||
t.segmentIdentifier = output
|
t.segmentIdentifier = output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetInternalHTTPPort will set the port to be used for internal communication.
|
||||||
func (t *Transcoder) SetInternalHTTPPort(port string) {
|
func (t *Transcoder) SetInternalHTTPPort(port string) {
|
||||||
t.internalListenerPort = port
|
t.internalListenerPort = port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCodec will set the codec to be used for the transocder.
|
||||||
func (t *Transcoder) SetCodec(codecName string) {
|
func (t *Transcoder) SetCodec(codecName string) {
|
||||||
t.codec = getCodec(codecName)
|
t.codec = getCodec(codecName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,24 +99,24 @@ func createVariantDirectories() {
|
||||||
|
|
||||||
if len(data.GetStreamOutputVariants()) != 0 {
|
if len(data.GetStreamOutputVariants()) != 0 {
|
||||||
for index := range data.GetStreamOutputVariants() {
|
for index := range data.GetStreamOutputVariants() {
|
||||||
if err := os.MkdirAll(path.Join(config.PrivateHLSStoragePath, strconv.Itoa(index)), 0777); err != nil {
|
if err := os.MkdirAll(path.Join(config.PrivateHLSStoragePath, strconv.Itoa(index)), 0750); err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
dir := path.Join(config.PublicHLSStoragePath, strconv.Itoa(index))
|
dir := path.Join(config.PublicHLSStoragePath, strconv.Itoa(index))
|
||||||
log.Traceln("Creating", dir)
|
log.Traceln("Creating", dir)
|
||||||
if err := os.MkdirAll(dir, 0777); err != nil {
|
if err := os.MkdirAll(dir, 0750); err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dir := path.Join(config.PrivateHLSStoragePath, strconv.Itoa(0))
|
dir := path.Join(config.PrivateHLSStoragePath, strconv.Itoa(0))
|
||||||
log.Traceln("Creating", dir)
|
log.Traceln("Creating", dir)
|
||||||
if err := os.MkdirAll(dir, 0777); err != nil {
|
if err := os.MkdirAll(dir, 0750); err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
dir = path.Join(config.PublicHLSStoragePath, strconv.Itoa(0))
|
dir = path.Join(config.PublicHLSStoragePath, strconv.Itoa(0))
|
||||||
log.Traceln("Creating", dir)
|
log.Traceln("Creating", dir)
|
||||||
if err := os.MkdirAll(dir, 0777); err != nil {
|
if err := os.MkdirAll(dir, 0750); err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
// ExternalAPIUser represents a single 3rd party integration that uses an access token.
|
// ExternalAPIUser represents a single 3rd party integration that uses an access token.
|
||||||
// This struct mostly matches the User struct so they can be used interchangeably.
|
// This struct mostly matches the User struct so they can be used interchangeably.
|
||||||
type ExternalAPIUser struct {
|
type ExternalAPIUser struct {
|
||||||
Id string `json:"id"`
|
ID string `json:"id"`
|
||||||
AccessToken string `json:"accessToken"`
|
AccessToken string `json:"accessToken"`
|
||||||
DisplayName string `json:"displayName"`
|
DisplayName string `json:"displayName"`
|
||||||
DisplayColor int `json:"displayColor"`
|
DisplayColor int `json:"displayColor"`
|
||||||
|
@ -40,7 +40,7 @@ var validAccessTokenScopes = []string{
|
||||||
ScopeHasAdminAccess,
|
ScopeHasAdminAccess,
|
||||||
}
|
}
|
||||||
|
|
||||||
// InsertToken will add a new token to the database.
|
// InsertExternalAPIUser will add a new API user to the database.
|
||||||
func InsertExternalAPIUser(token string, name string, color int, scopes []string) error {
|
func InsertExternalAPIUser(token string, name string, color int, scopes []string) error {
|
||||||
log.Traceln("Adding new API user:", name)
|
log.Traceln("Adding new API user:", name)
|
||||||
|
|
||||||
|
@ -206,7 +206,7 @@ func makeExternalAPIUserFromRow(row *sql.Row) (*ExternalAPIUser, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
integration := ExternalAPIUser{
|
integration := ExternalAPIUser{
|
||||||
Id: id,
|
ID: id,
|
||||||
AccessToken: accessToken,
|
AccessToken: accessToken,
|
||||||
DisplayName: displayName,
|
DisplayName: displayName,
|
||||||
DisplayColor: displayColor,
|
DisplayColor: displayColor,
|
||||||
|
@ -237,7 +237,7 @@ func makeExternalAPIUsersFromRows(rows *sql.Rows) ([]ExternalAPIUser, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
integration := ExternalAPIUser{
|
integration := ExternalAPIUser{
|
||||||
Id: id,
|
ID: id,
|
||||||
AccessToken: accessToken,
|
AccessToken: accessToken,
|
||||||
DisplayName: displayName,
|
DisplayName: displayName,
|
||||||
DisplayColor: displayColor,
|
DisplayColor: displayColor,
|
||||||
|
|
|
@ -16,8 +16,9 @@ import (
|
||||||
|
|
||||||
var _datastore *data.Datastore
|
var _datastore *data.Datastore
|
||||||
|
|
||||||
|
// User represents a single chat user.
|
||||||
type User struct {
|
type User struct {
|
||||||
Id string `json:"id"`
|
ID string `json:"id"`
|
||||||
AccessToken string `json:"-"`
|
AccessToken string `json:"-"`
|
||||||
DisplayName string `json:"displayName"`
|
DisplayName string `json:"displayName"`
|
||||||
DisplayColor int `json:"displayColor"`
|
DisplayColor int `json:"displayColor"`
|
||||||
|
@ -27,14 +28,17 @@ type User struct {
|
||||||
NameChangedAt *time.Time `json:"nameChangedAt,omitempty"`
|
NameChangedAt *time.Time `json:"nameChangedAt,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsEnabled will return if this single user is enabled.
|
||||||
func (u *User) IsEnabled() bool {
|
func (u *User) IsEnabled() bool {
|
||||||
return u.DisabledAt == nil
|
return u.DisabledAt == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetupUsers will perform the initial initialization of the user package.
|
||||||
func SetupUsers() {
|
func SetupUsers() {
|
||||||
_datastore = data.GetDatastore()
|
_datastore = data.GetDatastore()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateAnonymousUser will create a new anonymous user with the provided display name.
|
||||||
func CreateAnonymousUser(username string) (*User, error) {
|
func CreateAnonymousUser(username string) (*User, error) {
|
||||||
id := shortid.MustGenerate()
|
id := shortid.MustGenerate()
|
||||||
accessToken, err := utils.GenerateAccessToken()
|
accessToken, err := utils.GenerateAccessToken()
|
||||||
|
@ -51,7 +55,7 @@ func CreateAnonymousUser(username string) (*User, error) {
|
||||||
displayColor := utils.GenerateRandomDisplayColor()
|
displayColor := utils.GenerateRandomDisplayColor()
|
||||||
|
|
||||||
user := &User{
|
user := &User{
|
||||||
Id: id,
|
ID: id,
|
||||||
AccessToken: accessToken,
|
AccessToken: accessToken,
|
||||||
DisplayName: displayName,
|
DisplayName: displayName,
|
||||||
DisplayColor: displayColor,
|
DisplayColor: displayColor,
|
||||||
|
@ -65,7 +69,8 @@ func CreateAnonymousUser(username string) (*User, error) {
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ChangeUsername(userId string, username string) {
|
// ChangeUsername will change the user associated to userID from one display name to another.
|
||||||
|
func ChangeUsername(userID string, username string) {
|
||||||
_datastore.DbLock.Lock()
|
_datastore.DbLock.Lock()
|
||||||
defer _datastore.DbLock.Unlock()
|
defer _datastore.DbLock.Unlock()
|
||||||
|
|
||||||
|
@ -87,13 +92,13 @@ func ChangeUsername(userId string, username string) {
|
||||||
}
|
}
|
||||||
defer stmt.Close()
|
defer stmt.Close()
|
||||||
|
|
||||||
_, err = stmt.Exec(username, fmt.Sprintf(",%s", username), time.Now(), userId)
|
_, err = stmt.Exec(username, fmt.Sprintf(",%s", username), time.Now(), userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
if err := tx.Commit(); err != nil {
|
||||||
log.Errorln("error changing display name of user", userId, err)
|
log.Errorln("error changing display name of user", userID, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +121,7 @@ func create(user *User) error {
|
||||||
}
|
}
|
||||||
defer stmt.Close()
|
defer stmt.Close()
|
||||||
|
|
||||||
_, err = stmt.Exec(user.Id, user.AccessToken, user.DisplayName, user.DisplayColor, user.DisplayName, user.CreatedAt)
|
_, err = stmt.Exec(user.ID, user.AccessToken, user.DisplayName, user.DisplayColor, user.DisplayName, user.CreatedAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("error creating new user", err)
|
log.Errorln("error creating new user", err)
|
||||||
}
|
}
|
||||||
|
@ -124,6 +129,7 @@ func create(user *User) error {
|
||||||
return tx.Commit()
|
return tx.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetEnabled will will set the enabled flag on a single user assigned to userID.
|
||||||
func SetEnabled(userID string, enabled bool) error {
|
func SetEnabled(userID string, enabled bool) error {
|
||||||
_datastore.DbLock.Lock()
|
_datastore.DbLock.Lock()
|
||||||
defer _datastore.DbLock.Unlock()
|
defer _datastore.DbLock.Unlock()
|
||||||
|
@ -166,8 +172,8 @@ func GetUserByToken(token string) *User {
|
||||||
return getUserFromRow(row)
|
return getUserFromRow(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserById will return a user by a user ID.
|
// GetUserByID will return a user by a user ID.
|
||||||
func GetUserById(id string) *User {
|
func GetUserByID(id string) *User {
|
||||||
_datastore.DbLock.Lock()
|
_datastore.DbLock.Lock()
|
||||||
defer _datastore.DbLock.Unlock()
|
defer _datastore.DbLock.Unlock()
|
||||||
|
|
||||||
|
@ -218,7 +224,7 @@ func getUsersFromRows(rows *sql.Rows) []*User {
|
||||||
}
|
}
|
||||||
|
|
||||||
user := &User{
|
user := &User{
|
||||||
Id: id,
|
ID: id,
|
||||||
DisplayName: displayName,
|
DisplayName: displayName,
|
||||||
DisplayColor: displayColor,
|
DisplayColor: displayColor,
|
||||||
CreatedAt: createdAt,
|
CreatedAt: createdAt,
|
||||||
|
@ -250,7 +256,7 @@ func getUserFromRow(row *sql.Row) *User {
|
||||||
}
|
}
|
||||||
|
|
||||||
return &User{
|
return &User{
|
||||||
Id: id,
|
ID: id,
|
||||||
DisplayName: displayName,
|
DisplayName: displayName,
|
||||||
DisplayColor: displayColor,
|
DisplayColor: displayColor,
|
||||||
CreatedAt: createdAt,
|
CreatedAt: createdAt,
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// SendChatEvent will send a chat event to webhook destinations.
|
||||||
func SendChatEvent(chatEvent *events.UserMessageEvent) {
|
func SendChatEvent(chatEvent *events.UserMessageEvent) {
|
||||||
webhookEvent := WebhookEvent{
|
webhookEvent := WebhookEvent{
|
||||||
Type: chatEvent.GetMessageType(),
|
Type: chatEvent.GetMessageType(),
|
||||||
|
@ -12,7 +13,7 @@ func SendChatEvent(chatEvent *events.UserMessageEvent) {
|
||||||
User: chatEvent.User,
|
User: chatEvent.User,
|
||||||
Body: chatEvent.Body,
|
Body: chatEvent.Body,
|
||||||
RawBody: chatEvent.RawBody,
|
RawBody: chatEvent.RawBody,
|
||||||
ID: chatEvent.Id,
|
ID: chatEvent.ID,
|
||||||
Visible: chatEvent.HiddenAt == nil,
|
Visible: chatEvent.HiddenAt == nil,
|
||||||
Timestamp: &chatEvent.Timestamp,
|
Timestamp: &chatEvent.Timestamp,
|
||||||
},
|
},
|
||||||
|
@ -21,6 +22,7 @@ func SendChatEvent(chatEvent *events.UserMessageEvent) {
|
||||||
SendEventToWebhooks(webhookEvent)
|
SendEventToWebhooks(webhookEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendChatEventUsernameChanged will send a username changed event to webhook destinations.
|
||||||
func SendChatEventUsernameChanged(event events.NameChangeEvent) {
|
func SendChatEventUsernameChanged(event events.NameChangeEvent) {
|
||||||
webhookEvent := WebhookEvent{
|
webhookEvent := WebhookEvent{
|
||||||
Type: models.UserNameChanged,
|
Type: models.UserNameChanged,
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/teris-io/shortid"
|
"github.com/teris-io/shortid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// SendStreamStatusEvent will send all webhook destinations the current stream status.
|
||||||
func SendStreamStatusEvent(eventType models.EventType) {
|
func SendStreamStatusEvent(eventType models.EventType) {
|
||||||
SendEventToWebhooks(WebhookEvent{
|
SendEventToWebhooks(WebhookEvent{
|
||||||
Type: eventType,
|
Type: eventType,
|
||||||
|
|
|
@ -14,11 +14,13 @@ import (
|
||||||
"github.com/owncast/owncast/models"
|
"github.com/owncast/owncast/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// WebhookEvent represents an event sent as a webhook.
|
||||||
type WebhookEvent struct {
|
type WebhookEvent struct {
|
||||||
Type models.EventType `json:"type"` // messageSent | userJoined | userNameChange
|
Type models.EventType `json:"type"` // messageSent | userJoined | userNameChange
|
||||||
EventData interface{} `json:"eventData,omitempty"`
|
EventData interface{} `json:"eventData,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WebhookChatMessage represents a single chat message sent as a webhook payload.
|
||||||
type WebhookChatMessage struct {
|
type WebhookChatMessage struct {
|
||||||
User *user.User `json:"user,omitempty"`
|
User *user.User `json:"user,omitempty"`
|
||||||
Body string `json:"body,omitempty"`
|
Body string `json:"body,omitempty"`
|
||||||
|
@ -28,6 +30,7 @@ type WebhookChatMessage struct {
|
||||||
Timestamp *time.Time `json:"timestamp,omitempty"`
|
Timestamp *time.Time `json:"timestamp,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendEventToWebhooks will send a single webhook event to all webhook destinations.
|
||||||
func SendEventToWebhooks(payload WebhookEvent) {
|
func SendEventToWebhooks(payload WebhookEvent) {
|
||||||
webhooks := data.GetWebhooksForEvent(payload.Type)
|
webhooks := data.GetWebhooksForEvent(payload.Type)
|
||||||
|
|
||||||
|
|
|
@ -19,12 +19,14 @@ import (
|
||||||
|
|
||||||
const maxLogEntries = 500
|
const maxLogEntries = 500
|
||||||
|
|
||||||
|
// OCLogger represents the owncast internal logging.
|
||||||
type OCLogger struct {
|
type OCLogger struct {
|
||||||
Entries []logrus.Entry
|
Entries []logrus.Entry
|
||||||
Warnings []logrus.Entry
|
Warnings []logrus.Entry
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logger is the shared instance of the internal logger.
|
||||||
var Logger *OCLogger
|
var Logger *OCLogger
|
||||||
|
|
||||||
// Setup configures our custom logging destinations.
|
// Setup configures our custom logging destinations.
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/owncast/owncast/config"
|
"github.com/owncast/owncast/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GetTranscoderLogFilePath returns the logging path for the transcoder log output.
|
||||||
func GetTranscoderLogFilePath() string {
|
func GetTranscoderLogFilePath() string {
|
||||||
return filepath.Join(config.LogDirectory, "transcoder.log")
|
return filepath.Join(config.LogDirectory, "transcoder.log")
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,8 @@ const maxCPUAlertingThresholdPCT = 85
|
||||||
const maxRAMAlertingThresholdPCT = 85
|
const maxRAMAlertingThresholdPCT = 85
|
||||||
const maxDiskAlertingThresholdPCT = 90
|
const maxDiskAlertingThresholdPCT = 90
|
||||||
|
|
||||||
var inCpuAlertingState = false
|
var inCPUAlertingState = false
|
||||||
var inRamAlertingState = false
|
var inRAMAlertingState = false
|
||||||
var inDiskAlertingState = false
|
var inDiskAlertingState = false
|
||||||
|
|
||||||
var errorResetDuration = time.Minute * 5
|
var errorResetDuration = time.Minute * 5
|
||||||
|
@ -30,14 +30,14 @@ func handleCPUAlerting() {
|
||||||
}
|
}
|
||||||
|
|
||||||
avg := recentAverage(Metrics.CPUUtilizations)
|
avg := recentAverage(Metrics.CPUUtilizations)
|
||||||
if avg > maxCPUAlertingThresholdPCT && !inCpuAlertingState {
|
if avg > maxCPUAlertingThresholdPCT && !inCPUAlertingState {
|
||||||
log.Warnf(alertingError, "CPU", avg)
|
log.Warnf(alertingError, "CPU", avg)
|
||||||
inCpuAlertingState = true
|
inCPUAlertingState = true
|
||||||
|
|
||||||
resetTimer := time.NewTimer(errorResetDuration)
|
resetTimer := time.NewTimer(errorResetDuration)
|
||||||
go func() {
|
go func() {
|
||||||
<-resetTimer.C
|
<-resetTimer.C
|
||||||
inCpuAlertingState = false
|
inCPUAlertingState = false
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,14 +48,14 @@ func handleRAMAlerting() {
|
||||||
}
|
}
|
||||||
|
|
||||||
avg := recentAverage(Metrics.RAMUtilizations)
|
avg := recentAverage(Metrics.RAMUtilizations)
|
||||||
if avg > maxRAMAlertingThresholdPCT && !inRamAlertingState {
|
if avg > maxRAMAlertingThresholdPCT && !inRAMAlertingState {
|
||||||
log.Warnf(alertingError, "memory", avg)
|
log.Warnf(alertingError, "memory", avg)
|
||||||
inRamAlertingState = true
|
inRAMAlertingState = true
|
||||||
|
|
||||||
resetTimer := time.NewTimer(errorResetDuration)
|
resetTimer := time.NewTimer(errorResetDuration)
|
||||||
go func() {
|
go func() {
|
||||||
<-resetTimer.C
|
<-resetTimer.C
|
||||||
inRamAlertingState = false
|
inRAMAlertingState = false
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,9 +85,9 @@ func getBitrateString(bitrate int) string {
|
||||||
} else if bitrate >= 1000 {
|
} else if bitrate >= 1000 {
|
||||||
if math.Mod(float64(bitrate), 1000) == 0 {
|
if math.Mod(float64(bitrate), 1000) == 0 {
|
||||||
return fmt.Sprintf("%dMbps", bitrate/1000.0)
|
return fmt.Sprintf("%dMbps", bitrate/1000.0)
|
||||||
} else {
|
|
||||||
return fmt.Sprintf("%.1fMbps", float32(bitrate)/1000.0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%.1fMbps", float32(bitrate)/1000.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ExternalAccessTokenHandlerFunc is a function that is called after validing access.
|
||||||
type ExternalAccessTokenHandlerFunc func(user.ExternalAPIUser, http.ResponseWriter, *http.Request)
|
type ExternalAccessTokenHandlerFunc func(user.ExternalAPIUser, http.ResponseWriter, *http.Request)
|
||||||
|
|
||||||
// RequireAdminAuth wraps a handler requiring HTTP basic auth for it using the given
|
// RequireAdminAuth wraps a handler requiring HTTP basic auth for it using the given
|
||||||
|
|
|
@ -234,12 +234,12 @@ func Start() error {
|
||||||
port := config.WebServerPort
|
port := config.WebServerPort
|
||||||
ip := config.WebServerIP
|
ip := config.WebServerIP
|
||||||
|
|
||||||
ip_addr := net.ParseIP(ip)
|
ipAddr := net.ParseIP(ip)
|
||||||
if ip_addr == nil {
|
if ipAddr == nil {
|
||||||
log.Fatalln("Invalid IP address", ip)
|
log.Fatalln("Invalid IP address", ip)
|
||||||
}
|
}
|
||||||
log.Infof("Web server is listening on IP %s port %d.", ip_addr.String(), port)
|
log.Infof("Web server is listening on IP %s port %d.", ipAddr.String(), port)
|
||||||
log.Infoln("The web admin interface is available at /admin.")
|
log.Infoln("The web admin interface is available at /admin.")
|
||||||
|
|
||||||
return http.ListenAndServe(fmt.Sprintf("%s:%d", ip_addr.String(), port), nil)
|
return http.ListenAndServe(fmt.Sprintf("%s:%d", ipAddr.String(), port), nil)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
const tokenLength = 32
|
const tokenLength = 32
|
||||||
|
|
||||||
|
// GenerateAccessToken will generate and return an access token.
|
||||||
func GenerateAccessToken() (string, error) {
|
func GenerateAccessToken() (string, error) {
|
||||||
return generateRandomString(tokenLength)
|
return generateRandomString(tokenLength)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,15 +12,15 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
"github.com/schollz/sqlite3dump"
|
"github.com/schollz/sqlite3dump"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Restore will attempt to restore the database using a specified backup file.
|
||||||
func Restore(backupFile string, databaseFile string) error {
|
func Restore(backupFile string, databaseFile string) error {
|
||||||
log.Printf("Restoring database backup %s to %s", backupFile, databaseFile)
|
log.Printf("Restoring database backup %s to %s", backupFile, databaseFile)
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(backupFile)
|
data, err := ioutil.ReadFile(backupFile) // nolint
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Unable to read backup file %s", err)
|
return fmt.Errorf("Unable to read backup file %s", err)
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ func Restore(backupFile string, databaseFile string) error {
|
||||||
|
|
||||||
defer gz.Close()
|
defer gz.Close()
|
||||||
|
|
||||||
rawSql := b.String()
|
rawSQL := b.String()
|
||||||
|
|
||||||
if _, err := os.Create(databaseFile); err != nil {
|
if _, err := os.Create(databaseFile); err != nil {
|
||||||
return errors.New("Unable to write restored database")
|
return errors.New("Unable to write restored database")
|
||||||
|
@ -49,13 +49,14 @@ func Restore(backupFile string, databaseFile string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := db.Exec(rawSql); err != nil {
|
if _, err := db.Exec(rawSQL); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Backup will backup the provided instance of the database to the specified file.
|
||||||
func Backup(db *sql.DB, backupFile string) {
|
func Backup(db *sql.DB, backupFile string) {
|
||||||
log.Traceln("Backing up database to", backupFile)
|
log.Traceln("Backing up database to", backupFile)
|
||||||
|
|
||||||
|
@ -76,10 +77,10 @@ func Backup(db *sql.DB, backupFile string) {
|
||||||
handleError(err)
|
handleError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
out.Flush()
|
_ = out.Flush()
|
||||||
|
|
||||||
// Create a new backup file
|
// Create a new backup file
|
||||||
f, err := os.OpenFile(backupFile, os.O_WRONLY|os.O_CREATE, 0600)
|
f, err := os.OpenFile(backupFile, os.O_WRONLY|os.O_CREATE, 0600) // nolint
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleError(err)
|
handleError(err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// NullTime is a custom nullable time for representing datetime.
|
||||||
type NullTime struct {
|
type NullTime struct {
|
||||||
Time time.Time
|
Time time.Time
|
||||||
Valid bool // Valid is true if Time is not NULL
|
Valid bool // Valid is true if Time is not NULL
|
||||||
|
@ -25,6 +26,7 @@ func (nt NullTime) Value() (driver.Value, error) {
|
||||||
return nt.Time, nil
|
return nt.Time, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements the JSON marshal function.
|
||||||
func (nt NullTime) MarshalJSON() ([]byte, error) {
|
func (nt NullTime) MarshalJSON() ([]byte, error) {
|
||||||
if !nt.Valid {
|
if !nt.Valid {
|
||||||
return []byte("null"), nil
|
return []byte("null"), nil
|
||||||
|
@ -33,6 +35,7 @@ func (nt NullTime) MarshalJSON() ([]byte, error) {
|
||||||
return []byte(val), nil
|
return []byte(val), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the JSON unmarshal function.
|
||||||
func (nt NullTime) UnmarshalJSON(data []byte) error {
|
func (nt NullTime) UnmarshalJSON(data []byte) error {
|
||||||
dateString := string(data)
|
dateString := string(data)
|
||||||
if dateString == "null" {
|
if dateString == "null" {
|
||||||
|
@ -45,6 +48,6 @@ func (nt NullTime) UnmarshalJSON(data []byte) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
nt.Time = parsedDateTime
|
nt.Time = parsedDateTime // nolint
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -626,11 +626,12 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GeneratePhrase will generate and return a random string consisting of our word list.
|
||||||
func GeneratePhrase() string {
|
func GeneratePhrase() string {
|
||||||
r := rand.New(rand.NewSource(time.Now().UnixNano())) //nolint
|
r := rand.New(rand.NewSource(time.Now().UnixNano())) //nolint
|
||||||
|
|
||||||
left_index := int(r.Float32() * float32(len(left)))
|
leftIndex := int(r.Float32() * float32(len(left)))
|
||||||
right_index := int(r.Float32() * float32(len(right)))
|
rightIndex := int(r.Float32() * float32(len(right)))
|
||||||
|
|
||||||
return fmt.Sprintf("%s-%s", left[left_index], right[right_index])
|
return fmt.Sprintf("%s-%s", left[leftIndex], right[rightIndex])
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ func GetRelativePathFromAbsolutePath(path string) string {
|
||||||
return filepath.Join(variant, file)
|
return filepath.Join(variant, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetIndexFromFilePath is a utility that will return the index/key/variant name in a full path.
|
||||||
func GetIndexFromFilePath(path string) string {
|
func GetIndexFromFilePath(path string) string {
|
||||||
pathComponents := strings.Split(path, "/")
|
pathComponents := strings.Split(path, "/")
|
||||||
variant := pathComponents[len(pathComponents)-2]
|
variant := pathComponents[len(pathComponents)-2]
|
||||||
|
@ -51,7 +52,7 @@ func GetIndexFromFilePath(path string) string {
|
||||||
|
|
||||||
// Copy copies the file to destination.
|
// Copy copies the file to destination.
|
||||||
func Copy(source, destination string) error {
|
func Copy(source, destination string) error {
|
||||||
input, err := ioutil.ReadFile(source)
|
input, err := ioutil.ReadFile(source) // nolint
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -108,6 +109,7 @@ func IsUserAgentAPlayer(userAgent string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RenderSimpleMarkdown will return HTML without sanitization or specific formatting rules.
|
||||||
func RenderSimpleMarkdown(raw string) string {
|
func RenderSimpleMarkdown(raw string) string {
|
||||||
markdown := goldmark.New(
|
markdown := goldmark.New(
|
||||||
goldmark.WithRendererOptions(
|
goldmark.WithRendererOptions(
|
||||||
|
@ -135,6 +137,7 @@ func RenderSimpleMarkdown(raw string) string {
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RenderPageContentMarkdown will return HTML specifically handled for the user-specified page content.
|
||||||
func RenderPageContentMarkdown(raw string) string {
|
func RenderPageContentMarkdown(raw string) string {
|
||||||
markdown := goldmark.New(
|
markdown := goldmark.New(
|
||||||
goldmark.WithRendererOptions(
|
goldmark.WithRendererOptions(
|
||||||
|
@ -189,7 +192,8 @@ func GetCacheDurationSecondsForPath(filePath string) int {
|
||||||
return 60 * 10
|
return 60 * 10
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsValidUrl(urlToTest string) bool {
|
// IsValidURL will return if a URL string is a valid URL or not.
|
||||||
|
func IsValidURL(urlToTest string) bool {
|
||||||
if _, err := url.ParseRequestURI(urlToTest); err != nil {
|
if _, err := url.ParseRequestURI(urlToTest); err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -207,9 +211,8 @@ func ValidatedFfmpegPath(ffmpegPath string) string {
|
||||||
if ffmpegPath != "" {
|
if ffmpegPath != "" {
|
||||||
if err := VerifyFFMpegPath(ffmpegPath); err == nil {
|
if err := VerifyFFMpegPath(ffmpegPath); err == nil {
|
||||||
return ffmpegPath
|
return ffmpegPath
|
||||||
} else {
|
|
||||||
log.Warnln(ffmpegPath, "is an invalid path to ffmpeg will try to use a copy in your path, if possible")
|
|
||||||
}
|
}
|
||||||
|
log.Warnln(ffmpegPath, "is an invalid path to ffmpeg will try to use a copy in your path, if possible")
|
||||||
}
|
}
|
||||||
|
|
||||||
// First look to see if ffmpeg is in the current working directory
|
// First look to see if ffmpeg is in the current working directory
|
||||||
|
@ -255,17 +258,18 @@ func VerifyFFMpegPath(path string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removes the directory and makes it again. Throws fatal error on failure.
|
// CleanupDirectory removes the directory and makes it fresh again. Throws fatal error on failure.
|
||||||
func CleanupDirectory(path string) {
|
func CleanupDirectory(path string) {
|
||||||
log.Traceln("Cleaning", path)
|
log.Traceln("Cleaning", path)
|
||||||
if err := os.RemoveAll(path); err != nil {
|
if err := os.RemoveAll(path); err != nil {
|
||||||
log.Fatalln("Unable to remove directory. Please check the ownership and permissions", err)
|
log.Fatalln("Unable to remove directory. Please check the ownership and permissions", err)
|
||||||
}
|
}
|
||||||
if err := os.MkdirAll(path, 0777); err != nil {
|
if err := os.MkdirAll(path, 0750); err != nil {
|
||||||
log.Fatalln("Unable to create directory. Please check the ownership and permissions", err)
|
log.Fatalln("Unable to create directory. Please check the ownership and permissions", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindInSlice will return the index if a string is located in a slice of strings.
|
||||||
func FindInSlice(slice []string, val string) (int, bool) {
|
func FindInSlice(slice []string, val string) (int, bool) {
|
||||||
for i, item := range slice {
|
for i, item := range slice {
|
||||||
if item == val {
|
if item == val {
|
||||||
|
|
4
yp/yp.go
4
yp/yp.go
|
@ -75,7 +75,7 @@ func (yp *YP) ping() {
|
||||||
log.Warnln("Server URL not set in the configuration. Directory access is disabled until this is set.")
|
log.Warnln("Server URL not set in the configuration. Directory access is disabled until this is set.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
isValidInstanceURL := isUrl(myInstanceURL)
|
isValidInstanceURL := isURL(myInstanceURL)
|
||||||
if myInstanceURL == "" || !isValidInstanceURL {
|
if myInstanceURL == "" || !isValidInstanceURL {
|
||||||
if !_inErrorState {
|
if !_inErrorState {
|
||||||
log.Warnln("YP Error: unable to use", myInstanceURL, "as a public instance URL. Fix this value in your configuration.")
|
log.Warnln("YP Error: unable to use", myInstanceURL, "as a public instance URL. Fix this value in your configuration.")
|
||||||
|
@ -134,7 +134,7 @@ func (yp *YP) ping() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func isUrl(str string) bool {
|
func isURL(str string) bool {
|
||||||
u, err := url.Parse(str)
|
u, err := url.Parse(str)
|
||||||
return err == nil && u.Scheme != "" && u.Host != ""
|
return err == nil && u.Scheme != "" && u.Host != ""
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue