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:
Gabe Kangas 2021-09-12 00:18:15 -07:00 committed by GitHub
parent 70e9f4945f
commit c6c6f0233d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
57 changed files with 331 additions and 186 deletions

View file

@ -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`.

View file

@ -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

View file

@ -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.

View file

@ -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,

View file

@ -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
} }

View file

@ -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,
} }

View file

@ -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
} }
} }

View file

@ -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
} }

View file

@ -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)
} }

View file

@ -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)
} }

View file

@ -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,

View file

@ -1,3 +1,4 @@
// nolint:goimports
// +build !freebsd // +build !freebsd
package chat package chat

View file

@ -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)

View file

@ -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
} }

View file

@ -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(

View file

@ -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"
) )

View file

@ -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,

View file

@ -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
} }

View file

@ -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,
} }

View file

@ -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,
} }

View file

@ -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
} }

View file

@ -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

View file

@ -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, &timestamp, &userDisplayName, &userDisplayColor, &userCreatedAt, &userDisabledAt, &previousUsernames, &userNameChangedAt) err = rows.Scan(&id, &userID, &body, &messageType, &hiddenAt, &timestamp, &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, &timestamp) err := row.Scan(&id, &userID, &body, &eventType, &hiddenAt, &timestamp)
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{

View file

@ -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,

View file

@ -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() {

View file

@ -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 {

View file

@ -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,

View file

@ -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

View file

@ -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
} }

View file

@ -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

View file

@ -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
} }

View file

@ -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
} }
} }

View file

@ -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
} }

View file

@ -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 {
} }

View file

@ -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)
} }

View file

@ -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)
} }

View file

@ -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",

View file

@ -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

View file

@ -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)
} }

View file

@ -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)
} }
} }

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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)

View file

@ -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.

View file

@ -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")
} }

View file

@ -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
}() }()
} }
} }

View file

@ -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 ""

View file

@ -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

View file

@ -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)
} }

View file

@ -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)
} }

View file

@ -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

View file

@ -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
} }

View file

@ -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])
} }

View file

@ -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 {

View file

@ -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 != ""
} }