Blocklist improvements + WebSocket timeout tweak
This commit is contained in:
parent
4b971fcf41
commit
a1b0d2e965
|
@ -345,7 +345,7 @@ The `unmute` action does the opposite and removes the mute status:
|
||||||
|
|
||||||
## Block
|
## Block
|
||||||
|
|
||||||
Sent by: Client.
|
Sent by: Client, Server.
|
||||||
|
|
||||||
The block command places a hard block between the current user and the target.
|
The block command places a hard block between the current user and the target.
|
||||||
|
|
||||||
|
@ -364,6 +364,10 @@ When either user blocks the other:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The server may send a "block" message to the client in response to the BlockNow API endpoint: your main website can communicate that a block was just added, so if either user is currently in chat the block can apply immediately instead of at either user's next re-join of the room.
|
||||||
|
|
||||||
|
The server "block" message follows the same format, having the username of the other party.
|
||||||
|
|
||||||
## Blocklist
|
## Blocklist
|
||||||
|
|
||||||
Sent by: Client.
|
Sent by: Client.
|
||||||
|
|
113
pkg/api.go
113
pkg/api.go
|
@ -354,6 +354,119 @@ func (s *Server) BlockList() http.HandlerFunc {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BlockNow (/api/block/now) allows your website to add to a current online chatter's
|
||||||
|
// blocked list immediately.
|
||||||
|
//
|
||||||
|
// For example: the BlockList endpoint does a bulk sync of the blocklist at the time
|
||||||
|
// a user joins the chat room, but if users are already on chat when the blocking begins,
|
||||||
|
// it doesn't take effect until one or the other re-joins the room. This API endpoint
|
||||||
|
// can apply the blocking immediately to the currently online users.
|
||||||
|
//
|
||||||
|
// It is a POST request with a json body containing the following schema:
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "APIKey": "from settings.toml",
|
||||||
|
// "Usernames": [ "source", "target" ]
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The pair of usernames will be the two users who block one another (in any order).
|
||||||
|
// If any of the users are currently connected to the chat, they will all mutually
|
||||||
|
// block one another immediately.
|
||||||
|
func (s *Server) BlockNow() http.HandlerFunc {
|
||||||
|
type request struct {
|
||||||
|
APIKey string
|
||||||
|
Usernames []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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any of these users are online, and update their blocklist accordingly.
|
||||||
|
var changed bool
|
||||||
|
for _, username := range params.Usernames {
|
||||||
|
if sub, err := s.GetSubscriber(username); err == nil {
|
||||||
|
for _, otherName := range params.Usernames {
|
||||||
|
if username == otherName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Info("BlockNow API: %s is currently on chat, add block for %+v", username, otherName)
|
||||||
|
|
||||||
|
sub.muteMu.Lock()
|
||||||
|
sub.muted[otherName] = struct{}{}
|
||||||
|
sub.blocked[otherName] = struct{}{}
|
||||||
|
sub.muteMu.Unlock()
|
||||||
|
|
||||||
|
// Changes have been made to online users.
|
||||||
|
changed = true
|
||||||
|
|
||||||
|
// Send a server-side "block" command to the subscriber, so their front-end page might
|
||||||
|
// update the cachedBlocklist so there's no leakage in case of chat server rebooting.
|
||||||
|
sub.SendJSON(messages.Message{
|
||||||
|
Action: messages.ActionBlock,
|
||||||
|
Username: otherName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any changes to blocklists were made: send the Who List.
|
||||||
|
if changed {
|
||||||
|
s.SendWhoList()
|
||||||
|
}
|
||||||
|
|
||||||
|
enc.Encode(result{
|
||||||
|
OK: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Blocklist cache sent over from your website.
|
// Blocklist cache sent over from your website.
|
||||||
var (
|
var (
|
||||||
// Map of username to the list of usernames they block.
|
// Map of username to the list of usernames they block.
|
||||||
|
|
|
@ -179,6 +179,7 @@ func (s *Server) OnMessage(sub *Subscriber, msg messages.Message) {
|
||||||
// If the user is OP, just tell them we would.
|
// If the user is OP, just tell them we would.
|
||||||
if sub.IsAdmin() {
|
if sub.IsAdmin() {
|
||||||
sub.ChatServer("Your recent chat context would have been reported to your main website.")
|
sub.ChatServer("Your recent chat context would have been reported to your main website.")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the report to the main website.
|
// Send the report to the main website.
|
||||||
|
@ -483,7 +484,7 @@ func (s *Server) OnBlock(sub *Subscriber, msg messages.Message) {
|
||||||
|
|
||||||
// OnBlocklist is a bulk user mute from the CachedBlocklist sent by the website.
|
// OnBlocklist is a bulk user mute from the CachedBlocklist sent by the website.
|
||||||
func (s *Server) OnBlocklist(sub *Subscriber, msg messages.Message) {
|
func (s *Server) OnBlocklist(sub *Subscriber, msg messages.Message) {
|
||||||
log.Info("%s syncs their blocklist: %s", sub.Username, msg.Usernames)
|
log.Info("[%s] syncs their blocklist: %s", sub.Username, msg.Usernames)
|
||||||
|
|
||||||
sub.muteMu.Lock()
|
sub.muteMu.Lock()
|
||||||
for _, username := range msg.Usernames {
|
for _, username := range msg.Usernames {
|
||||||
|
|
|
@ -28,6 +28,13 @@ func (s *Server) filterMessage(sub *Subscriber, rawMsg messages.Message, msg *me
|
||||||
if strings.HasPrefix(msg.Channel, "@") {
|
if strings.HasPrefix(msg.Channel, "@") {
|
||||||
// DM
|
// DM
|
||||||
pushDirectMessageContext(sub, sub.Username, msg.Channel[1:], rawMsg)
|
pushDirectMessageContext(sub, sub.Username, msg.Channel[1:], rawMsg)
|
||||||
|
|
||||||
|
// If either party is an admin user, waive filtering this DM chat.
|
||||||
|
if sub.IsAdmin() {
|
||||||
|
return nil, false
|
||||||
|
} else if other, err := s.GetSubscriber(msg.Channel[1:]); err == nil && other.IsAdmin() {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Public channel
|
// Public channel
|
||||||
pushMessageContext(sub, msg.Channel, rawMsg)
|
pushMessageContext(sub, msg.Channel, rawMsg)
|
||||||
|
|
|
@ -36,6 +36,7 @@ func (s *Server) Setup() error {
|
||||||
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("/api/blocklist", s.BlockList())
|
||||||
|
mux.Handle("/api/block/now", s.BlockNow())
|
||||||
mux.Handle("/api/authenticate", s.Authenticate())
|
mux.Handle("/api/authenticate", s.Authenticate())
|
||||||
mux.Handle("/api/shutdown", s.ShutdownAPI())
|
mux.Handle("/api/shutdown", s.ShutdownAPI())
|
||||||
mux.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("dist/assets"))))
|
mux.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("dist/assets"))))
|
||||||
|
|
|
@ -218,7 +218,7 @@ func (s *Server) WebSocket() http.HandlerFunc {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case msg := <-sub.messages:
|
case msg := <-sub.messages:
|
||||||
err = writeTimeout(ctx, time.Second*5, c, msg)
|
err = writeTimeout(ctx, time.Second*15, c, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
25
src/App.vue
25
src/App.vue
|
@ -957,6 +957,21 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Server side "block" event: for when the main website sends a BlockNow API request.
|
||||||
|
onBlock(msg) {
|
||||||
|
// Close any video connections we had with this user.
|
||||||
|
this.closeVideo(msg.username);
|
||||||
|
|
||||||
|
// Add it to our CachedBlocklist so in case the server reboots, we continue to sync it on reconnect.
|
||||||
|
for (let existing of this.config.CachedBlocklist) {
|
||||||
|
if (existing === msg.username) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.config.CachedBlocklist.push(msg.username);
|
||||||
|
},
|
||||||
|
|
||||||
// Mute or unmute a user.
|
// Mute or unmute a user.
|
||||||
muteUser(username) {
|
muteUser(username) {
|
||||||
username = this.normalizeUsername(username);
|
username = this.normalizeUsername(username);
|
||||||
|
@ -1184,6 +1199,11 @@ export default {
|
||||||
this.ws.connected = true;
|
this.ws.connected = true;
|
||||||
this.ChatClient("Websocket connected!");
|
this.ChatClient("Websocket connected!");
|
||||||
|
|
||||||
|
// Upload our blocklist to the server before login. This resolves a bug where if a block
|
||||||
|
// was added recently (other user still online in chat), that user would briefly see your
|
||||||
|
// "has entered the room" message followed by you immediately not being online.
|
||||||
|
this.bulkMuteUsers();
|
||||||
|
|
||||||
// Tell the server our username.
|
// Tell the server our username.
|
||||||
this.ws.conn.send(JSON.stringify({
|
this.ws.conn.send(JSON.stringify({
|
||||||
action: "login",
|
action: "login",
|
||||||
|
@ -1249,6 +1269,9 @@ export default {
|
||||||
case "unwatch":
|
case "unwatch":
|
||||||
this.onUnwatch(msg);
|
this.onUnwatch(msg);
|
||||||
break;
|
break;
|
||||||
|
case "block":
|
||||||
|
this.onBlock(msg);
|
||||||
|
break;
|
||||||
case "error":
|
case "error":
|
||||||
this.pushHistory({
|
this.pushHistory({
|
||||||
channel: msg.channel,
|
channel: msg.channel,
|
||||||
|
@ -3733,7 +3756,7 @@ export default {
|
||||||
label on the chat history panel -->
|
label on the chat history panel -->
|
||||||
<div class="dropdown-content p-0">
|
<div class="dropdown-content p-0">
|
||||||
<EmojiPicker
|
<EmojiPicker
|
||||||
:native="false"
|
:native="true"
|
||||||
:display-recent="true"
|
:display-recent="true"
|
||||||
:disable-skin-tones="true"
|
:disable-skin-tones="true"
|
||||||
theme="auto"
|
theme="auto"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user