node build fixed

This commit is contained in:
ra_ma
2025-09-20 14:08:38 +01:00
parent c6ebbe069d
commit 3d298fa434
1516 changed files with 535727 additions and 2 deletions

View File

@@ -0,0 +1 @@
Full credit to [thelennylord/discord-rpc](https://github.com/thelennylord/discord-rpc/)

View File

@@ -0,0 +1,101 @@
package discordrpc_client
import (
"os"
"strconv"
"time"
"github.com/google/uuid"
)
// Activity holds the data for discord rich presence
//
// See https://discord.com/developers/docs/game-sdk/activities#data-models-activity-struct
type Activity struct {
Name string `json:"name,omitempty"`
Details string `json:"details,omitempty"`
DetailsURL string `json:"details_url,omitempty"` // URL to details
State string `json:"state,omitempty"`
StateURL string `json:"state_url,omitempty"` // URL to state
Timestamps *Timestamps `json:"timestamps,omitempty"`
Assets *Assets `json:"assets,omitempty"`
Party *Party `json:"party,omitempty"`
Secrets *Secrets `json:"secrets,omitempty"`
Buttons []*Button `json:"buttons,omitempty"`
Instance bool `json:"instance"`
Type int `json:"type"`
StatusDisplayType int `json:"status_display_type,omitempty"` // 1 = name, 2 = details, 3 = state
}
// Timestamps holds unix timestamps for start and/or end of the game
//
// See https://discord.com/developers/docs/game-sdk/activities#data-models-activitytimestamps-struct
type Timestamps struct {
Start *Epoch `json:"start,omitempty"`
End *Epoch `json:"end,omitempty"`
}
// Epoch wrapper around time.Time to ensure times are sent as a unix epoch int
type Epoch struct{ time.Time }
// MarshalJSON converts time.Time to unix time int
func (t Epoch) MarshalJSON() ([]byte, error) {
return []byte(strconv.FormatInt(t.Unix(), 10)), nil
}
// Assets passes image references for inclusion in rich presence
//
// See https://discord.com/developers/docs/game-sdk/activities#data-models-activityassets-struct
type Assets struct {
LargeImage string `json:"large_image,omitempty"`
LargeText string `json:"large_text,omitempty"`
LargeURL string `json:"large_url,omitempty"` // URL to large image, if any
SmallImage string `json:"small_image,omitempty"`
SmallText string `json:"small_text,omitempty"`
SmallURL string `json:"small_url,omitempty"` // URL to small image, if any
}
// Party holds information for the current party of the player
type Party struct {
ID string `json:"id"`
Size []int `json:"size"` // seems to be element [0] is count and [1] is max
}
// Secrets holds secrets for Rich Presence joining and spectating
type Secrets struct {
Join string `json:"join,omitempty"`
Spectate string `json:"spectate,omitempty"`
Match string `json:"match,omitempty"`
}
type Button struct {
Label string `json:"label,omitempty"`
Url string `json:"url,omitempty"`
}
// SetActivity sets the Rich Presence activity for the running application
func (c *Client) SetActivity(activity Activity) error {
payload := Payload{
Cmd: SetActivityCommand,
Args: Args{
Pid: os.Getpid(),
Activity: &activity,
},
Nonce: uuid.New(),
}
return c.SendPayload(payload)
}
func (c *Client) CancelActivity() error {
payload := Payload{
Cmd: SetActivityCommand,
Args: Args{
Pid: os.Getpid(),
Activity: nil,
},
Nonce: uuid.New(),
}
return c.SendPayload(payload)
}

View File

@@ -0,0 +1,55 @@
package discordrpc_client
import (
"fmt"
"github.com/goccy/go-json"
"seanime/internal/discordrpc/ipc"
)
// Client wrapper for the Discord RPC client
type Client struct {
ClientID string
Socket *discordrpc_ipc.Socket
}
func (c *Client) Close() {
if c == nil {
return
}
c.Socket.Close()
}
// New sends a handshake in the socket and returns an error or nil and an instance of Client
func New(clientId string) (*Client, error) {
if clientId == "" {
return nil, fmt.Errorf("no clientId set")
}
payload, err := json.Marshal(handshake{"1", clientId})
if err != nil {
return nil, err
}
sock, err := discordrpc_ipc.NewConnection()
if err != nil {
return nil, err
}
c := &Client{Socket: sock, ClientID: clientId}
r, err := c.Socket.Send(0, string(payload))
if err != nil {
return nil, err
}
var responseBody Data
if err := json.Unmarshal([]byte(r), &responseBody); err != nil {
return nil, err
}
if responseBody.Code > 1000 {
return nil, fmt.Errorf(responseBody.Message)
}
return c, nil
}

View File

@@ -0,0 +1,49 @@
package discordrpc_client
import (
"seanime/internal/constants"
"testing"
"time"
)
func TestClient(t *testing.T) {
drpc, err := New(constants.DiscordApplicationId)
if err != nil {
t.Fatalf("failed to connect to discord ipc: %v", err)
}
defer drpc.Close()
mangaActivity := Activity{
Details: "Boku no Kokoro no Yabai Yatsu",
State: "Reading Chapter 30",
Assets: &Assets{
LargeImage: "https://s4.anilist.co/file/anilistcdn/media/manga/cover/medium/bx101557-bEJu54cmVYxx.jpg",
LargeText: "Boku no Kokoro no Yabai Yatsu",
SmallImage: "logo",
SmallText: "Seanime",
},
Timestamps: &Timestamps{
Start: &Epoch{
Time: time.Now(),
},
},
Instance: true,
Type: 3,
}
go func() {
_ = drpc.SetActivity(mangaActivity)
time.Sleep(10 * time.Second)
mangaActivity2 := mangaActivity
mangaActivity2.Timestamps.Start.Time = time.Now()
mangaActivity2.State = "Reading Chapter 31"
_ = drpc.SetActivity(mangaActivity2)
return
}()
//if err != nil {
// t.Fatalf("failed to set activity: %v", err)
//}
time.Sleep(30 * time.Second)
}

View File

@@ -0,0 +1,114 @@
package discordrpc_client
import (
"fmt"
"github.com/goccy/go-json"
"github.com/google/uuid"
)
type command string
const (
// DispatchCommand event dispatch
DispatchCommand command = "DISPATCH"
// AuthorizeCommand used to authorize a new client with your app
AuthorizeCommand command = "AUTHORIZE"
// AuthenticateCommand used to authenticate an existing client with your app
AuthenticateCommand command = "AUTHENTICATE"
// GetGuildCommand used to retrieve guild information from the client
GetGuildCommand command = "GET_GUILD"
// GetGuildsCommand used to retrieve a list of guilds from the client
GetGuildsCommand command = "GET_GUILDS"
// GetChannelCommand used to retrieve channel information from the client
GetChannelCommand command = "GET_CHANNEL"
// GetChannelsCommand used to retrieve a list of channels for a guild from the client
GetChannelsCommand command = "GET_CHANNELS"
// SubscribeCommand used to subscribe to an RPC event
SubscribeCommand command = "SUBSCRIBE"
// UnSubscribeCommand used to unsubscribe from an RPC event
UnSubscribeCommand command = "UNSUBSCRIBE"
// SetUserVoiceSettingsCommand used to change voice settings of users in voice channels
SetUserVoiceSettingsCommand command = "SET_USER_VOICE_SETTINGS"
// SelectVoiceChannelCommand used to join or leave a voice channel, group dm, or dm
SelectVoiceChannelCommand command = "SELECT_VOICE_CHANNEL"
// GetSelectedVoiceChannelCommand used to get the current voice channel the client is in
GetSelectedVoiceChannelCommand command = "GET_SELECTED_VOICE_CHANNEL"
// SelectTextChannelCommand used to join or leave a text channel, group dm, or dm
SelectTextChannelCommand command = "SELECT_TEXT_CHANNEL"
// GetVoiceSettingsCommand used to retrieve the client's voice settings
GetVoiceSettingsCommand command = "GET_VOICE_SETTINGS"
// SetVoiceSettingsCommand used to set the client's voice settings
SetVoiceSettingsCommand command = "SET_VOICE_SETTINGS"
// CaptureShortcutCommand used to capture a keyboard shortcut entered by the user
CaptureShortcutCommand command = "CAPTURE_SHORTCUT"
// SetCertifiedDevicesCommand used to send info about certified hardware devices
SetCertifiedDevicesCommand command = "SET_CERTIFIED_DEVICES"
// SetActivityCommand used to update a user's Rich Presence
SetActivityCommand command = "SET_ACTIVITY"
// SendActivityJoinInviteCommand used to consent to a Rich Presence Ask to Join request
SendActivityJoinInviteCommand command = "SEND_ACTIVITY_JOIN_INVITE"
// CloseActivityRequestCommand used to reject a Rich Presence Ask to Join request
CloseActivityRequestCommand command = "CLOSE_ACTIVITY_REQUEST"
)
type Payload struct {
Cmd command `json:"cmd"`
Args Args `json:"args"`
Event event `json:"evt,omitempty"`
Data *Data `json:"data,omitempty"`
Nonce uuid.UUID `json:"nonce"`
}
// SendPayload sends payload to the Discord RPC server
func (c *Client) SendPayload(payload Payload) error {
if c == nil {
return nil
}
// Marshal the payload into JSON
rb, err := json.Marshal(payload)
if err != nil {
return err
}
// Send over the socket
r, err := c.Socket.Send(1, string(rb))
if err != nil {
return err
}
// Response usually matches the outgoing request, also a payload
var responseBody Payload
if err := json.Unmarshal([]byte(r), &responseBody); err != nil {
return err
}
// TODO: Convert op codes to enums? Either way seems that 1000 is good, everything else is bad
if responseBody.Data.Code > 1000 {
return fmt.Errorf(responseBody.Data.Message)
}
if responseBody.Nonce != payload.Nonce {
return fmt.Errorf("invalid nonce")
}
return nil
}

View File

@@ -0,0 +1,66 @@
package discordrpc_client
import (
"encoding/json"
"time"
"github.com/google/uuid"
)
type ActivityEventData struct {
Secret string `json:"secret"`
User *User `json:"user"`
}
type event string
var (
ActivityJoinEvent event = "ACTIVITY_JOIN"
ActivitySpectateEvent event = "ACTIVITY_SPECTATE"
ActivityJoinRequestEvent event = "ACTIVITY_JOIN_REQUEST"
)
func (c *Client) RegisterEvent(ch chan ActivityEventData, evt event) error {
if c == nil {
return nil
}
payload := Payload{
Cmd: SubscribeCommand,
Event: evt,
Nonce: uuid.New(),
}
err := c.SendPayload(payload)
if err != nil {
return nil
}
go func() {
for {
r, err := c.Socket.Read()
if err != nil {
continue
}
var response struct {
Event event `json:"event"`
Data *ActivityEventData `json:"data"`
}
if err := json.Unmarshal([]byte(r), &response); err != nil {
continue
}
if response.Event == evt {
continue
}
ch <- *response.Data
time.Sleep(10 * time.Millisecond)
}
}()
return nil
}

View File

@@ -0,0 +1,25 @@
package discordrpc_client
type handshake struct {
V string `json:"v"`
ClientID string `json:"client_id"`
}
// Data section of the RPC response
type Data struct {
Code int `json:"code"`
Message string `json:"message"`
}
// Args seems to contain the most data, Pid here is mandatory
type Args struct {
Pid int `json:"pid"`
Activity *Activity `json:"activity,omitempty"`
}
type User struct {
Id string `json:"id"`
Username string `json:"username"`
Discriminator string `json:"discriminator"`
Avatar string `json:"avatar"`
}