library: io: implement client type and I/O methods

Path discovery is ported directly from Discord's C++ example, Windows has a crazy looking socket path that needs special syscall magic to dial, so we'll put that off for the time being.

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
This commit is contained in:
Ophestra Umiker 2024-06-19 23:11:44 +09:00
parent 078092bc4c
commit 4e1e343081
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
2 changed files with 111 additions and 0 deletions

91
io.go Normal file
View File

@ -0,0 +1,91 @@
package rpcfetch
import (
"encoding/binary"
"encoding/json"
"net"
"os"
"strconv"
)
type Client struct {
id string
dialed bool
active bool
net.Conn
}
// Raw wraps around the Raw method to provide generic json parsing
func Raw[T any](d *Client, opcode uint32, payload any) (uint32, T, error) {
var p T
opcodeResp, payloadResp, err := d.Raw(opcode, payload)
if err != nil {
return opcodeResp, p, err
}
return opcodeResp, p, json.Unmarshal(payloadResp, &p)
}
// Raw writes a raw payload to Discord and returns the response opcode and payload
func (d *Client) Raw(opcode uint32, payload any) (uint32, []byte, error) {
if err := binary.Write(d.Conn, binary.LittleEndian, opcode); err != nil {
return 0, nil, err
}
if p, err := json.Marshal(payload); err != nil {
return 0, nil, err
} else {
if err = binary.Write(d.Conn, binary.LittleEndian, uint32(len(p))); err != nil {
return 0, nil, err
}
if _, err = d.Conn.Write(p); err != nil {
return 0, nil, err
}
}
var (
opcodeResp uint32
lengthResp uint32
)
if err := binary.Read(d.Conn, binary.LittleEndian, &opcodeResp); err != nil {
return 0, nil, err
}
if err := binary.Read(d.Conn, binary.LittleEndian, &lengthResp); err != nil {
return 0, nil, err
}
payloadResp := make([]byte, lengthResp)
_, err := d.Read(payloadResp)
return opcodeResp, payloadResp, err
}
// New sets up and returns the reference to a new Client
func New(id string) *Client {
d := &Client{
id: id,
}
return d
}
func sockPath() string {
snap := "/run/user/" + strconv.Itoa(os.Getuid()) + "/snap.discord"
if _, err := os.Stat(snap); err == nil {
return snap
}
for _, env := range []string{"XDG_RUNTIME_DIR", "TMPDIR", "TMP", "TEMP"} {
if val, ok := os.LookupEnv(env); ok {
return val
}
}
// fallback
return "/tmp"
}

20
io_unix.go Normal file
View File

@ -0,0 +1,20 @@
package rpcfetch
import (
"net"
"time"
)
func (d *Client) dial() error {
if d.dialed {
panic("attempted to dial on open client")
}
if conn, err := net.DialTimeout("unix", sockPath()+"/discord-ipc-0", 5*time.Second); err != nil {
return err
} else {
d.Conn = conn
d.dialed = true
}
return nil
}