Compare commits
18 Commits
v0.2.1-old
...
v0.0.13
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d6151c867 | ||
|
|
3a7f69531c | ||
|
|
a74ea4ef40 | ||
|
|
c2a7d9f50b | ||
|
|
e64dd2d206 | ||
|
|
f39c1184e5 | ||
|
|
a9cd28dbec | ||
|
|
4b5a1fcc3f | ||
|
|
96274ad541 | ||
|
|
717bbcf2e7 | ||
|
|
29972bb4e7 | ||
|
|
625b0b0099 | ||
|
|
27afb93e13 | ||
|
|
eb1121e9fa | ||
|
|
e65abb6073 | ||
|
|
29ec8a9091 | ||
|
|
ba7861cda9 | ||
|
|
1cbe2a56fb |
@@ -18,7 +18,12 @@ func handleUndoInboxRequest(c context.Context, activity vocab.ActivityStreamsUnd
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
log.Traceln("Undo", iter.GetType().GetTypeName(), "ignored")
|
||||
t := iter.GetType()
|
||||
if t != nil {
|
||||
log.Traceln("Undo", t.GetTypeName(), "ignored")
|
||||
} else {
|
||||
log.Traceln("Undo (no type) ignored")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
|
||||
func handle(request apmodels.InboxRequest) {
|
||||
if verified, err := Verify(request.Request); err != nil {
|
||||
log.Errorln("Error in attempting to verify request", err)
|
||||
log.Debugln("Error in attempting to verify request", err)
|
||||
return
|
||||
} else if !verified {
|
||||
log.Debugln("Request failed verification", err)
|
||||
|
||||
@@ -173,6 +173,11 @@ func SendPublicMessage(textContent string) error {
|
||||
activity, _, note, noteID := createBaseOutboundMessage(textContent)
|
||||
note.SetActivityStreamsTag(tagProp)
|
||||
|
||||
if !data.GetFederationIsPrivate() {
|
||||
note = apmodels.MakeNotePublic(note)
|
||||
activity = apmodels.MakeActivityPublic(activity)
|
||||
}
|
||||
|
||||
b, err := apmodels.Serialize(activity)
|
||||
if err != nil {
|
||||
log.Errorln("unable to serialize custom fediverse message activity", err)
|
||||
|
||||
@@ -24,19 +24,10 @@ func createFederationFollowersTable() {
|
||||
"approved_at" TIMESTAMP,
|
||||
"disabled_at" TIMESTAMP,
|
||||
"request_object" BLOB,
|
||||
PRIMARY KEY (iri));
|
||||
CREATE INDEX iri_index ON ap_followers (iri);
|
||||
CREATE INDEX approved_at_index ON ap_followers (approved_at);`
|
||||
|
||||
stmt, err := _datastore.DB.Prepare(createTableSQL)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
_, err = stmt.Exec()
|
||||
if err != nil {
|
||||
log.Warnln("error executing sql creating followers table", createTableSQL, err)
|
||||
}
|
||||
PRIMARY KEY (iri));`
|
||||
_datastore.MustExec(createTableSQL)
|
||||
_datastore.MustExec(`CREATE INDEX IF NOT EXISTS idx_iri ON ap_followers (iri);`)
|
||||
_datastore.MustExec(`CREATE INDEX IF NOT EXISTS idx_approved_at ON ap_followers (approved_at);`)
|
||||
}
|
||||
|
||||
// GetFollowerCount will return the number of followers we're keeping track of.
|
||||
|
||||
@@ -202,17 +202,10 @@ func createFederatedActivitiesTable() {
|
||||
"actor" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"timestamp" TIMESTAMP NOT NULL
|
||||
);
|
||||
CREATE INDEX iri_actor_index ON ap_accepted_activities (iri,actor);`
|
||||
);`
|
||||
|
||||
stmt, err := _datastore.DB.Prepare(createTableSQL)
|
||||
if err != nil {
|
||||
log.Fatal("error creating inbox table", err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
if _, err := stmt.Exec(); err != nil {
|
||||
log.Fatal("error creating inbound federated activities table", err)
|
||||
}
|
||||
_datastore.MustExec(createTableSQL)
|
||||
_datastore.MustExec(`CREATE INDEX IF NOT EXISTS idx_iri_actor_index ON ap_accepted_activities (iri,actor);`)
|
||||
}
|
||||
|
||||
func createFederationOutboxTable() {
|
||||
@@ -223,20 +216,12 @@ func createFederationOutboxTable() {
|
||||
"type" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
"live_notification" BOOLEAN DEFAULT FALSE,
|
||||
PRIMARY KEY (iri));
|
||||
CREATE INDEX iri ON ap_outbox (iri);
|
||||
CREATE INDEX type ON ap_outbox (type);
|
||||
CREATE INDEX live_notification ON ap_outbox (live_notification);`
|
||||
PRIMARY KEY (iri));`
|
||||
|
||||
stmt, err := _datastore.DB.Prepare(createTableSQL)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
_, err = stmt.Exec()
|
||||
if err != nil {
|
||||
log.Warnln("error executing sql creating outbox table", createTableSQL, err)
|
||||
}
|
||||
_datastore.MustExec(createTableSQL)
|
||||
_datastore.MustExec(`CREATE INDEX IF NOT EXISTS idx_iri ON ap_outbox (iri);`)
|
||||
_datastore.MustExec(`CREATE INDEX IF NOT EXISTS idx_type ON ap_outbox (type);`)
|
||||
_datastore.MustExec(`CREATE INDEX IF NOT EXISTS idx_live_notification ON ap_outbox (live_notification);`)
|
||||
}
|
||||
|
||||
// GetOutboxPostCount will return the number of posts in the outbox.
|
||||
|
||||
@@ -3,6 +3,7 @@ package fediverse
|
||||
import (
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -19,27 +20,37 @@ type OTPRegistration struct {
|
||||
// to be active at a time.
|
||||
var pendingAuthRequests = make(map[string]OTPRegistration)
|
||||
|
||||
const registrationTimeout = time.Minute * 10
|
||||
|
||||
// RegisterFediverseOTP will start the OTP flow for a user, creating a new
|
||||
// code and returning it to be sent to a destination.
|
||||
func RegisterFediverseOTP(accessToken, userID, userDisplayName, account string) OTPRegistration {
|
||||
func RegisterFediverseOTP(accessToken, userID, userDisplayName, account string) (OTPRegistration, bool) {
|
||||
request, requestExists := pendingAuthRequests[accessToken]
|
||||
|
||||
// If a request is already registered and has not expired then return that
|
||||
// existing request.
|
||||
if requestExists && time.Since(request.Timestamp) < registrationTimeout {
|
||||
return request, false
|
||||
}
|
||||
|
||||
code, _ := createCode()
|
||||
r := OTPRegistration{
|
||||
Code: code,
|
||||
UserID: userID,
|
||||
UserDisplayName: userDisplayName,
|
||||
Account: account,
|
||||
Account: strings.ToLower(account),
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
pendingAuthRequests[accessToken] = r
|
||||
|
||||
return r
|
||||
return r, true
|
||||
}
|
||||
|
||||
// ValidateFediverseOTP will verify a OTP code for a auth request.
|
||||
func ValidateFediverseOTP(accessToken, code string) (bool, *OTPRegistration) {
|
||||
request, ok := pendingAuthRequests[accessToken]
|
||||
|
||||
if !ok || request.Code != code || time.Since(request.Timestamp) > time.Minute*10 {
|
||||
if !ok || request.Code != code || time.Since(request.Timestamp) > registrationTimeout {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package fediverse
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
accessToken = "fake-access-token"
|
||||
@@ -10,7 +13,11 @@ const (
|
||||
)
|
||||
|
||||
func TestOTPFlowValidation(t *testing.T) {
|
||||
r := RegisterFediverseOTP(accessToken, userID, userDisplayName, account)
|
||||
r, success := RegisterFediverseOTP(accessToken, userID, userDisplayName, account)
|
||||
|
||||
if !success {
|
||||
t.Error("Registration should be permitted.")
|
||||
}
|
||||
|
||||
if r.Code == "" {
|
||||
t.Error("Code is empty")
|
||||
@@ -41,3 +48,31 @@ func TestOTPFlowValidation(t *testing.T) {
|
||||
t.Error("UserDisplayName is not set correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSingleOTPFlowRequest(t *testing.T) {
|
||||
r1, _ := RegisterFediverseOTP(accessToken, userID, userDisplayName, account)
|
||||
r2, s2 := RegisterFediverseOTP(accessToken, userID, userDisplayName, account)
|
||||
|
||||
if r1.Code != r2.Code {
|
||||
t.Error("Only one registration should be permitted.")
|
||||
}
|
||||
|
||||
if s2 {
|
||||
t.Error("Second registration should not be permitted.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountCaseInsensitive(t *testing.T) {
|
||||
account := "Account"
|
||||
accessToken := "another-fake-access-token"
|
||||
r1, _ := RegisterFediverseOTP(accessToken, userID, userDisplayName, account)
|
||||
_, reg1 := ValidateFediverseOTP(accessToken, r1.Code)
|
||||
|
||||
// Simulate second auth with account in different case
|
||||
r2, _ := RegisterFediverseOTP(accessToken, userID, userDisplayName, strings.ToUpper(account))
|
||||
_, reg2 := ValidateFediverseOTP(accessToken, r2.Code)
|
||||
|
||||
if reg1.Account != reg2.Account {
|
||||
t.Errorf("Account names should be case-insensitive: %s %s", reg1.Account, reg2.Account)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/owncast/owncast/core/data"
|
||||
"github.com/owncast/owncast/core/user"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/owncast/owncast/db"
|
||||
)
|
||||
@@ -24,18 +23,9 @@ func Setup(db *data.Datastore) {
|
||||
"type" TEXT NOT NULL,
|
||||
"timestamp" DATE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id)
|
||||
);CREATE INDEX auth_token ON auth (token);`
|
||||
|
||||
stmt, err := db.DB.Prepare(createTableSQL)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = stmt.Exec()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
);`
|
||||
_datastore.MustExec(createTableSQL)
|
||||
_datastore.MustExec(`CREATE INDEX IF NOT EXISTS idx_auth_token ON auth (token);`)
|
||||
}
|
||||
|
||||
// AddAuth will add an external authentication token and type for a user.
|
||||
|
||||
@@ -4,7 +4,7 @@ import "path/filepath"
|
||||
|
||||
const (
|
||||
// StaticVersionNumber is the version of Owncast that is used when it's not overwritten via build-time settings.
|
||||
StaticVersionNumber = "0.0.12" // Shown when you build from develop
|
||||
StaticVersionNumber = "0.0.13" // Shown when you build from develop
|
||||
// WebRoot is the web server root directory.
|
||||
WebRoot = "webroot"
|
||||
// FfmpegSuggestedVersion is the version of ffmpeg we suggest.
|
||||
@@ -13,6 +13,8 @@ const (
|
||||
DataDirectory = "data"
|
||||
// EmojiDir is relative to the webroot.
|
||||
EmojiDir = "/img/emoji"
|
||||
// MaxChatDisplayNameLength is the maximum length of a chat display name.
|
||||
MaxChatDisplayNameLength = 30
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -37,7 +37,7 @@ func GetViewersOverTime(w http.ResponseWriter, r *http.Request) {
|
||||
// GetActiveViewers returns currently connected clients.
|
||||
func GetActiveViewers(w http.ResponseWriter, r *http.Request) {
|
||||
c := core.GetActiveViewers()
|
||||
viewers := []models.Viewer{}
|
||||
viewers := make([]models.Viewer, 0, len(c))
|
||||
for _, v := range c {
|
||||
viewers = append(viewers, *v)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/owncast/owncast/activitypub"
|
||||
"github.com/owncast/owncast/auth"
|
||||
"github.com/owncast/owncast/auth/fediverse"
|
||||
fediverseauth "github.com/owncast/owncast/auth/fediverse"
|
||||
"github.com/owncast/owncast/controllers"
|
||||
"github.com/owncast/owncast/core/chat"
|
||||
@@ -29,7 +28,12 @@ func RegisterFediverseOTPRequest(u user.User, w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
accessToken := r.URL.Query().Get("accessToken")
|
||||
reg := fediverseauth.RegisterFediverseOTP(accessToken, u.ID, u.DisplayName, req.FediverseAccount)
|
||||
reg, success := fediverseauth.RegisterFediverseOTP(accessToken, u.ID, u.DisplayName, req.FediverseAccount)
|
||||
if !success {
|
||||
controllers.WriteSimpleResponse(w, false, "Could not register auth request. One may already be pending. Try again later.")
|
||||
return
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("<p>This is an automated message from %s. If you did not request this message please ignore or block. Your requested one-time code is:</p><p>%s</p>", data.GetServerName(), reg.Code)
|
||||
if err := activitypub.SendDirectFederatedMessage(msg, reg.Account); err != nil {
|
||||
controllers.WriteSimpleResponse(w, false, "Could not send code to fediverse: "+err.Error())
|
||||
@@ -52,7 +56,7 @@ func VerifyFediverseOTPRequest(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
accessToken := r.URL.Query().Get("accessToken")
|
||||
valid, authRegistration := fediverse.ValidateFediverseOTP(accessToken, req.Code)
|
||||
valid, authRegistration := fediverseauth.ValidateFediverseOTP(accessToken, req.Code)
|
||||
if !valid {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
@@ -94,5 +98,4 @@ func VerifyFediverseOTPRequest(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
controllers.WriteSimpleResponse(w, true, "")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/owncast/owncast/config"
|
||||
"github.com/owncast/owncast/core/chat/events"
|
||||
"github.com/owncast/owncast/core/data"
|
||||
"github.com/owncast/owncast/core/user"
|
||||
@@ -25,12 +26,17 @@ func (s *Server) userNameChanged(eventData chatClientEvent) {
|
||||
// Check if name is on the blocklist
|
||||
blocklist := data.GetForbiddenUsernameList()
|
||||
|
||||
// Names have a max length
|
||||
if len(proposedUsername) > config.MaxChatDisplayNameLength {
|
||||
proposedUsername = proposedUsername[:config.MaxChatDisplayNameLength]
|
||||
}
|
||||
|
||||
for _, blockedName := range blocklist {
|
||||
normalizedName := strings.TrimSpace(blockedName)
|
||||
normalizedName = strings.ToLower(normalizedName)
|
||||
if strings.Contains(normalizedName, proposedUsername) {
|
||||
// Denied.
|
||||
log.Debugln(eventData.client.User.DisplayName, "blocked from changing name to", proposedUsername, "due to blocked name", normalizedName)
|
||||
log.Debugln(logSanitize(eventData.client.User.DisplayName), "blocked from changing name to", logSanitize(proposedUsername), "due to blocked name", normalizedName)
|
||||
message := fmt.Sprintf("You cannot change your name to **%s**.", proposedUsername)
|
||||
s.sendActionToClient(eventData.client, message)
|
||||
|
||||
@@ -59,7 +65,7 @@ func (s *Server) userNameChanged(eventData chatClientEvent) {
|
||||
oldName := savedUser.DisplayName
|
||||
|
||||
// Save the new name
|
||||
if err := user.ChangeUsername(eventData.client.User.ID, receivedEvent.NewName); err != nil {
|
||||
if err := user.ChangeUsername(eventData.client.User.ID, proposedUsername); err != nil {
|
||||
log.Errorln("error changing username", err)
|
||||
}
|
||||
|
||||
@@ -69,7 +75,7 @@ func (s *Server) userNameChanged(eventData chatClientEvent) {
|
||||
eventData.client.User.NameChangedAt = &now
|
||||
|
||||
// Send chat event letting everyone about about the name change
|
||||
savedUser.DisplayName = receivedEvent.NewName
|
||||
savedUser.DisplayName = proposedUsername
|
||||
|
||||
broadcastEvent := events.NameChangeBroadcast{
|
||||
Oldname: oldName,
|
||||
@@ -132,3 +138,11 @@ func (s *Server) userMessageSent(eventData chatClientEvent) {
|
||||
eventData.client.MessageCount++
|
||||
_lastSeenCache[event.User.ID] = time.Now()
|
||||
}
|
||||
|
||||
func logSanitize(userValue string) string {
|
||||
// strip carriage return and newline from user-submitted values to prevent log injection
|
||||
sanitizedValue := strings.Replace(userValue, "\n", "", -1)
|
||||
sanitizedValue = strings.Replace(sanitizedValue, "\r", "", -1)
|
||||
|
||||
return fmt.Sprintf("userSuppliedValue(%s)", sanitizedValue)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package chat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"database/sql"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -207,20 +207,14 @@ type rowData struct {
|
||||
userType *string
|
||||
}
|
||||
|
||||
func getChat(query string) []interface{} {
|
||||
func getChat(rows *sql.Rows) ([]interface{}, error) {
|
||||
history := make([]interface{}, 0)
|
||||
rows, err := _datastore.DB.Query(query)
|
||||
if err != nil || rows.Err() != nil {
|
||||
log.Errorln("error fetching chat history", err)
|
||||
return history
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
row := rowData{}
|
||||
|
||||
// Convert a database row into a chat event
|
||||
if err = rows.Scan(
|
||||
if err := rows.Scan(
|
||||
&row.id,
|
||||
&row.userID,
|
||||
&row.body,
|
||||
@@ -241,9 +235,7 @@ func getChat(query string) []interface{} {
|
||||
&row.userScopes,
|
||||
&row.userType,
|
||||
); err != nil {
|
||||
log.Errorln(err)
|
||||
log.Errorln("There is a problem converting query to chat objects. Please report this:", query)
|
||||
break
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var message interface{}
|
||||
@@ -266,7 +258,7 @@ func getChat(query string) []interface{} {
|
||||
history = append(history, message)
|
||||
}
|
||||
|
||||
return history
|
||||
return history, nil
|
||||
}
|
||||
|
||||
var _historyCache *[]interface{}
|
||||
@@ -277,20 +269,89 @@ func GetChatModerationHistory() []interface{} {
|
||||
return *_historyCache
|
||||
}
|
||||
|
||||
tx, err := _datastore.DB.Begin()
|
||||
if err != nil {
|
||||
log.Errorln("error fetching chat moderation history", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
defer tx.Rollback() // nolint
|
||||
|
||||
// Get all messages regardless of visibility
|
||||
query := "SELECT messages.id, user_id, body, title, subtitle, image, link, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at, authenticated_at, scopes, type FROM messages INNER JOIN users ON messages.user_id = users.id ORDER BY timestamp DESC"
|
||||
result := getChat(query)
|
||||
stmt, err := tx.Prepare(query)
|
||||
if err != nil {
|
||||
log.Errorln("error fetching chat moderation history", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
rows, err := stmt.Query()
|
||||
if err != nil {
|
||||
log.Errorln("error fetching chat moderation history", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
defer stmt.Close()
|
||||
defer rows.Close()
|
||||
|
||||
result, err := getChat(rows)
|
||||
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
log.Errorln("There is a problem enumerating chat message rows. Please report this:", query)
|
||||
return nil
|
||||
}
|
||||
|
||||
_historyCache = &result
|
||||
|
||||
if err = tx.Commit(); err != nil {
|
||||
log.Errorln("error fetching chat moderation history", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetChatHistory will return all the chat messages suitable for returning as user-facing chat history.
|
||||
func GetChatHistory() []interface{} {
|
||||
tx, err := _datastore.DB.Begin()
|
||||
if err != nil {
|
||||
log.Errorln("error fetching chat history", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
defer tx.Rollback() // nolint
|
||||
|
||||
// Get all visible messages
|
||||
query := fmt.Sprintf("SELECT messages.id, messages.user_id, messages.body, messages.title, messages.subtitle, messages.image, messages.link, messages.eventType, messages.hidden_at, messages.timestamp, users.display_name, users.display_color, users.created_at, users.disabled_at, users.previous_names, users.namechanged_at, users.authenticated_at, users.scopes, users.type FROM users JOIN messages ON users.id = messages.user_id WHERE hidden_at IS NULL AND disabled_at IS NULL ORDER BY timestamp DESC LIMIT %d", maxBacklogNumber)
|
||||
m := getChat(query)
|
||||
query := "SELECT messages.id, messages.user_id, messages.body, messages.title, messages.subtitle, messages.image, messages.link, messages.eventType, messages.hidden_at, messages.timestamp, users.display_name, users.display_color, users.created_at, users.disabled_at, users.previous_names, users.namechanged_at, users.authenticated_at, users.scopes, users.type FROM users JOIN messages ON users.id = messages.user_id WHERE hidden_at IS NULL AND disabled_at IS NULL ORDER BY timestamp DESC LIMIT ?"
|
||||
|
||||
stmt, err := tx.Prepare(query)
|
||||
if err != nil {
|
||||
log.Errorln("error fetching chat history", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
rows, err := stmt.Query(maxBacklogNumber)
|
||||
if err != nil {
|
||||
log.Errorln("error fetching chat history", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
defer stmt.Close()
|
||||
defer rows.Close()
|
||||
|
||||
m, err := getChat(rows)
|
||||
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
log.Errorln("There is a problem enumerating chat message rows. Please report this:", query)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = tx.Commit(); err != nil {
|
||||
log.Errorln("error fetching chat history", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Invert order of messages
|
||||
for i, j := 0, len(m)-1; i < j; i, j = i+1, j-1 {
|
||||
@@ -307,10 +368,40 @@ func SetMessageVisibilityForUserID(userID string, visible bool) error {
|
||||
_historyCache = nil
|
||||
}()
|
||||
|
||||
tx, err := _datastore.DB.Begin()
|
||||
if err != nil {
|
||||
log.Errorln("error while setting message visibility", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
defer tx.Rollback() // nolint
|
||||
query := "SELECT messages.id, user_id, body, title, subtitle, image, link, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at, authenticated_at, scopes, type FROM messages INNER JOIN users ON messages.user_id = users.id WHERE user_id IS ?"
|
||||
|
||||
stmt, err := tx.Prepare(query)
|
||||
if err != nil {
|
||||
log.Errorln("error while setting message visibility", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
rows, err := stmt.Query(userID)
|
||||
if err != nil {
|
||||
log.Errorln("error while setting message visibility", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
defer stmt.Close()
|
||||
defer rows.Close()
|
||||
|
||||
// Get a list of IDs to send to the connected clients to hide
|
||||
ids := make([]string, 0)
|
||||
query := fmt.Sprintf("SELECT messages.id, user_id, body, title, subtitle, image, link, eventType, hidden_at, timestamp, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at, authenticated_at, scopes, type FROM messages INNER JOIN users ON messages.user_id = users.id WHERE user_id IS '%s'", userID)
|
||||
messages := getChat(query)
|
||||
|
||||
messages, err := getChat(rows)
|
||||
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
log.Errorln("There is a problem enumerating chat message rows. Please report this:", query)
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(messages) == 0 {
|
||||
return nil
|
||||
@@ -320,6 +411,11 @@ func SetMessageVisibilityForUserID(userID string, visible bool) error {
|
||||
ids = append(ids, message.(events.UserMessageEvent).ID)
|
||||
}
|
||||
|
||||
if err = tx.Commit(); err != nil {
|
||||
log.Errorln("error while setting message visibility ", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Tell the clients to hide/show these messages.
|
||||
return SetMessagesVisibility(ids, visible)
|
||||
}
|
||||
|
||||
@@ -355,7 +355,7 @@ func (s *Server) eventReceived(event chatClientEvent) {
|
||||
s.userNameChanged(event)
|
||||
|
||||
default:
|
||||
log.Debugln(eventType, "event not found:", typecheck)
|
||||
log.Debugln(logSanitize(fmt.Sprint(eventType)), "event not found:", logSanitize(fmt.Sprint(typecheck)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
schemaVersion = 5
|
||||
schemaVersion = 6
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -13,30 +13,26 @@ import (
|
||||
func CreateMessagesTable(db *sql.DB) {
|
||||
createTableSQL := `CREATE TABLE IF NOT EXISTS messages (
|
||||
"id" string NOT NULL,
|
||||
"user_id" INTEGER,
|
||||
"user_id" TEXT,
|
||||
"body" TEXT,
|
||||
"eventType" TEXT,
|
||||
"hidden_at" DATETIME,
|
||||
"timestamp" DATETIME,
|
||||
"title" TEXT,
|
||||
"subtitle" TEXT,
|
||||
"image" TEXT,
|
||||
"link" TEXT,
|
||||
"title" TEXT,
|
||||
"subtitle" TEXT,
|
||||
"image" TEXT,
|
||||
"link" TEXT,
|
||||
PRIMARY KEY (id)
|
||||
);CREATE INDEX index ON messages (id, user_id, hidden_at, timestamp);
|
||||
CREATE INDEX id ON messages (id);
|
||||
CREATE INDEX user_id ON messages (user_id);
|
||||
CREATE INDEX hidden_at ON messages (hidden_at);
|
||||
CREATE INDEX timestamp ON messages (timestamp);`
|
||||
);`
|
||||
MustExec(createTableSQL, db)
|
||||
|
||||
stmt, err := db.Prepare(createTableSQL)
|
||||
if err != nil {
|
||||
log.Fatal("error creating chat messages table", err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
if _, err := stmt.Exec(); err != nil {
|
||||
log.Fatal("error creating chat messages table", err)
|
||||
}
|
||||
// Create indexes
|
||||
MustExec(`CREATE INDEX IF NOT EXISTS user_id_hidden_at_timestamp ON messages (id, user_id, hidden_at, timestamp);`, db)
|
||||
MustExec(`CREATE INDEX IF NOT EXISTS idx_id ON messages (id);`, db)
|
||||
MustExec(`CREATE INDEX IF NOT EXISTS idx_user_id ON messages (user_id);`, db)
|
||||
MustExec(`CREATE INDEX IF NOT EXISTS idx_hidden_at ON messages (hidden_at);`, db)
|
||||
MustExec(`CREATE INDEX IF NOT EXISTS idx_timestamp ON messages (timestamp);`, db)
|
||||
MustExec(`CREATE INDEX IF NOT EXISTS idx_messages_hidden_at_timestamp on messages(hidden_at, timestamp);`, db)
|
||||
}
|
||||
|
||||
// GetMessagesCount will return the number of messages in the database.
|
||||
|
||||
@@ -29,6 +29,8 @@ func migrateDatabaseSchema(db *sql.DB, from, to int) error {
|
||||
migrateToSchema4(db)
|
||||
case 4:
|
||||
migrateToSchema5(db)
|
||||
case 5:
|
||||
migrateToSchema6(db)
|
||||
default:
|
||||
log.Fatalln("missing database migration step")
|
||||
}
|
||||
@@ -42,6 +44,16 @@ func migrateDatabaseSchema(db *sql.DB, from, to int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func migrateToSchema6(db *sql.DB) {
|
||||
// Fix chat messages table schema. Since chat is ephemeral we can drop
|
||||
// the table and recreate it.
|
||||
// Drop the old messages table
|
||||
MustExec(`DROP TABLE messages`, db)
|
||||
|
||||
// Recreate it
|
||||
CreateMessagesTable(db)
|
||||
}
|
||||
|
||||
// nolint:cyclop
|
||||
func migrateToSchema5(db *sql.DB) {
|
||||
// Create the access tokens table.
|
||||
|
||||
@@ -118,18 +118,9 @@ func (ds *Datastore) Setup() {
|
||||
"key" string NOT NULL PRIMARY KEY,
|
||||
"value" BLOB,
|
||||
"timestamp" DATE DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
);CREATE INDEX IF NOT EXISTS messages_timestamp_index ON messages(timestamp);`
|
||||
);`
|
||||
|
||||
stmt, err := ds.DB.Prepare(createTableSQL)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = stmt.Exec()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
ds.MustExec(createTableSQL)
|
||||
|
||||
if !HasPopulatedDefaults() {
|
||||
PopulateDefaults()
|
||||
@@ -173,3 +164,16 @@ func (ds *Datastore) Reset() {
|
||||
func GetDatastore() *Datastore {
|
||||
return _datastore
|
||||
}
|
||||
|
||||
// MustExec will execute a SQL statement on a provided database instance.
|
||||
func (ds *Datastore) MustExec(s string) {
|
||||
stmt, err := ds.DB.Prepare(s)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
_, err = stmt.Exec()
|
||||
if err != nil {
|
||||
log.Warnln(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,20 +41,12 @@ func createUsersTable(db *sql.DB) {
|
||||
"type" TEXT DEFAULT 'STANDARD',
|
||||
"last_used" DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id)
|
||||
);CREATE INDEX user_id_disabled_at_index ON users (id, disabled_at);
|
||||
CREATE INDEX user_id_index ON users (id);
|
||||
CREATE INDEX user_id_disabled_index ON users (id, disabled_at);
|
||||
CREATE INDEX user_disabled_at_index ON USERS (disabled_at);`
|
||||
);`
|
||||
|
||||
stmt, err := db.Prepare(createTableSQL)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
_, err = stmt.Exec()
|
||||
if err != nil {
|
||||
log.Warnln(err)
|
||||
}
|
||||
MustExec(createTableSQL, db)
|
||||
MustExec(`CREATE INDEX IF NOT EXISTS idx_user_id ON users (id);`, db)
|
||||
MustExec(`CREATE INDEX IF NOT EXISTS idx_user_id_disabled ON users (id, disabled_at);`, db)
|
||||
MustExec(`CREATE INDEX IF NOT EXISTS idx_user_disabled_at ON users (disabled_at);`, db)
|
||||
}
|
||||
|
||||
// GetUsersCount will return the number of users in the database.
|
||||
|
||||
20
core/data/utils.go
Normal file
20
core/data/utils.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// MustExec will execute a SQL statement on a provided database instance.
|
||||
func MustExec(s string, db *sql.DB) {
|
||||
stmt, err := db.Prepare(s)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
_, err = stmt.Exec()
|
||||
if err != nil {
|
||||
log.Warnln(err)
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -20,6 +20,18 @@ func GetSocialHandle(platform string) *SocialHandle {
|
||||
// GetAllSocialHandles will return a list of all the social platforms we support.
|
||||
func GetAllSocialHandles() map[string]SocialHandle {
|
||||
socialHandlePlatforms := map[string]SocialHandle{
|
||||
"fediverse": {
|
||||
Platform: "Fediverse",
|
||||
Icon: "/img/platformlogos/fediverse.svg",
|
||||
},
|
||||
"matrix": {
|
||||
Platform: "Matrix",
|
||||
Icon: "/img/platformlogos/matrix.svg",
|
||||
},
|
||||
"xmpp": {
|
||||
Platform: "XMPP",
|
||||
Icon: "/img/platformlogos/xmpp.svg",
|
||||
},
|
||||
"bandcamp": {
|
||||
Platform: "Bandcamp",
|
||||
Icon: "/img/platformlogos/bandcamp.svg",
|
||||
|
||||
@@ -17,18 +17,10 @@ func createNotificationsTable(db *sql.DB) {
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"channel" TEXT NOT NULL,
|
||||
"destination" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
|
||||
CREATE INDEX channel_index ON notifications (channel);`
|
||||
"created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP);`
|
||||
|
||||
stmt, err := db.Prepare(createTableSQL)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
_, err = stmt.Exec()
|
||||
if err != nil {
|
||||
log.Warnln("error executing sql creating followers table", createTableSQL, err)
|
||||
}
|
||||
data.MustExec(createTableSQL, db)
|
||||
data.MustExec(`CREATE INDEX IF NOT EXISTS idx_channel ON notifications (channel);`, db)
|
||||
}
|
||||
|
||||
// AddNotification saves a new user notification destination.
|
||||
|
||||
@@ -2,7 +2,7 @@ openapi: 3.0.1
|
||||
info:
|
||||
title: Owncast
|
||||
description: Owncast is a self-hosted live video and web chat server for use with existing popular broadcasting software.
|
||||
version: '0.0.12'
|
||||
version: '0.0.13'
|
||||
contact:
|
||||
name: Gabe Kangas
|
||||
email: gabek@real-ity.com
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"version": "1",
|
||||
"packages": [{
|
||||
"schema": "db/schema.sql",
|
||||
"queries": "db/query.sql",
|
||||
"name": "db",
|
||||
"path": "db"
|
||||
}]
|
||||
}
|
||||
6
sqlc.yaml
Normal file
6
sqlc.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
version: 1
|
||||
packages:
|
||||
- path: db
|
||||
name: db
|
||||
schema: 'db/schema.sql'
|
||||
queries: 'db/query.sql'
|
||||
162
webroot/img/platformlogos/fediverse.svg
Normal file
162
webroot/img/platformlogos/fediverse.svg
Normal file
@@ -0,0 +1,162 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="196.52mm"
|
||||
height="196.52mm"
|
||||
viewBox="0 0 196.52 196.52"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
sodipodi:docname="fediverse.svg"
|
||||
inkscape:export-filename="/home/nestor/Pictures/Fediversal/Logo_penta_connectat-imbrincat_retallats-color-512x.png"
|
||||
inkscape:export-xdpi="66.175453"
|
||||
inkscape:export-ydpi="66.175453"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.71293238"
|
||||
inkscape:cx="49.093015"
|
||||
inkscape:cy="446.04511"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer3"
|
||||
showgrid="false"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:snap-midpoints="true"
|
||||
inkscape:snap-global="false"
|
||||
inkscape:window-width="3834"
|
||||
inkscape:window-height="1568"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="1"
|
||||
fit-margin-top="5"
|
||||
fit-margin-left="5"
|
||||
fit-margin-right="5"
|
||||
fit-margin-bottom="5"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#d1d1d1" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="Linies"
|
||||
style="display:inline"
|
||||
transform="translate(6.6789703,-32.495842)">
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#a730b8;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:12.4769;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 30.433048,105.79682 a 20.675157,20.675157 0 0 1 -8.842632,8.8016 l 48.544801,48.73413 11.703623,-5.93124 z m 64.032011,64.27936 -11.703623,5.93123 24.597704,24.69384 a 20.675158,20.675158 0 0 1 8.8444,-8.80277 z"
|
||||
id="path9722"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#5496be;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:12.4769;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 150.63096,125.07992 -27.48274,13.92806 2.02632,12.9615 31.09575,-15.75978 a 20.675158,20.675158 0 0 1 -5.63933,-11.12978 z m -43.43711,22.01339 -64.98098,32.93215 a 20.675157,20.675157 0 0 1 5.640504,11.13096 l 61.366786,-31.10103 z"
|
||||
id="path9729"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ce3d1a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:12.4769;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 86.296497,65.972733 54.94108,127.18595 64.198706,136.47934 97.397557,71.668922 A 20.675157,20.675157 0 0 1 86.296497,65.972733 Z M 46.785998,143.10691 30.903725,174.1124 a 20.675158,20.675158 0 0 1 11.099891,5.6956 L 56.04304,152.39971 Z"
|
||||
id="path9713"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#d0188f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:12.4769;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 21.320788,114.7344 a 20.675157,20.675157 0 0 1 -10.328521,2.15938 20.675157,20.675157 0 0 1 -1.9958343,-0.20867 l 9.2740393,59.31996 a 20.675158,20.675158 0 0 1 10.328518,-2.15937 20.675158,20.675158 0 0 1 1.994664,0.20867 z"
|
||||
id="path1015"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#5b36e9;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:12.4769;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 47.902612,191.45008 a 20.675157,20.675157 0 0 1 0.216288,4.16459 20.675157,20.675157 0 0 1 -2.181058,8.15683 l 59.309998,9.51729 a 20.675158,20.675158 0 0 1 -0.21687,-4.16516 20.675158,20.675158 0 0 1 2.18163,-8.15626 z"
|
||||
id="path1674"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#30b873;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:12.4769;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 156.47896,136.42659 -27.37547,53.44382 a 20.675157,20.675157 0 0 1 11.10223,5.69678 l 27.37489,-53.44325 a 20.675158,20.675158 0 0 1 -11.10165,-5.69735 z"
|
||||
id="path1676"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ebe305;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:12.4769;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 119.14248,60.83866 a 20.675157,20.675157 0 0 1 -8.84438,8.802775 l 42.39493,42.558465 a 20.675158,20.675158 0 0 1 8.8438,-8.80277 z"
|
||||
id="path1678"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#f47601;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:12.4769;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 80.447909,54.626661 26.855786,81.785842 a 20.675158,20.675158 0 0 1 5.639919,11.129782 L 86.08724,65.755272 A 20.675157,20.675157 0 0 1 80.447909,54.626661 Z"
|
||||
id="path1680"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#57c115;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:12.4769;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 110.20021,69.69067 a 20.675157,20.675157 0 0 1 -10.498502,2.245535 20.675157,20.675157 0 0 1 -1.809441,-0.189912 l 4.747213,30.398237 12.9533,2.07848 z m -4.80173,50.12623 11.22415,71.87057 a 20.675158,20.675158 0 0 1 10.17788,-2.08376 20.675158,20.675158 0 0 1 2.16289,0.23739 l -10.61104,-67.9463 z"
|
||||
id="path9758"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#dbb210;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:12.4769;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 32.533803,93.143636 a 20.675157,20.675157 0 0 1 0.227426,4.23023 20.675157,20.675157 0 0 1 -2.148818,8.097644 l 30.421682,4.88613 5.979296,-11.676661 z m 52.13965,8.373124 -5.979885,11.67783 71.879942,11.5442 a 20.675158,20.675158 0 0 1 -0.20867,-4.11711 20.675158,20.675158 0 0 1 2.20568,-8.20021 z"
|
||||
id="path9760"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer3"
|
||||
inkscape:label="Nodes"
|
||||
style="display:inline;opacity:1"
|
||||
transform="translate(6.6789703,-32.495842)">
|
||||
<circle
|
||||
style="fill:#ffca00;fill-opacity:0.995968;stroke:none;stroke-width:0.300108;stroke-opacity:0.960784"
|
||||
id="path817"
|
||||
cx="103.46658"
|
||||
cy="45.731655"
|
||||
transform="rotate(3.1178174)"
|
||||
r="18.795599" />
|
||||
<circle
|
||||
id="path819"
|
||||
style="fill:#64ff00;fill-opacity:0.995968;stroke:none;stroke-width:0.300108;stroke-opacity:0.960784"
|
||||
cx="177.37808"
|
||||
cy="112.26514"
|
||||
transform="rotate(3.1178174)"
|
||||
r="18.795599" />
|
||||
<circle
|
||||
id="path823"
|
||||
style="fill:#00a3ff;fill-opacity:0.995968;stroke:none;stroke-width:0.300108;stroke-opacity:0.960784"
|
||||
cx="136.92497"
|
||||
cy="203.10144"
|
||||
transform="rotate(3.1178174)"
|
||||
r="18.795599" />
|
||||
<circle
|
||||
style="fill:#9500ff;fill-opacity:0.995968;stroke:none;stroke-width:0.300108;stroke-opacity:0.960784"
|
||||
id="path825"
|
||||
cx="38.012062"
|
||||
cy="192.70789"
|
||||
transform="rotate(3.1178174)"
|
||||
r="18.795599" />
|
||||
<circle
|
||||
id="path827"
|
||||
style="fill:#ff0000;fill-opacity:0.995968;stroke:none;stroke-width:0.300108;stroke-opacity:0.960784"
|
||||
cx="17.333637"
|
||||
cy="95.447998"
|
||||
transform="rotate(3.1178174)"
|
||||
r="18.795599" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 18 KiB |
14
webroot/img/platformlogos/matrix.svg
Normal file
14
webroot/img/platformlogos/matrix.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 520 520" style="enable-background:new 0 0 520 520;" xml:space="preserve">
|
||||
<path d="M13.7,11.9v496.2h35.7V520H0V0h49.4v11.9H13.7z"/>
|
||||
<path d="M166.3,169.2v25.1h0.7c6.7-9.6,14.8-17,24.2-22.2c9.4-5.3,20.3-7.9,32.5-7.9c11.7,0,22.4,2.3,32.1,6.8
|
||||
c9.7,4.5,17,12.6,22.1,24c5.5-8.1,13-15.3,22.4-21.5c9.4-6.2,20.6-9.3,33.5-9.3c9.8,0,18.9,1.2,27.3,3.6c8.4,2.4,15.5,6.2,21.5,11.5
|
||||
c6,5.3,10.6,12.1,14,20.6c3.3,8.5,5,18.7,5,30.7v124.1h-50.9V249.6c0-6.2-0.2-12.1-0.7-17.6c-0.5-5.5-1.8-10.3-3.9-14.3
|
||||
c-2.2-4.1-5.3-7.3-9.5-9.7c-4.2-2.4-9.9-3.6-17-3.6c-7.2,0-13,1.4-17.4,4.1c-4.4,2.8-7.9,6.3-10.4,10.8c-2.5,4.4-4.2,9.4-5,15.1
|
||||
c-0.8,5.6-1.3,11.3-1.3,17v103.3h-50.9v-104c0-5.5-0.1-10.9-0.4-16.3c-0.2-5.4-1.3-10.3-3.1-14.9c-1.8-4.5-4.8-8.2-9-10.9
|
||||
c-4.2-2.7-10.3-4.1-18.5-4.1c-2.4,0-5.6,0.5-9.5,1.6c-3.9,1.1-7.8,3.1-11.5,6.1c-3.7,3-6.9,7.3-9.5,12.9c-2.6,5.6-3.9,13-3.9,22.1
|
||||
v107.6h-50.9V169.2H166.3z"/>
|
||||
<path d="M506.3,508.1V11.9h-35.7V0H520v520h-49.4v-11.9H506.3z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
120
webroot/img/platformlogos/xmpp.svg
Normal file
120
webroot/img/platformlogos/xmpp.svg
Normal file
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
|
||||
|
||||
<svg:svg
|
||||
version="1.1"
|
||||
xml:space="preserve"
|
||||
viewBox="0 0 200 200"
|
||||
width="200px"
|
||||
height="200px"
|
||||
x="0px"
|
||||
y="0px"
|
||||
enable-background="new 0 0 200 200"
|
||||
class="TridactylThemeDark"
|
||||
id="svg36"
|
||||
sodipodi:docname="xmpp.svg"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:xhtml="http://www.w3.org/1999/xhtml"><svg:defs
|
||||
id="defs40" /><sodipodi:namedview
|
||||
id="namedview38"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#505050"
|
||||
showgrid="false"
|
||||
inkscape:zoom="3.337544"
|
||||
inkscape:cx="134.53006"
|
||||
inkscape:cy="118.05088"
|
||||
inkscape:window-width="3834"
|
||||
inkscape:window-height="1568"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg36" />
|
||||
|
||||
<svg:linearGradient
|
||||
id="SVGID_right_"
|
||||
y2="1.279e-13"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x2="-1073.2"
|
||||
gradientTransform="matrix(1.1429608,0,0,1.1429608,1352.3375,27.297)"
|
||||
y1="126.85"
|
||||
x1="-1073.2">
|
||||
<svg:stop
|
||||
stop-color="#1b3967"
|
||||
offset=".011"
|
||||
id="stop2" />
|
||||
<svg:stop
|
||||
stop-color="#13b5ea"
|
||||
offset=".467"
|
||||
id="stop4" />
|
||||
<svg:stop
|
||||
stop-color="#002b5c"
|
||||
offset=".9945"
|
||||
id="stop6" />
|
||||
</svg:linearGradient>
|
||||
|
||||
<svg:linearGradient
|
||||
id="SVGID_left_"
|
||||
y2="1.279e-13"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x2="-1073.2"
|
||||
gradientTransform="matrix(-1.1429608,0,0,1.1429608,-1152.3376,27.295856)"
|
||||
y1="126.85"
|
||||
x1="-1073.2">
|
||||
<svg:stop
|
||||
stop-color="#1b3967"
|
||||
offset=".011"
|
||||
id="stop9" />
|
||||
<svg:stop
|
||||
stop-color="#13b5ea"
|
||||
offset=".467"
|
||||
id="stop11" />
|
||||
<svg:stop
|
||||
stop-color="#002b5c"
|
||||
offset=".9945"
|
||||
id="stop13" />
|
||||
</svg:linearGradient>
|
||||
|
||||
<svg:path
|
||||
d="m 158.17335,43.514472 c 0.088,1.500707 -2.04247,1.106386 -2.04247,2.620809 0,44.062284 -53.21398,111.285539 -104.795782,124.274139 v 1.87332 C 119.85902,165.9736 198.27413,94.789982 200,27.298143 l -41.83123,16.217472 z"
|
||||
style="fill:url(#SVGID_right_);stroke-width:1.14296"
|
||||
id="path16" />
|
||||
<svg:path
|
||||
d="m 137.44918,48.935535 c 0.0868,1.500708 0.13715,3.005988 0.13715,4.522696 0,44.062284 -35.08773,103.434549 -86.667269,116.421999 v 1.87332 C 118.40404,168.56469 171.85574,99.719572 171.85574,46.942212 c 0,-2.714532 -0.1463,-5.405062 -0.42403,-8.064732 l -33.98023,10.05577 z"
|
||||
style="fill:#e96d1f;stroke-width:1.14296"
|
||||
id="path18" />
|
||||
<svg:path
|
||||
d="m 171.75858,38.249995 -8.70592,3.111139 c 0.0469,1.099528 0.0755,2.576234 0.0755,3.686048 0,47.111704 -42.59929,112.243348 -99.748468,122.433978 -3.708919,1.24354 -8.61565,2.37393 -12.494848,3.35002 v 1.87217 C 125.46753,166.34848 177.86772,90.563313 171.7643,38.245422 Z"
|
||||
style="fill:#d9541e;stroke-width:1.14296"
|
||||
id="path20" />
|
||||
|
||||
<svg:path
|
||||
d="m 41.826653,43.513329 c -0.088,1.500708 2.042471,1.106387 2.042471,2.620809 0,44.062284 53.21398,111.285542 104.795786,124.274152 v 1.87331 C 80.140986,165.97245 1.7258709,94.78884 0,27.297 l 41.831225,16.217472 z"
|
||||
style="fill:url(#SVGID_left_);stroke-width:1.14296"
|
||||
id="path22" />
|
||||
<svg:path
|
||||
d="m 62.550819,48.934392 c -0.08687,1.500708 -0.137155,3.005988 -0.137155,4.522697 0,44.062284 35.087749,103.434541 86.667286,116.422011 v 1.87331 C 81.595976,168.56354 28.144269,99.718429 28.144269,46.941069 c 0,-2.714533 0.146298,-5.405062 0.424038,-8.064732 l 33.980226,10.05577 z"
|
||||
style="fill:#a0ce67;stroke-width:1.14296"
|
||||
id="path24" />
|
||||
<svg:path
|
||||
d="m 28.241419,38.248851 8.705933,3.111139 c -0.04686,1.099529 -0.07543,2.576234 -0.07543,3.68605 0,47.111704 42.599295,112.24334 99.748468,122.43398 3.7089,1.24353 8.61563,2.37393 12.49485,3.35002 v 1.87216 C 74.532478,166.34735 22.132294,90.56217 28.235705,38.24428 Z"
|
||||
style="fill:#439639;stroke-width:1.14296"
|
||||
id="path26" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script /><xhtml:iframe
|
||||
class="cleanslate hidden"
|
||||
src="xmpp_files/commandline.html"
|
||||
id="cmdline_iframe"
|
||||
loading="lazy"
|
||||
style="height: 0px !important;" /></svg:svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -347,8 +347,9 @@ export default class ChatInput extends Component {
|
||||
>
|
||||
<${ContentEditable}
|
||||
id="message-input"
|
||||
aria-role="textbox"
|
||||
class="appearance-none block w-full bg-transparent text-sm text-gray-700 h-full focus:outline-none"
|
||||
placeholderText=${placeholderText}
|
||||
aria-placeholder=${placeholderText}
|
||||
innerRef=${this.formMessageInput}
|
||||
html=${inputHTML}
|
||||
disabled=${!inputEnabled}
|
||||
|
||||
@@ -33,7 +33,7 @@ function SocialIcon(props) {
|
||||
return html`
|
||||
<a class=${itemClass} target="_blank" rel="me" href=${url}>
|
||||
<span
|
||||
class="platform-icon rounded-lg bg-no-repeat"
|
||||
class="platform-icon bg-no-repeat"
|
||||
style=${style}
|
||||
title="Find me on ${name}"
|
||||
></span>
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
|
||||
/* If the div is empty then show the placeholder */
|
||||
#message-input:empty:before {
|
||||
content: attr(placeholderText);
|
||||
content: attr(aria-placeholder);
|
||||
pointer-events: none;
|
||||
position: absolute; /* Fixes firefox positioning caret on the right */
|
||||
display: block; /* For Firefox */
|
||||
|
||||
Reference in New Issue
Block a user