Cached Blocklist from your website
* New API endpoint: /api/blocklist where your site can pre-deliver muted username lists for users before they enter the chat. * Image sharing in DMs is allowed if either party is an operator.
This commit is contained in:
parent
84da298c12
commit
029f25029d
20
Protocol.md
20
Protocol.md
|
@ -343,6 +343,26 @@ The `unmute` action does the opposite and removes the mute status:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Blocklist
|
||||||
|
|
||||||
|
Sent by: Client.
|
||||||
|
|
||||||
|
The blocklist command is basically a bulk mute for (potentially) many usernames at once.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Client blocklist
|
||||||
|
{
|
||||||
|
"action": "blocklist",
|
||||||
|
"usernames": [ "target1", "target2", "target3" ]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
How this works: if you have an existing website and use JWT authentication to sign users into chat, your site can pre-emptively sync the user's block list **before** the user enters the room, using the `/api/blocklist` endpoint (see the README.md for BareRTC).
|
||||||
|
|
||||||
|
The chat server holds onto blocklists temporarily in memory: when that user loads the chat room (with a JWT token!), the front-end page receives the cached blocklist. As part of the "on connected" handler, the chat page sends the `blocklist` command over WebSocket to perform a mass mute on these users in one go.
|
||||||
|
|
||||||
|
The reason for this workflow is in case the chat server is rebooted _while_ the user is in the room. The cached blocklist pushed by your website is forgotten by the chat server back-end, but the client's page was still open with the cached blocklist already, and it will send the `blocklist` command to the server when it reconnects, eliminating any gaps.
|
||||||
|
|
||||||
## Boot
|
## Boot
|
||||||
|
|
||||||
Sent by: Client.
|
Sent by: Client.
|
||||||
|
|
36
README.md
36
README.md
|
@ -230,6 +230,42 @@ Returns basic info about the count and usernames of connected chatters:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
* `POST /api/blocklist`
|
||||||
|
|
||||||
|
Your server may pre-cache the user's blocklist for them **before** they
|
||||||
|
enter the chat room. Your site will use the `AdminAPIKey` parameter that
|
||||||
|
matches the setting in BareRTC's settings.toml (by default, a random UUID
|
||||||
|
is generated the first time).
|
||||||
|
|
||||||
|
The request payload coming from your site will be an application/json
|
||||||
|
post body like:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
APIKey: "from your settings.toml",
|
||||||
|
Username: "soandso",
|
||||||
|
Blocklist: [ "usernames", "that", "they", "block" ],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The server holds onto these in memory and when that user enters the chat
|
||||||
|
room (**JWT authentication only**) the front-end page will embed their
|
||||||
|
cached blocklist. When they connect to the WebSocket server, they send a
|
||||||
|
`blocklist` message to push their blocklist to the server -- it is
|
||||||
|
basically a bulk `mute` action that mutes all these users pre-emptively:
|
||||||
|
the user will not see their chat messages and the muted users can not see
|
||||||
|
the user's webcam when they broadcast later, the same as a regular `mute`
|
||||||
|
action.
|
||||||
|
|
||||||
|
The JSON response to this endpoint may look like:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"OK": true,
|
||||||
|
"Error": "if error, or this key is omitted if OK"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
# Tour of the Codebase
|
# Tour of the Codebase
|
||||||
|
|
||||||
This app uses WebSockets and WebRTC at the very simplest levels, without using a framework like `Socket.io`. Here is a tour of the codebase with the more interesting modules listed first.
|
This app uses WebSockets and WebRTC at the very simplest levels, without using a framework like `Socket.io`. Here is a tour of the codebase with the more interesting modules listed first.
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -7,6 +7,7 @@ require (
|
||||||
github.com/BurntSushi/toml v1.2.1
|
github.com/BurntSushi/toml v1.2.1
|
||||||
github.com/edwvee/exiffix v0.0.0-20210922235313-0f6cbda5e58f
|
github.com/edwvee/exiffix v0.0.0-20210922235313-0f6cbda5e58f
|
||||||
github.com/golang-jwt/jwt/v4 v4.4.3
|
github.com/golang-jwt/jwt/v4 v4.4.3
|
||||||
|
github.com/google/uuid v1.3.0
|
||||||
github.com/mattn/go-shellwords v1.0.12
|
github.com/mattn/go-shellwords v1.0.12
|
||||||
github.com/microcosm-cc/bluemonday v1.0.22
|
github.com/microcosm-cc/bluemonday v1.0.22
|
||||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20210228213109-c3a9aa474629
|
github.com/shurcooL/github_flavored_markdown v0.0.0-20210228213109-c3a9aa474629
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -36,6 +36,8 @@ github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgj
|
||||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||||
|
|
117
pkg/api.go
117
pkg/api.go
|
@ -3,8 +3,11 @@ package barertc
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"git.kirsle.net/apps/barertc/pkg/config"
|
"git.kirsle.net/apps/barertc/pkg/config"
|
||||||
|
"git.kirsle.net/apps/barertc/pkg/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Statistics (/api/statistics) returns info about the users currently logged onto the chat,
|
// Statistics (/api/statistics) returns info about the users currently logged onto the chat,
|
||||||
|
@ -65,3 +68,117 @@ func (s *Server) Statistics() http.HandlerFunc {
|
||||||
enc.Encode(result)
|
enc.Encode(result)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BlockList (/api/blocklist) allows your website to pre-sync mute lists between your
|
||||||
|
// user accounts, so that when they see each other in chat they will pre-emptively mute
|
||||||
|
// or boot one another.
|
||||||
|
//
|
||||||
|
// It is a POST request with a json body containing the following schema:
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "APIKey": "from settings.toml",
|
||||||
|
// "Username": "soandso",
|
||||||
|
// "Blocklist": [ "list", "of", "other", "usernames" ],
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The chat server will remember these mappings (until rebooted). How they are
|
||||||
|
// used is that the blocklist is embedded in the front-end page when the username
|
||||||
|
// signs in later. As part of the On Connect handler, the front-end will send the
|
||||||
|
// list of usernames in a bulk `mute` command to the server. This way even if the
|
||||||
|
// chat server reboots while the user is connected, when it comes back up and the user
|
||||||
|
// reconnects they will retransmit their block list.
|
||||||
|
func (s *Server) BlockList() http.HandlerFunc {
|
||||||
|
type request struct {
|
||||||
|
APIKey string
|
||||||
|
Username string
|
||||||
|
Blocklist []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type result struct {
|
||||||
|
OK bool
|
||||||
|
Error string `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// JSON writer for the response.
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
|
||||||
|
// Parse the request.
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
enc.Encode(result{
|
||||||
|
Error: "Only POST methods allowed",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
} else if r.Header.Get("Content-Type") != "application/json" {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
enc.Encode(result{
|
||||||
|
Error: "Only application/json content-types allowed",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
// Parse the request payload.
|
||||||
|
var (
|
||||||
|
params request
|
||||||
|
dec = json.NewDecoder(r.Body)
|
||||||
|
)
|
||||||
|
if err := dec.Decode(¶ms); err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
enc.Encode(result{
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the API key.
|
||||||
|
if params.APIKey != config.Current.AdminAPIKey {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
enc.Encode(result{
|
||||||
|
Error: "Authentication denied.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the cached blocklist.
|
||||||
|
SetCachedBlocklist(params.Username, params.Blocklist)
|
||||||
|
enc.Encode(result{
|
||||||
|
OK: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blocklist cache sent over from your website.
|
||||||
|
var (
|
||||||
|
// Map of username to the list of usernames they block.
|
||||||
|
cachedBlocklist map[string][]string
|
||||||
|
cachedBlocklistMu sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cachedBlocklist = map[string][]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCachedBlocklist returns the blocklist for a username.
|
||||||
|
func GetCachedBlocklist(username string) []string {
|
||||||
|
cachedBlocklistMu.RLock()
|
||||||
|
defer cachedBlocklistMu.RUnlock()
|
||||||
|
if list, ok := cachedBlocklist[username]; ok {
|
||||||
|
log.Debug("GetCachedBlocklist(%s) blocks %s", username, list)
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
log.Debug("GetCachedBlocklist(%s): no blocklist stored", username)
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCachedBlocklist sets the blocklist cache for a user.
|
||||||
|
func SetCachedBlocklist(username string, blocklist []string) {
|
||||||
|
log.Info("SetCachedBlocklist: %s mutes users %s", username, strings.Join(blocklist, ", "))
|
||||||
|
cachedBlocklistMu.Lock()
|
||||||
|
defer cachedBlocklistMu.Unlock()
|
||||||
|
cachedBlocklist[username] = blocklist
|
||||||
|
}
|
||||||
|
|
|
@ -8,11 +8,12 @@ import (
|
||||||
|
|
||||||
"git.kirsle.net/apps/barertc/pkg/log"
|
"git.kirsle.net/apps/barertc/pkg/log"
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Version of the config format - when new fields are added, it will attempt
|
// Version of the config format - when new fields are added, it will attempt
|
||||||
// to write the settings.toml to disk so new defaults populate.
|
// to write the settings.toml to disk so new defaults populate.
|
||||||
var currentVersion = 4
|
var currentVersion = 5
|
||||||
|
|
||||||
// Config for your BareRTC app.
|
// Config for your BareRTC app.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
@ -29,8 +30,9 @@ type Config struct {
|
||||||
Branding string
|
Branding string
|
||||||
WebsiteURL string
|
WebsiteURL string
|
||||||
|
|
||||||
CORSHosts []string
|
CORSHosts []string
|
||||||
PermitNSFW bool
|
AdminAPIKey string
|
||||||
|
PermitNSFW bool
|
||||||
|
|
||||||
UseXForwardedFor bool
|
UseXForwardedFor bool
|
||||||
|
|
||||||
|
@ -72,9 +74,10 @@ var Current = DefaultConfig()
|
||||||
// settings.toml file to disk.
|
// settings.toml file to disk.
|
||||||
func DefaultConfig() Config {
|
func DefaultConfig() Config {
|
||||||
var c = Config{
|
var c = Config{
|
||||||
Title: "BareRTC",
|
Title: "BareRTC",
|
||||||
Branding: "BareRTC",
|
Branding: "BareRTC",
|
||||||
WebsiteURL: "https://www.example.com",
|
WebsiteURL: "https://www.example.com",
|
||||||
|
AdminAPIKey: uuid.New().String(),
|
||||||
CORSHosts: []string{
|
CORSHosts: []string{
|
||||||
"https://www.example.com",
|
"https://www.example.com",
|
||||||
},
|
},
|
||||||
|
|
|
@ -374,6 +374,21 @@ func (s *Server) OnMute(sub *Subscriber, msg Message, mute bool) {
|
||||||
s.SendWhoList()
|
s.SendWhoList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnBlocklist is a bulk user mute from the CachedBlocklist sent by the website.
|
||||||
|
func (s *Server) OnBlocklist(sub *Subscriber, msg Message) {
|
||||||
|
log.Info("%s syncs their blocklist: %s", sub.Username, msg.Usernames)
|
||||||
|
|
||||||
|
sub.muteMu.Lock()
|
||||||
|
for _, username := range msg.Usernames {
|
||||||
|
sub.muted[username] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub.muteMu.Unlock()
|
||||||
|
|
||||||
|
// Send the Who List in case our cam will show as disabled to the muted party.
|
||||||
|
s.SendWhoList()
|
||||||
|
}
|
||||||
|
|
||||||
// OnCandidate handles WebRTC candidate signaling.
|
// OnCandidate handles WebRTC candidate signaling.
|
||||||
func (s *Server) OnCandidate(sub *Subscriber, msg Message) {
|
func (s *Server) OnCandidate(sub *Subscriber, msg Message) {
|
||||||
// Look up the other subscriber.
|
// Look up the other subscriber.
|
||||||
|
|
|
@ -35,6 +35,9 @@ type Message struct {
|
||||||
// Send on `file` actions, passing e.g. image data.
|
// Send on `file` actions, passing e.g. image data.
|
||||||
Bytes []byte `json:"bytes,omitempty"`
|
Bytes []byte `json:"bytes,omitempty"`
|
||||||
|
|
||||||
|
// Send on `blocklist` actions, for doing a `mute` on a list of users
|
||||||
|
Usernames []string `json:"usernames,omitempty"`
|
||||||
|
|
||||||
// WebRTC negotiation messages: proxy their signaling messages
|
// WebRTC negotiation messages: proxy their signaling messages
|
||||||
// between the two users to negotiate peer connection.
|
// between the two users to negotiate peer connection.
|
||||||
Candidate string `json:"candidate,omitempty"` // candidate
|
Candidate string `json:"candidate,omitempty"` // candidate
|
||||||
|
@ -43,10 +46,11 @@ type Message struct {
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Actions sent by the client side only
|
// Actions sent by the client side only
|
||||||
ActionLogin = "login" // post the username to backend
|
ActionLogin = "login" // post the username to backend
|
||||||
ActionBoot = "boot" // boot a user off your video feed
|
ActionBoot = "boot" // boot a user off your video feed
|
||||||
ActionMute = "mute" // mute a user's chat messages
|
ActionMute = "mute" // mute a user's chat messages
|
||||||
ActionUnmute = "unmute"
|
ActionUnmute = "unmute"
|
||||||
|
ActionBlocklist = "blocklist" // mute in bulk for usernames
|
||||||
|
|
||||||
// Actions sent by server or client
|
// Actions sent by server or client
|
||||||
ActionMessage = "message" // post a message to the room
|
ActionMessage = "message" // post a message to the room
|
||||||
|
|
11
pkg/pages.go
11
pkg/pages.go
|
@ -20,9 +20,10 @@ func IndexPage() http.HandlerFunc {
|
||||||
|
|
||||||
// Handle a JWT authentication token.
|
// Handle a JWT authentication token.
|
||||||
var (
|
var (
|
||||||
tokenStr = r.FormValue("jwt")
|
tokenStr = r.FormValue("jwt")
|
||||||
claims = &jwt.Claims{}
|
claims = &jwt.Claims{}
|
||||||
authOK bool
|
authOK bool
|
||||||
|
blocklist = []string{} // cached blocklist from your website, for JWT auth only
|
||||||
)
|
)
|
||||||
if tokenStr != "" {
|
if tokenStr != "" {
|
||||||
parsed, ok, err := jwt.ParseAndValidate(tokenStr)
|
parsed, ok, err := jwt.ParseAndValidate(tokenStr)
|
||||||
|
@ -36,6 +37,7 @@ func IndexPage() http.HandlerFunc {
|
||||||
|
|
||||||
authOK = ok
|
authOK = ok
|
||||||
claims = parsed
|
claims = parsed
|
||||||
|
blocklist = GetCachedBlocklist(claims.Subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are we enforcing strict JWT authentication?
|
// Are we enforcing strict JWT authentication?
|
||||||
|
@ -66,6 +68,9 @@ func IndexPage() http.HandlerFunc {
|
||||||
"JWTTokenString": tokenStr,
|
"JWTTokenString": tokenStr,
|
||||||
"JWTAuthOK": authOK,
|
"JWTAuthOK": authOK,
|
||||||
"JWTClaims": claims,
|
"JWTClaims": claims,
|
||||||
|
|
||||||
|
// Cached user blocklist sent by your website.
|
||||||
|
"CachedBlocklist": blocklist,
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpl.Funcs(template.FuncMap{
|
tmpl.Funcs(template.FuncMap{
|
||||||
|
|
|
@ -34,6 +34,7 @@ func (s *Server) Setup() error {
|
||||||
mux.Handle("/about", AboutPage())
|
mux.Handle("/about", AboutPage())
|
||||||
mux.Handle("/ws", s.WebSocket())
|
mux.Handle("/ws", s.WebSocket())
|
||||||
mux.Handle("/api/statistics", s.Statistics())
|
mux.Handle("/api/statistics", s.Statistics())
|
||||||
|
mux.Handle("/api/blocklist", s.BlockList())
|
||||||
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static"))))
|
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static"))))
|
||||||
|
|
||||||
s.mux = mux
|
s.mux = mux
|
||||||
|
|
|
@ -95,6 +95,8 @@ func (sub *Subscriber) ReadLoop(s *Server) {
|
||||||
s.OnBoot(sub, msg)
|
s.OnBoot(sub, msg)
|
||||||
case ActionMute, ActionUnmute:
|
case ActionMute, ActionUnmute:
|
||||||
s.OnMute(sub, msg, msg.Action == ActionMute)
|
s.OnMute(sub, msg, msg.Action == ActionMute)
|
||||||
|
case ActionBlocklist:
|
||||||
|
s.OnBlocklist(sub, msg)
|
||||||
case ActionCandidate:
|
case ActionCandidate:
|
||||||
s.OnCandidate(sub, msg)
|
s.OnCandidate(sub, msg)
|
||||||
case ActionSDP:
|
case ActionSDP:
|
||||||
|
|
|
@ -72,7 +72,10 @@ const app = Vue.createApp({
|
||||||
['😋', '⭐', '😇', '😴', '😱', '👀', '🎃'],
|
['😋', '⭐', '😇', '😴', '😱', '👀', '🎃'],
|
||||||
['🤮', '🥳', '🙏', '🤦', '💩', '🤯', '💯'],
|
['🤮', '🥳', '🙏', '🤦', '💩', '🤯', '💯'],
|
||||||
['😏', '🙈', '🙉', '🙊', '☀️', '🌈', '🎂'],
|
['😏', '🙈', '🙉', '🙊', '☀️', '🌈', '🎂'],
|
||||||
]
|
],
|
||||||
|
|
||||||
|
// Cached blocklist for the current user sent by your website.
|
||||||
|
CachedBlocklist: CachedBlocklist,
|
||||||
},
|
},
|
||||||
|
|
||||||
// User JWT settings if available.
|
// User JWT settings if available.
|
||||||
|
@ -365,6 +368,27 @@ const app = Vue.createApp({
|
||||||
// Is the current channel a DM?
|
// Is the current channel a DM?
|
||||||
return this.channel.indexOf("@") === 0;
|
return this.channel.indexOf("@") === 0;
|
||||||
},
|
},
|
||||||
|
canUploadFile() {
|
||||||
|
// Public channels: OK
|
||||||
|
if (!this.channel.indexOf('@') === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// User is an admin?
|
||||||
|
if (this.jwt.claims.op) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// User is in a DM thread with an admin?
|
||||||
|
if (this.isDM) {
|
||||||
|
let partner = this.normalizeUsername(this.channel);
|
||||||
|
if (this.whoMap[partner] != undefined && this.whoMap[partner].op) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !this.isDM;
|
||||||
|
},
|
||||||
isOp() {
|
isOp() {
|
||||||
// Returns if the current user has operator rights
|
// Returns if the current user has operator rights
|
||||||
return this.jwt.claims.op;
|
return this.jwt.claims.op;
|
||||||
|
@ -507,6 +531,11 @@ const app = Vue.createApp({
|
||||||
if (myNSFW != theirNSFW) {
|
if (myNSFW != theirNSFW) {
|
||||||
this.webcam.nsfw = theirNSFW;
|
this.webcam.nsfw = theirNSFW;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: Me events only come when we join the server or a moderator has
|
||||||
|
// flagged our video. This is as good of an "on connected" handler as we
|
||||||
|
// get, so push over our cached blocklist from the website now.
|
||||||
|
this.bulkMuteUsers();
|
||||||
},
|
},
|
||||||
|
|
||||||
// WhoList updates.
|
// WhoList updates.
|
||||||
|
@ -575,6 +604,27 @@ const app = Vue.createApp({
|
||||||
isMutedUser(username) {
|
isMutedUser(username) {
|
||||||
return this.muted[this.normalizeUsername(username)] != undefined;
|
return this.muted[this.normalizeUsername(username)] != undefined;
|
||||||
},
|
},
|
||||||
|
bulkMuteUsers() {
|
||||||
|
// On page load, if the website sent you a CachedBlocklist, mute all
|
||||||
|
// of these users in bulk when the server connects.
|
||||||
|
// this.ChatClient("BulkMuteUsers: sending our blocklist " + this.config.CachedBlocklist);
|
||||||
|
|
||||||
|
if (this.config.CachedBlocklist.length === 0) {
|
||||||
|
return; // nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the client side mute.
|
||||||
|
let blocklist = this.config.CachedBlocklist;
|
||||||
|
for (let username of blocklist) {
|
||||||
|
this.muted[username] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the username list to the server.
|
||||||
|
this.ws.conn.send(JSON.stringify({
|
||||||
|
action: "blocklist",
|
||||||
|
usernames: blocklist,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
|
||||||
// Send a video request to access a user's camera.
|
// Send a video request to access a user's camera.
|
||||||
sendOpen(username) {
|
sendOpen(username) {
|
||||||
|
@ -1760,10 +1810,6 @@ const app = Vue.createApp({
|
||||||
|
|
||||||
// The image upload button handler.
|
// The image upload button handler.
|
||||||
uploadFile() {
|
uploadFile() {
|
||||||
if (this.isDM) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let input = document.createElement('input');
|
let input = document.createElement('input');
|
||||||
input.type = 'file';
|
input.type = 'file';
|
||||||
input.accept = 'image/*';
|
input.accept = 'image/*';
|
||||||
|
|
|
@ -924,7 +924,7 @@
|
||||||
|
|
||||||
<div class="columns is-mobile">
|
<div class="columns is-mobile">
|
||||||
<div class="column"
|
<div class="column"
|
||||||
:class="{'pr-1': !isDM}">
|
:class="{'pr-1': canUploadFile}">
|
||||||
<form @submit.prevent="sendMessage()">
|
<form @submit.prevent="sendMessage()">
|
||||||
<input type="text" class="input"
|
<input type="text" class="input"
|
||||||
v-model="message"
|
v-model="message"
|
||||||
|
@ -933,7 +933,7 @@
|
||||||
:disabled="!ws.connected">
|
:disabled="!ws.connected">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="column pl-1 is-narrow" v-if="!isDM">
|
<div class="column pl-1 is-narrow" v-if="canUploadFile">
|
||||||
<button type="button" class="button"
|
<button type="button" class="button"
|
||||||
@click="uploadFile()">
|
@click="uploadFile()">
|
||||||
<i class="fa fa-image"></i>
|
<i class="fa fa-image"></i>
|
||||||
|
@ -1110,6 +1110,7 @@ const TURN = {{.Config.TURN}};
|
||||||
const UserJWTToken = {{.JWTTokenString}};
|
const UserJWTToken = {{.JWTTokenString}};
|
||||||
const UserJWTValid = {{if .JWTAuthOK}}true{{else}}false{{end}};
|
const UserJWTValid = {{if .JWTAuthOK}}true{{else}}false{{end}};
|
||||||
const UserJWTClaims = {{.JWTClaims.ToJSON}};
|
const UserJWTClaims = {{.JWTClaims.ToJSON}};
|
||||||
|
const CachedBlocklist = {{.CachedBlocklist}};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="/static/js/vue-3.2.45.js"></script>
|
<script src="/static/js/vue-3.2.45.js"></script>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user