Skip to content

Commit

Permalink
Merge pull request #16 from EsefexBot/dev-api
Browse files Browse the repository at this point in the history
Update Websocket
  • Loading branch information
jokil123 authored Jan 28, 2024
2 parents 190a53a + 89d7e3f commit 19a807f
Show file tree
Hide file tree
Showing 20 changed files with 315 additions and 22 deletions.
17 changes: 11 additions & 6 deletions EsefexApi/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"esefexapi/api/middleware"
"esefexapi/api/routes"
"esefexapi/audioplayer"
"esefexapi/clientnotifiy"
"esefexapi/db"
"esefexapi/service"

Expand All @@ -22,19 +23,21 @@ type HttpApi struct {
handlers *routes.RouteHandlers
mw *middleware.Middleware
a audioplayer.IAudioPlayer
apiPort int
port int
cProto string
domain string
stop chan struct{}
ready chan struct{}
}

func NewHttpApi(dbs *db.Databases, plr audioplayer.IAudioPlayer, ds *discordgo.Session, apiPort int, cProto string) *HttpApi {
func NewHttpApi(dbs *db.Databases, plr audioplayer.IAudioPlayer, ds *discordgo.Session, apiPort int, cProto string, wsCN *clientnotifiy.WsClientNotifier, domain string) *HttpApi {
return &HttpApi{
handlers: routes.NewRouteHandlers(dbs, plr, ds, cProto),
handlers: routes.NewRouteHandlers(dbs, plr, ds, cProto, wsCN),
mw: middleware.NewMiddleware(dbs, ds),
a: plr,
apiPort: apiPort,
port: apiPort,
cProto: cProto,
domain: domain,
stop: make(chan struct{}, 1),
ready: make(chan struct{}),
}
Expand Down Expand Up @@ -67,10 +70,12 @@ func (api *HttpApi) run() {

router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./api/public/"))))

log.Printf("Webserver started on port %d (http://localhost:%d)\n", api.apiPort, api.apiPort)
router.Handle("/api/ws", cors(auth(h.GetWs()))).Methods("GET")

log.Printf("Webserver started on port %d (%s)\n", api.port, api.domain)

// nolint:errcheck
go http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", api.apiPort), router)
go http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", api.port), router)

close(api.ready)
<-api.stop
Expand Down
4 changes: 3 additions & 1 deletion EsefexApi/api/middleware/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ func (m *Middleware) Auth(next http.Handler) http.Handler {
return
}

userID := Ouser.Unwrap().ID

// Inject the user into the request context
ctx := context.WithValue(r.Context(), "user", Ouser)
ctx := context.WithValue(r.Context(), "user", userID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
2 changes: 1 addition & 1 deletion EsefexApi/api/public/simpleui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Esefex Simple UI</title>
<script src="https://unpkg.com/[email protected]"></script>
<!-- <script src="https://unpkg.com/[email protected]"></script> -->
<script src="./index.js" defer></script>
<link rel="stylesheet" href="./index.css">
</head>
Expand Down
14 changes: 14 additions & 0 deletions EsefexApi/api/public/simpleui/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
async function init() {
// create a websocket connection to the server
let socket = new WebSocket(`ws://${window.location.host}/api/ws`);
socket.onopen = () => {
console.log('websocket connection established');
};
socket.addEventListener('message', async (event) => {
if (event.data != 'update') {
return;
}

// reload the page
window.location.reload();
});

const soundsDiv = document.getElementById('sounds');

let guildRequest = await fetch('/api/guild', {
Expand Down
29 changes: 29 additions & 0 deletions EsefexApi/api/routes/getws.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package routes

import (
"esefexapi/types"
"log"
"net/http"

"github.com/gorilla/websocket"
)

var wsUpgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}

// api/ws
func (h *RouteHandlers) GetWs() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userID := r.Context().Value("user").(types.UserID)

conn, err := wsUpgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("Error upgrading websocket: %v", err)
return
}

h.wsCN.AddConnection(userID, conn)
})
}
5 changes: 4 additions & 1 deletion EsefexApi/api/routes/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package routes

import (
"esefexapi/audioplayer"
"esefexapi/clientnotifiy"
"esefexapi/db"

"github.com/bwmarrin/discordgo"
Expand All @@ -11,14 +12,16 @@ type RouteHandlers struct {
dbs *db.Databases
a audioplayer.IAudioPlayer
ds *discordgo.Session
wsCN *clientnotifiy.WsClientNotifier
cProto string
}

func NewRouteHandlers(dbs *db.Databases, a audioplayer.IAudioPlayer, ds *discordgo.Session, cProto string) *RouteHandlers {
func NewRouteHandlers(dbs *db.Databases, a audioplayer.IAudioPlayer, ds *discordgo.Session, cProto string, wsCN *clientnotifiy.WsClientNotifier) *RouteHandlers {
return &RouteHandlers{
a: a,
dbs: dbs,
ds: ds,
cProto: cProto,
wsCN: wsCN,
}
}
8 changes: 6 additions & 2 deletions EsefexApi/bot/bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package bot

import (
"esefexapi/bot/commands"
"esefexapi/clientnotifiy"
"esefexapi/db"
"esefexapi/service"

Expand All @@ -16,14 +17,16 @@ var _ service.IService = &DiscordBot{}
type DiscordBot struct {
ds *discordgo.Session
cmdh *commands.CommandHandlers
cn clientnotifiy.IClientNotifier
stop chan struct{}
ready chan struct{}
}

func NewDiscordBot(ds *discordgo.Session, dbs *db.Databases, domain string) *DiscordBot {
func NewDiscordBot(ds *discordgo.Session, dbs *db.Databases, domain string, cn clientnotifiy.IClientNotifier) *DiscordBot {
return &DiscordBot{
ds: ds,
cmdh: commands.NewCommandHandlers(ds, dbs, domain),
cmdh: commands.NewCommandHandlers(ds, dbs, domain, cn),
cn: cn,
stop: make(chan struct{}, 1),
ready: make(chan struct{}),
}
Expand All @@ -40,6 +43,7 @@ func (b *DiscordBot) run() {
ready := b.WaitReady()

b.cmdh.RegisterComandHandlers()
b.RegisterClientUpdateHandlers()

err := ds.Open()
if err != nil {
Expand Down
27 changes: 27 additions & 0 deletions EsefexApi/bot/clientupdate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package bot

import (
"esefexapi/types"
"log"

"github.com/bwmarrin/discordgo"
)

func (b *DiscordBot) RegisterClientUpdateHandlers() {
b.ds.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) {
err := b.cn.UpdateNotificationUsers()
if err != nil {
log.Printf("Error notifying clients: %+v", err)
}
})

b.ds.AddHandler(func(s *discordgo.Session, r *discordgo.VoiceStateUpdate) {
userID := types.UserID(r.UserID)

err := b.cn.UpdateNotificationUsers(userID)
if err != nil {
log.Printf("Error notifying clients: %+v", err)
}
})

}
8 changes: 5 additions & 3 deletions EsefexApi/bot/commands/cmdhashstore/cmdhashstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"esefexapi/util"
"io"
"os"

"github.com/pkg/errors"
)

type CommandHashStore interface {
type ICommandHashStore interface {
GetCommandHash() (string, error)
SetCommandHash(hash string) error
}
Expand Down Expand Up @@ -39,13 +41,13 @@ func (f *FileCmdHashStore) GetCommandHash() (string, error) {
func (f *FileCmdHashStore) SetCommandHash(hash string) error {
file, err := os.Create(f.FilePath)
if err != nil {
return err
return errors.Wrap(err, "error creating file")
}
defer file.Close()

_, err = file.WriteString(hash)
if err != nil {
return err
return errors.Wrap(err, "error writing to file")
}

return nil
Expand Down
5 changes: 4 additions & 1 deletion EsefexApi/bot/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"esefexapi/bot/commands/cmdhandler"
"esefexapi/bot/commands/middleware"
"esefexapi/clientnotifiy"
"esefexapi/db"
"fmt"
"log"
Expand All @@ -29,16 +30,18 @@ type CommandHandlers struct {
dbs *db.Databases
domain string
mw *middleware.CommandMiddleware
cn clientnotifiy.IClientNotifier
Commands map[string]*discordgo.ApplicationCommand
Handlers map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate)
}

func NewCommandHandlers(ds *discordgo.Session, dbs *db.Databases, domain string) *CommandHandlers {
func NewCommandHandlers(ds *discordgo.Session, dbs *db.Databases, domain string, cn clientnotifiy.IClientNotifier) *CommandHandlers {
c := &CommandHandlers{
ds: ds,
dbs: dbs,
domain: domain,
mw: middleware.NewCommandMiddleware(dbs),
cn: cn,
Commands: map[string]*discordgo.ApplicationCommand{},
Handlers: map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){},
}
Expand Down
3 changes: 3 additions & 0 deletions EsefexApi/bot/commands/sound.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ func (c *CommandHandlers) SoundUpload(s *discordgo.Session, i *discordgo.Interac
return nil, errors.Wrap(err, "Error adding sound")
}

guildID := types.GuildID(i.GuildID)
c.cn.UpdateNotificationGuilds(guildID)

log.Printf("Uploaded sound effect %v to guild %v", uid.SoundID, i.GuildID)
return &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Expand Down
124 changes: 124 additions & 0 deletions EsefexApi/clientnotifiy/clientnotifiy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package clientnotifiy

import (
"esefexapi/types"
"esefexapi/util/dcgoutil"

"github.com/bwmarrin/discordgo"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
)

type IClientNotifier interface {
// UpdateNotificationUsers notifies the clients that some data has been updated
// This should cause the client to refetch the data
// if users is empty, then all clients should be notified
// this function will handle the case where a user does not have any connections
UpdateNotificationUsers(users ...types.UserID) error
UpdateNotificationGuilds(guilds ...types.GuildID) error
UpdateNotificationChannels(channels ...types.ChannelID) error
}

var _ IClientNotifier = &WsClientNotifier{}

// implements ClientNotifier
type WsClientNotifier struct {
userConnections map[types.UserID][]*websocket.Conn
ds *discordgo.Session
stop chan struct{}
ready chan struct{}
}

func NewWsClientNotifier(ds *discordgo.Session) *WsClientNotifier {
return &WsClientNotifier{
userConnections: make(map[types.UserID][]*websocket.Conn),
ds: ds,
stop: make(chan struct{}),
ready: make(chan struct{}),
}
}

// UpdateNotificationChannels implements IClientNotifier.
func (w *WsClientNotifier) UpdateNotificationChannels(channels ...types.ChannelID) error {
for _, channel := range channels {
users, err := dcgoutil.ChannelUserIDs(w.ds, channel)
if err != nil {
return errors.Wrap(err, "error getting channel user ids")
}

err = w.UpdateNotificationUsers(users...)
if err != nil {
return errors.Wrap(err, "error updating notification")
}
}

return nil
}

// UpdateNotificationGuilds implements IClientNotifier.
func (w *WsClientNotifier) UpdateNotificationGuilds(guilds ...types.GuildID) error {
for _, guild := range guilds {
users, err := dcgoutil.GuildUserIDs(w.ds, guild)
if err != nil {
return errors.Wrap(err, "error getting channel user ids")
}

err = w.UpdateNotificationUsers(users...)
if err != nil {
return errors.Wrap(err, "error updating notification")
}
}

return nil
}

func (w *WsClientNotifier) UpdateNotificationUsers(users ...types.UserID) error {
if len(users) == 0 {
for k := range w.userConnections {
err := w.writeUpdate(k)
if err != nil {
return errors.Wrap(err, "error writing update")
}
}
return nil
}

for _, user := range users {
if _, ok := w.userConnections[user]; !ok {
continue
}

err := w.writeUpdate(user)
if err != nil {
return errors.Wrap(err, "error writing update")
}
}
return nil
}

func (w *WsClientNotifier) writeUpdate(user types.UserID) error {
var causedError error = nil

for _, conn := range w.userConnections[user] {
err := conn.WriteMessage(websocket.TextMessage, []byte("update"))
if err != nil {
conn.Close()
w.RemoveConnection(user, conn)
causedError = errors.Wrap(err, "error writing message to websocket, removing connection")
}
}
return causedError
}

func (w *WsClientNotifier) AddConnection(user types.UserID, conn *websocket.Conn) {
w.userConnections[user] = append(w.userConnections[user], conn)
}

func (w *WsClientNotifier) RemoveConnection(user types.UserID, conn *websocket.Conn) {
for i, c := range w.userConnections[user] {
if c == conn {
w.userConnections[user] = append(w.userConnections[user][:i], w.userConnections[user][i+1:]...)
break
}
}
}
Loading

0 comments on commit 19a807f

Please sign in to comment.