From e3fc322980369dc9a2391f3ea128a8697a8030d9 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 16 Jul 2023 18:05:04 -0600 Subject: [PATCH 1/3] presence --- commands/guild.go | 4 +- lib/presence.go | 152 ++++++++++++++++++++++++++++++++++++++++++++++ main.go | 17 ++++++ state/main.go | 6 +- 4 files changed, 174 insertions(+), 5 deletions(-) create mode 100644 lib/presence.go diff --git a/commands/guild.go b/commands/guild.go index 75b132b..97c182c 100644 --- a/commands/guild.go +++ b/commands/guild.go @@ -442,7 +442,7 @@ func SwitchGuild(session *discordgo.Session, input string) { ListChannelsCommand(session) ListUsersCommand(session) - // TODO: update presence + lib.UpdatePresence(session) } } } @@ -486,7 +486,7 @@ func SwitchChannelsCommand(session *discordgo.Session) { ListUsersCommand(session) - // TODO: update presence + lib.UpdatePresence(session) } } }) diff --git a/lib/presence.go b/lib/presence.go new file mode 100644 index 0000000..48409ac --- /dev/null +++ b/lib/presence.go @@ -0,0 +1,152 @@ +package lib + +import ( + "fmt" + "reflect" + "sync" + "time" + "unsafe" + + "github.com/Cynosphere/comcord/state" + "github.com/bwmarrin/discordgo" + "github.com/gorilla/websocket" +) + +type ActivityMetadata struct { + ButtonURLs []string `json:"button_urls,omitempty"` +} + +type Activity struct { + Name string `json:"name"` + Type discordgo.ActivityType `json:"type"` + URL string `json:"url,omitempty"` + CreatedAt time.Time `json:"created_at"` + ApplicationID string `json:"application_id,omitempty"` + State string `json:"state,omitempty"` + Details string `json:"details,omitempty"` + Timestamps discordgo.TimeStamps `json:"timestamps,omitempty"` + Emoji discordgo.Emoji `json:"emoji,omitempty"` + Party discordgo.Party `json:"party,omitempty"` + Assets discordgo.Assets `json:"assets,omitempty"` + Secrets discordgo.Secrets `json:"secrets,omitempty"` + Instance bool `json:"instance,omitempty"` + Flags int `json:"flags,omitempty"` + Buttons []string `json:"buttons,omitempty"` + Metadata ActivityMetadata `json:"metadata,omitempty"` +} + +type GatewayPresenceUpdate struct { + Since int `json:"since"` + Activities []Activity `json:"activities,omitempty"` + Status string `json:"status"` + AFK bool `json:"afk"` + Broadcast string `json:"broadcast,omitempty"` +} + +type presenceOp struct { + Op int `json:"op"` + Data GatewayPresenceUpdate `json:"d"` +} + +func getUnexportedField(field reflect.Value) interface{} { + return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface() +} + +func UpdatePresence(session *discordgo.Session) { + values := reflect.ValueOf(session) + fieldWsConn := reflect.Indirect(values).FieldByName("wsConn") + fieldWsMutex := reflect.Indirect(values).FieldByName("wsMutex") + + wsConn := getUnexportedField(fieldWsConn).(*websocket.Conn) + wsMutex := getUnexportedField(fieldWsMutex).(sync.Mutex) + + afk := state.IsAFK() + presence := GatewayPresenceUpdate{ + Since: 0, + AFK: afk, + Activities: make([]Activity, 0), + } + + currentGuild := state.GetCurrentGuild() + currentChannel := state.GetCurrentChannel() + + var activity Activity + + startTime := state.GetStartTime() + + if session.State.User.Bot { + activity = Activity{ + Type: 0, + Name: "comcord", + } + + if currentGuild != "" && currentChannel != "" { + guild, guildErr := session.State.Guild(currentGuild) + channel, channelErr := session.State.Channel(currentChannel) + + if guildErr == nil && channelErr == nil { + activity.Type = 3 + activity.Name = fmt.Sprintf("#%s in %s | comcord", channel.Name, guild.Name) + } + } + + if afk { + activity.Name = activity.Name + " [AFK]" + } + } else { + activity = Activity{ + Type: 0, + ApplicationID: "1026163285877325874", + Name: "comcord", + Timestamps: discordgo.TimeStamps{ + StartTimestamp: startTime.Unix(), + }, + Buttons: make([]string, 0), + Metadata: ActivityMetadata{ + ButtonURLs: make([]string, 0), + }, + } + + activity.Buttons = append(activity.Buttons, "comcord Repo") + activity.Metadata.ButtonURLs = append(activity.Metadata.ButtonURLs, "https://gitdab.com/Cynosphere/comcord") + + if currentGuild != "" && currentChannel != "" { + guild, guildErr := session.State.Guild(currentGuild) + channel, channelErr := session.State.Channel(currentChannel) + + if guildErr == nil && channelErr == nil { + activity.Details = fmt.Sprintf("#%s - %s", channel.Name, guild.Name) + + activity.Assets = discordgo.Assets{} + activity.Assets.LargeText = guild.Name + if guild.Icon != "" { + activity.Assets.LargeImageID = fmt.Sprintf("mp:icons/%s/%s.png?size=1024", guild.ID, guild.Icon) + } + } + } + + if afk { + activity.State = "AFK" + } + } + + activity.CreatedAt = startTime + + presence.Activities = append(presence.Activities, activity) + + defaultStatus := state.GetConfigValue("defaultStatus") + if defaultStatus != "" { + presence.Status = defaultStatus + } else { + if afk { + presence.Status = "idle" + } else { + presence.Status = "online" + } + } + + op := presenceOp{3, presence} + wsMutex.Lock() + wsConn.WriteJSON(op) + wsMutex.Unlock() +} diff --git a/main.go b/main.go index 0b8bdde..12f6a58 100644 --- a/main.go +++ b/main.go @@ -90,6 +90,23 @@ func main() { } } + status := "online" + defaultStatus := config["defaultStatus"] + if defaultStatus != "" { + status = defaultStatus + } + startTime := state.GetStartTime() + client.Identify.Presence = discordgo.GatewayStatusUpdate{ + Since: 0, + Status: status, + AFK: false, + Game: discordgo.Activity{ + Name: "comcord", + ApplicationID: "1026163285877325874", + CreatedAt: startTime, + }, + } + events.Setup(client) err = client.Open() diff --git a/state/main.go b/state/main.go index cd63ec8..c46d09d 100644 --- a/state/main.go +++ b/state/main.go @@ -10,7 +10,7 @@ type ComcordState struct { Config map[string]string Connected bool RPCConnected bool - StartTime int64 + StartTime time.Time CurrentGuild string CurrentChannel string NameLength int @@ -30,7 +30,7 @@ func Setup(config map[string]string) { state.Config = config state.Connected = true state.RPCConnected = false - state.StartTime = time.Now().Unix() + state.StartTime = time.Now() state.CurrentGuild = "" state.CurrentChannel = "" state.NameLength = 2 @@ -59,7 +59,7 @@ func SetRPCConnected(value bool) { state.RPCConnected = value } -func GetStartTime() int64 { +func GetStartTime() time.Time { return state.StartTime } From 1a667b43d4ba1016fb37d4901119a4f32107cea8 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 16 Jul 2023 20:36:46 -0600 Subject: [PATCH 2/3] partial user account support, fix presences for user accounts --- lib/presence.go | 19 +++++++++++++------ main.go | 19 +++++++++++++++---- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/lib/presence.go b/lib/presence.go index 48409ac..e057411 100644 --- a/lib/presence.go +++ b/lib/presence.go @@ -19,18 +19,18 @@ type ActivityMetadata struct { type Activity struct { Name string `json:"name"` Type discordgo.ActivityType `json:"type"` - URL string `json:"url,omitempty"` + //URL string `json:"url,omitempty"` CreatedAt time.Time `json:"created_at"` ApplicationID string `json:"application_id,omitempty"` State string `json:"state,omitempty"` Details string `json:"details,omitempty"` Timestamps discordgo.TimeStamps `json:"timestamps,omitempty"` - Emoji discordgo.Emoji `json:"emoji,omitempty"` - Party discordgo.Party `json:"party,omitempty"` + //Emoji discordgo.Emoji `json:"emoji,omitempty"` + //Party discordgo.Party `json:"party,omitempty"` Assets discordgo.Assets `json:"assets,omitempty"` - Secrets discordgo.Secrets `json:"secrets,omitempty"` - Instance bool `json:"instance,omitempty"` - Flags int `json:"flags,omitempty"` + //Secrets discordgo.Secrets `json:"secrets,omitempty"` + //Instance bool `json:"instance,omitempty"` + //Flags int `json:"flags,omitempty"` Buttons []string `json:"buttons,omitempty"` Metadata ActivityMetadata `json:"metadata,omitempty"` } @@ -53,6 +53,13 @@ func getUnexportedField(field reflect.Value) interface{} { } func UpdatePresence(session *discordgo.Session) { + // there is a way to send presence without reflecting to grab the websocket + // connection, but theres an issue with the serialization that because a value + // isn't being considered "null" that its trying to apply and failing because + // the default doesn't make sense in this context, even if omitempty is set + // + // this doesnt happen with bot accounts because they have certain fields + // stripped values := reflect.ValueOf(session) fieldWsConn := reflect.Indirect(values).FieldByName("wsConn") fieldWsMutex := reflect.Indirect(values).FieldByName("wsMutex") diff --git a/main.go b/main.go index 12f6a58..e8f4a7f 100644 --- a/main.go +++ b/main.go @@ -63,8 +63,15 @@ func main() { state.Setup(config) commands.Setup() - // TODO: user account support - client, err := discordgo.New("Bot " + token) + allowUserAccounts := config["allowUserAccounts"] == "true" + tokenPrefix := "Bot " + if allowUserAccounts { + tokenPrefix = "" + } + + fullToken := tokenPrefix + token + + client, err := discordgo.New(fullToken) if err != nil { fmt.Println("% Failed to create client:", err) fmt.Print("\r") @@ -72,7 +79,8 @@ func main() { return } - // TODO: dont set for user accounts(? never really tested if it matters) + //client.LogLevel = discordgo.LogDebug + client.Identify.Intents = discordgo.IntentsAll if config["useMobile"] == "true" { @@ -82,7 +90,8 @@ func main() { Device: "Pixel, raven", } } else { - // TODO: user account support + // TODO: figure out how tempermental X-Super-Properties is, as in if it + // allows arbitrary values or not client.Identify.Properties = discordgo.IdentifyProperties{ OS: runtime.GOOS, Browser: "comcord", @@ -96,11 +105,13 @@ func main() { status = defaultStatus } startTime := state.GetStartTime() + client.Identify.Presence = discordgo.GatewayStatusUpdate{ Since: 0, Status: status, AFK: false, Game: discordgo.Activity{ + Type: 0, Name: "comcord", ApplicationID: "1026163285877325874", CreatedAt: startTime, From 3d401d97516602d4e79f7e68c7b08d58ba286b8e Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 16 Jul 2023 22:27:50 -0600 Subject: [PATCH 3/3] generalized status type config key instead of useMobile --- main.go | 24 +++++++++++------------- state/main.go | 4 ++-- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/main.go b/main.go index e8f4a7f..8a98643 100644 --- a/main.go +++ b/main.go @@ -83,20 +83,18 @@ func main() { client.Identify.Intents = discordgo.IntentsAll - if config["useMobile"] == "true" { - client.Identify.Properties = discordgo.IdentifyProperties{ - OS: "Android", - Browser: "Discord Android", - Device: "Pixel, raven", - } + client.Identify.Properties = discordgo.IdentifyProperties{ + OS: runtime.GOOS, + } + statusType := config["statusType"] + if statusType == "mobile" { + client.Identify.Properties.Browser = "Discord Android" + } else if statusType == "embedded" { + client.Identify.Properties.Browser = "Discord Embedded" + } else if statusType == "desktop" { + client.Identify.Properties.Browser = "Discord Client" } else { - // TODO: figure out how tempermental X-Super-Properties is, as in if it - // allows arbitrary values or not - client.Identify.Properties = discordgo.IdentifyProperties{ - OS: runtime.GOOS, - Browser: "comcord", - Device: "comcord", - } + client.Identify.Properties.Browser = "comcord" } status := "online" diff --git a/state/main.go b/state/main.go index c46d09d..2949db8 100644 --- a/state/main.go +++ b/state/main.go @@ -1,9 +1,9 @@ package state import ( - "time" + "time" - "github.com/bwmarrin/discordgo" + "github.com/bwmarrin/discordgo" ) type ComcordState struct {