Skip to content

Commit

Permalink
hi!
Browse files Browse the repository at this point in the history
  • Loading branch information
oddyamill committed Sep 8, 2024
0 parents commit f3d9683
Show file tree
Hide file tree
Showing 11 changed files with 444 additions and 0 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Build

on: [push]

jobs:
build:
runs-on: windows-latest
permissions:
id-token: write
contents: read
attestations: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.22.6'
- name: Build
run: go build -o ytmusicrpc.exe cmd/ytmusicrpc/main.go
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1
with:
subject-path: ytmusicrpc.exe
- name: Upload executable
uses: actions/upload-artifact@v4
with:
name: YoutubeMusicDiscordRichPresence2024SuperPuperShitCode
path: ytmusicrpc.exe
compression-level: 9
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
*.exe
109 changes: 109 additions & 0 deletions cmd/ytmusicrpc/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package main

import (
"encoding/json"
"flag"
"fmt"
"log"
"net/http"
"time"

"github.com/oddyamill/ytmusicrpc/discord"
)

const clientId = "1142840799738986556"
const authKey = "e8ab39d4b23d2877af508538de8424fd7c8ea4734870f462591b759acdf07199"

var activityType int

func init() {
discord.SendHandshake(clientId)
}

func main() {
flag.IntVar(&activityType, "type", 2, "discord activity type")
flag.Parse()

http.HandleFunc("/rpc", func(w http.ResponseWriter, r *http.Request) {
log.Printf("request %s %s", r.Method, r.URL.Path)

if r.Header.Get("Authorization") != authKey {
http.Error(w, "401 unauthorized", http.StatusUnauthorized)
return
}

switch r.Method {
case "POST":
updatePresence(w, r)
case "DELETE":
deletePresence(w)
default:
http.Error(w, "405 invalid request method", http.StatusMethodNotAllowed)
}
})

log.Panic(http.ListenAndServe("127.0.0.1:32484", nil))
}

type updatePresenceBody struct {
TrackId string `json:"trackId"`
Title string `json:"title"`
Artist string `json:"artist"`
Artwork string `json:"artwork"`
Album string `json:"album"`
Current int64 `json:"current"`
End int64 `json:"end"`
}

func updatePresence(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Content-Type") != "application/json" {
http.Error(w, "415 invalid content type", http.StatusUnsupportedMediaType)
return
}

var body updatePresenceBody
err := json.NewDecoder(r.Body).Decode(&body)

if err != nil {
http.Error(w, "400 bad json", http.StatusBadRequest)
return
}

if body.Album == "" || body.Artist == "" || body.Artwork == "" || body.Current < 0 || body.End == 0 || body.Title == "" || body.TrackId == "" {
http.Error(w, "400 invalid request body", http.StatusBadRequest)
return
}

timestamp := time.Now().UnixMilli()

activity := discord.Activity{
Type: activityType,
Details: body.Title,
State: body.Artist,
Assets: discord.Assets{
LargeImage: body.Artwork,
},
Timestamps: discord.Timestamps{
Start: timestamp - body.Current,
End: timestamp - body.Current + body.End,
},
Buttons: []discord.Button{
{
Label: "Слушать",
Url: "https://music.youtube.com/watch?v=" + body.TrackId,
},
},
}

if body.Title != body.Album {
activity.Assets.LargeText = body.Album
}

discord.UpdatePresence(activity)
fmt.Fprintf(w, "201 created")
}

func deletePresence(w http.ResponseWriter) {
discord.DeletePresence()
fmt.Fprintf(w, "200 ok")
}
99 changes: 99 additions & 0 deletions discord/discord.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package discord

import (
"crypto/rand"
"encoding/json"
"fmt"
"os"

"github.com/oddyamill/ytmusicrpc/ipc"
)

func SendHandshake(clientId string) {
ipc.Send(0, []byte(`{"v":1,"client_id":"`+clientId+`","nonce":"1"}`))
}

type Packet struct {
Cmd string `json:"cmd"`
Args Args `json:"args"`
Nonce string `json:"nonce"`
}

type Args struct {
Pid int `json:"pid"`
Activity *Activity `json:"activity"`
}

type Activity struct {
Type int `json:"type"`
Details string `json:"details"`
State string `json:"state"`
Assets Assets `json:"assets"`
Timestamps Timestamps `json:"timestamps"`
Buttons []Button `json:"buttons"`
}

type Assets struct {
LargeImage string `json:"large_image"`
LargeText string `json:"large_text,omitempty"`
}

type Timestamps struct {
Start int64 `json:"start"`
End int64 `json:"end"`
}

type Button struct {
Label string `json:"label"`
Url string `json:"url"`
}

func UpdatePresence(activity Activity) {
payload, err := json.Marshal(Packet{
"SET_ACTIVITY",
Args{
os.Getpid(),
&activity,
},
getNonce(),
})

if err != nil {
panic(err)
}

ipc.Send(1, payload)
}

func DeletePresence() {
payload, err := json.Marshal(Packet{
"SET_ACTIVITY",
Args{
os.Getpid(),
nil,
},
getNonce(),
})

if err != nil {
panic(err)
}

ipc.Send(1, payload)
}

// https://github.com/hugolgst/rich-go/blob/74618cc1ace23ea759a4be6a77ebc928b4d8c996/client/client.go#L67-L77

func getNonce() string {
buf := make([]byte, 16)

_, err := rand.Read(buf)

if err != nil {
panic(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:])
}
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/oddyamill/ytmusicrpc

go 1.22.6

require gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
72 changes: 72 additions & 0 deletions ipc/ipc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package ipc

import (
"bytes"
"encoding/binary"
"log"
"net"
"time"

npipe "gopkg.in/natefinch/npipe.v2"
)

// https://github.com/hugolgst/rich-go/blob/master/ipc/ipc_windows.go

var socket net.Conn

func init() {
sock, err := npipe.DialTimeout(`\\.\pipe\discord-ipc-0`, time.Second*2)

if err != nil {
panic(err)
}

log.Print("connected to Discord IPC")
socket = sock
}

// https://github.com/hugolgst/rich-go/blob/master/ipc/ipc.go

func Read() string {
buf := make([]byte, 512)
payloadlength, err := socket.Read(buf)

if err != nil {
panic(err)
}

buffer := new(bytes.Buffer)

for i := 8; i < payloadlength; i++ {
buffer.WriteByte(buf[i])
}

return buffer.String()
}


func Send(opcode int, payload []byte) string {
buf := new(bytes.Buffer)

err := binary.Write(buf, binary.LittleEndian, int32(opcode))

if err != nil {
panic(err)
}

err = binary.Write(buf, binary.LittleEndian, int32(len(payload)))

if err != nil {
panic(err)
}

buf.Write(payload)

_, err = socket.Write(buf.Bytes())

if err != nil {
panic(err)
}

return Read()
}
6 changes: 6 additions & 0 deletions jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"compilerOptions": {
"types": ["@types/tampermonkey"]
},
"include": ["userscript/ytmusicrpc.user.js"]
}
19 changes: 19 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"devDependencies": {
"@types/tampermonkey": "^5.0.3"
}
}
Loading

0 comments on commit f3d9683

Please sign in to comment.