mirror of
https://github.com/Apologieze/Benri.git
synced 2026-01-18 17:17:21 +01:00
Rich presence update + fix play button
This commit is contained in:
77
richgo/client/client.go
Normal file
77
richgo/client/client.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/hugolgst/rich-go/ipc"
|
||||
)
|
||||
|
||||
var logged bool
|
||||
|
||||
// Login sends a handshake in the socket and returns an error or nil
|
||||
func Login(clientid string) error {
|
||||
if !logged {
|
||||
payload, err := json.Marshal(Handshake{"1", clientid})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ipc.OpenSocket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Response should be parsed
|
||||
ipc.Send(0, string(payload))
|
||||
}
|
||||
logged = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Logout() {
|
||||
logged = false
|
||||
|
||||
err := ipc.CloseSocket()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func SetActivity(activity Activity) error {
|
||||
if !logged {
|
||||
return nil
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(Frame{
|
||||
"SET_ACTIVITY",
|
||||
Args{
|
||||
os.Getpid(),
|
||||
mapActivity(&activity),
|
||||
},
|
||||
getNonce(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Response should be parsed
|
||||
ipc.Send(1, string(payload))
|
||||
return nil
|
||||
}
|
||||
|
||||
func getNonce() string {
|
||||
buf := make([]byte, 16)
|
||||
_, err := rand.Read(buf)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
buf[6] = (buf[6] & 0x0f) | 0x40
|
||||
|
||||
return fmt.Sprintf("%x-%x-%x-%x-%x", buf[0:4], buf[4:6], buf[6:8], buf[8:10], buf[10:])
|
||||
}
|
||||
132
richgo/client/inputMapper.go
Normal file
132
richgo/client/inputMapper.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// ActivityType represents the type of Discord rich presence activity
|
||||
type ActivityType int
|
||||
|
||||
const (
|
||||
// ActivityTypePlaying represents "Playing ..." status
|
||||
ActivityTypePlaying ActivityType = 0
|
||||
// ActivityTypeListening represents "Listening to ..." status
|
||||
ActivityTypeListening ActivityType = 2
|
||||
// ActivityTypeWatching represents "Watching ..." status
|
||||
ActivityTypeWatching ActivityType = 3
|
||||
// ActivityTypeCompeting represents "Competing in ..." status
|
||||
ActivityTypeCompeting ActivityType = 5
|
||||
)
|
||||
|
||||
// Activity holds the data for discord rich presence
|
||||
type Activity struct {
|
||||
// What the player is currently doing
|
||||
Details string
|
||||
// The user's current party status
|
||||
State string
|
||||
// The activity type, defaults to Playing if not specified
|
||||
Type ActivityType
|
||||
// The id for a large asset of the activity, usually a snowflake
|
||||
LargeImage string
|
||||
// Text displayed when hovering over the large image of the activity
|
||||
LargeText string
|
||||
// The id for a small asset of the activity, usually a snowflake
|
||||
SmallImage string
|
||||
// Text displayed when hovering over the small image of the activity
|
||||
SmallText string
|
||||
// Information for the current party of the player
|
||||
Party *Party
|
||||
// Unix timestamps for start and/or end of the game
|
||||
Timestamps *Timestamps
|
||||
// Secrets for Rich Presence joining and spectating
|
||||
Secrets *Secrets
|
||||
// Clickable buttons that open a URL in the browser
|
||||
Buttons []*Button
|
||||
}
|
||||
|
||||
// Button holds a label and the corresponding URL that is opened on press
|
||||
type Button struct {
|
||||
// The label of the button
|
||||
Label string
|
||||
// The URL of the button
|
||||
Url string
|
||||
}
|
||||
|
||||
// Party holds information for the current party of the player
|
||||
type Party struct {
|
||||
// The ID of the party
|
||||
ID string
|
||||
// Used to show the party's current size
|
||||
Players int
|
||||
// Used to show the party's maximum size
|
||||
MaxPlayers int
|
||||
}
|
||||
|
||||
// Timestamps holds unix timestamps for start and/or end of the game
|
||||
type Timestamps struct {
|
||||
// unix time (in milliseconds) of when the activity started
|
||||
Start *time.Time
|
||||
// unix time (in milliseconds) of when the activity ends
|
||||
End *time.Time
|
||||
}
|
||||
|
||||
// Secrets holds secrets for Rich Presence joining and spectating
|
||||
type Secrets struct {
|
||||
// The secret for a specific instanced match
|
||||
Match string
|
||||
// The secret for joining a party
|
||||
Join string
|
||||
// The secret for spectating a game
|
||||
Spectate string
|
||||
}
|
||||
|
||||
func mapActivity(activity *Activity) *PayloadActivity {
|
||||
final := &PayloadActivity{
|
||||
Details: activity.Details,
|
||||
State: activity.State,
|
||||
Type: activity.Type,
|
||||
Assets: PayloadAssets{
|
||||
LargeImage: activity.LargeImage,
|
||||
LargeText: activity.LargeText,
|
||||
SmallImage: activity.SmallImage,
|
||||
SmallText: activity.SmallText,
|
||||
},
|
||||
}
|
||||
|
||||
if activity.Timestamps != nil && activity.Timestamps.Start != nil {
|
||||
start := uint64(activity.Timestamps.Start.UnixNano() / 1e6)
|
||||
final.Timestamps = &PayloadTimestamps{
|
||||
Start: &start,
|
||||
}
|
||||
if activity.Timestamps.End != nil {
|
||||
end := uint64(activity.Timestamps.End.UnixNano() / 1e6)
|
||||
final.Timestamps.End = &end
|
||||
}
|
||||
}
|
||||
|
||||
if activity.Party != nil {
|
||||
final.Party = &PayloadParty{
|
||||
ID: activity.Party.ID,
|
||||
Size: [2]int{activity.Party.Players, activity.Party.MaxPlayers},
|
||||
}
|
||||
}
|
||||
|
||||
if activity.Secrets != nil {
|
||||
final.Secrets = &PayloadSecrets{
|
||||
Join: activity.Secrets.Join,
|
||||
Match: activity.Secrets.Match,
|
||||
Spectate: activity.Secrets.Spectate,
|
||||
}
|
||||
}
|
||||
|
||||
if len(activity.Buttons) > 0 {
|
||||
for _, btn := range activity.Buttons {
|
||||
final.Buttons = append(final.Buttons, &PayloadButton{
|
||||
Label: btn.Label,
|
||||
Url: btn.Url,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return final
|
||||
}
|
||||
56
richgo/client/types.go
Normal file
56
richgo/client/types.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package client
|
||||
|
||||
type Handshake struct {
|
||||
V string `json:"v"`
|
||||
ClientId string `json:"client_id"`
|
||||
}
|
||||
|
||||
type Frame struct {
|
||||
Cmd string `json:"cmd"`
|
||||
Args Args `json:"args"`
|
||||
Nonce string `json:"nonce"`
|
||||
}
|
||||
|
||||
type Args struct {
|
||||
Pid int `json:"pid"`
|
||||
Activity *PayloadActivity `json:"activity"`
|
||||
}
|
||||
|
||||
type PayloadActivity struct {
|
||||
Details string `json:"details,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Type ActivityType `json:"type,omitempty"`
|
||||
Assets PayloadAssets `json:"assets,omitempty"`
|
||||
Party *PayloadParty `json:"party,omitempty"`
|
||||
Timestamps *PayloadTimestamps `json:"timestamps,omitempty"`
|
||||
Secrets *PayloadSecrets `json:"secrets,omitempty"`
|
||||
Buttons []*PayloadButton `json:"buttons,omitempty"`
|
||||
}
|
||||
|
||||
type PayloadAssets struct {
|
||||
LargeImage string `json:"large_image,omitempty"`
|
||||
LargeText string `json:"large_text,omitempty"`
|
||||
SmallImage string `json:"small_image,omitempty"`
|
||||
SmallText string `json:"small_text,omitempty"`
|
||||
}
|
||||
|
||||
type PayloadParty struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Size [2]int `json:"size,omitempty"`
|
||||
}
|
||||
|
||||
type PayloadTimestamps struct {
|
||||
Start *uint64 `json:"start,omitempty"`
|
||||
End *uint64 `json:"end,omitempty"`
|
||||
}
|
||||
|
||||
type PayloadSecrets struct {
|
||||
Match string `json:"match,omitempty"`
|
||||
Join string `json:"join,omitempty"`
|
||||
Spectate string `json:"spectate,omitempty"`
|
||||
}
|
||||
|
||||
type PayloadButton struct {
|
||||
Label string `json:"label,omitempty"`
|
||||
Url string `json:"url,omitempty"`
|
||||
}
|
||||
81
richgo/ipc/ipc.go
Normal file
81
richgo/ipc/ipc.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package ipc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
var socket net.Conn
|
||||
|
||||
// Choose the right directory to the ipc socket and return it
|
||||
func GetIpcPath() string {
|
||||
variablesnames := []string{"XDG_RUNTIME_DIR", "TMPDIR", "TMP", "TEMP"}
|
||||
|
||||
if _, err := os.Stat("/run/user/1000/snap.discord"); err == nil {
|
||||
return "/run/user/1000/snap.discord"
|
||||
}
|
||||
|
||||
if _, err := os.Stat("/run/user/1000/.flatpak/com.discordapp.Discord/xdg-run"); err == nil {
|
||||
return "/run/user/1000/.flatpak/com.discordapp.Discord/xdg-run"
|
||||
}
|
||||
|
||||
for _, variablename := range variablesnames {
|
||||
path, exists := os.LookupEnv(variablename)
|
||||
|
||||
if exists {
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
return "/tmp"
|
||||
}
|
||||
|
||||
func CloseSocket() error {
|
||||
if socket != nil {
|
||||
socket.Close()
|
||||
socket = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read the socket response
|
||||
func Read() string {
|
||||
buf := make([]byte, 512)
|
||||
payloadlength, err := socket.Read(buf)
|
||||
if err != nil {
|
||||
//fmt.Println("Nothing to read")
|
||||
}
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
for i := 8; i < payloadlength; i++ {
|
||||
buffer.WriteByte(buf[i])
|
||||
}
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// Send opcode and payload to the unix socket
|
||||
func Send(opcode int, payload string) string {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
err := binary.Write(buf, binary.LittleEndian, int32(opcode))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
err = binary.Write(buf, binary.LittleEndian, int32(len(payload)))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
buf.Write([]byte(payload))
|
||||
_, err = socket.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
return Read()
|
||||
}
|
||||
20
richgo/ipc/ipc_notwin.go
Normal file
20
richgo/ipc/ipc_notwin.go
Normal file
@@ -0,0 +1,20 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package ipc
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// OpenSocket opens the discord-ipc-0 unix socket
|
||||
func OpenSocket() error {
|
||||
sock, err := net.DialTimeout("unix", GetIpcPath()+"/discord-ipc-0", time.Second*2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
socket = sock
|
||||
return nil
|
||||
}
|
||||
23
richgo/ipc/ipc_windows.go
Normal file
23
richgo/ipc/ipc_windows.go
Normal file
@@ -0,0 +1,23 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package ipc
|
||||
|
||||
import (
|
||||
npipe "gopkg.in/natefinch/npipe.v2"
|
||||
"time"
|
||||
)
|
||||
|
||||
// OpenSocket opens the discord-ipc-0 named pipe
|
||||
func OpenSocket() error {
|
||||
// Connect to the Windows named pipe, this is a well known name
|
||||
// We use DialTimeout since it will block forever (or very very long) on Windows
|
||||
// if the pipe is not available (Discord not running)
|
||||
sock, err := npipe.DialTimeout(`\\.\pipe\discord-ipc-0`, time.Second*2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
socket = sock
|
||||
return nil
|
||||
}
|
||||
@@ -320,7 +320,7 @@ func displayLocalProgress() {
|
||||
playButton.Text = fmt.Sprint("Play Ep", currentEp)
|
||||
fmt.Println("Current Ep:", currentEp)
|
||||
|
||||
setPlayButtonVisibility()
|
||||
defer setPlayButtonVisibility()
|
||||
if localDbAnime != nil {
|
||||
if localDbAnime.Ep.Number == AnimeProgress {
|
||||
if localDbAnime.Ep.Player.PlaybackTime == 0 {
|
||||
@@ -339,6 +339,7 @@ func displayLocalProgress() {
|
||||
}
|
||||
|
||||
func setPlayButtonVisibility() {
|
||||
defer playButton.Refresh()
|
||||
if animeSelected.Media.NextAiringEpisode != nil {
|
||||
if *animeSelected.Progress+1 == animeSelected.Media.NextAiringEpisode.Episode {
|
||||
playButton.Hide()
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package richPresence
|
||||
|
||||
import (
|
||||
"AnimeGUI/richgo/client"
|
||||
"AnimeGUI/src/config"
|
||||
"fmt"
|
||||
"github.com/charmbracelet/log"
|
||||
"github.com/hugolgst/rich-go/client"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -51,9 +51,10 @@ func InitDiscordRichPresence() {
|
||||
func SetMenuActivity() {
|
||||
log.Info("Main Menu Activity presence")
|
||||
err := client.SetActivity(client.Activity{
|
||||
Type: client.ActivityTypeWatching,
|
||||
Details: "In Main Menu",
|
||||
State: advert,
|
||||
LargeImage: "https://apologize.fr/benri/icon.jpg",
|
||||
LargeImage: "main-image",
|
||||
LargeText: advert,
|
||||
/*SmallImage: "https://s4.anilist.co/file/anilistcdn/media/anime/cover/large/bx170942-B77wUSM1jQTu.jpg",
|
||||
SmallText: "And this is the small image",*/
|
||||
@@ -91,11 +92,12 @@ func SetAnimeActivity(anime *PresenceAnime) {
|
||||
}
|
||||
|
||||
err := client.SetActivity(client.Activity{
|
||||
Type: client.ActivityTypeWatching,
|
||||
State: fmt.Sprintf("%s remaining", numberToTime(anime.Duration-anime.PlaybackTime)),
|
||||
Details: fmt.Sprintf("%s Episode %d/%d", anime.Name, anime.Ep, anime.TotalEp),
|
||||
LargeImage: anime.ImageLink,
|
||||
LargeText: anime.Name,
|
||||
SmallImage: "https://apologize.fr/benri/icon.jpg",
|
||||
SmallImage: "main-image",
|
||||
SmallText: advert,
|
||||
/*Party: &client.Party{
|
||||
ID: "-1",
|
||||
|
||||
Reference in New Issue
Block a user