From 5ff0f2c63b96623c81657a8f0e2dbdc537edf103 Mon Sep 17 00:00:00 2001 From: delthas Date: Wed, 23 Nov 2022 20:06:42 +0100 Subject: Implement /NP This queries the system for the current song being played. This uses DBus & MPRIS/xesam. The priority is any player in playing state > any player in paused state. Only players in the playing or paused state, with a valid song title are considered. --- commands.go | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ doc/senpai.1.scd | 4 +++ go.mod | 1 + go.sum | 2 ++ 4 files changed, 101 insertions(+) diff --git a/commands.go b/commands.go index 55582a0..5a2c71b 100644 --- a/commands.go +++ b/commands.go @@ -3,6 +3,7 @@ package senpai import ( "errors" "fmt" + "net/url" "sort" "strconv" "strings" @@ -11,6 +12,8 @@ import ( "git.sr.ht/~taiite/senpai/irc" "git.sr.ht/~taiite/senpai/ui" "github.com/gdamore/tcell/v2" + "github.com/godbus/dbus/v5" + "golang.org/x/net/context" ) var ( @@ -57,6 +60,11 @@ func init() { Desc: "send an action (reply to last query if sent from home)", Handle: commandDoMe, }, + "NP": { + AllowHome: true, + Desc: "send the current song that is being played on the system", + Handle: commandDoNP, + }, "MSG": { AllowHome: true, MinArgs: 2, @@ -343,6 +351,14 @@ func commandDoMe(app *App, args []string) (err error) { return nil } +func commandDoNP(app *App, args []string) (err error) { + song := getSong() + if song == "" { + return fmt.Errorf("no song was detected") + } + return commandDoMe(app, []string{fmt.Sprintf("np: %s", song)}) +} + func commandDoMsg(app *App, args []string) (err error) { target := args[0] content := args[1] @@ -775,3 +791,81 @@ func (app *App) handleInput(buffer, content string) error { return cmd.Handle(app, args) } + +func getSong() string { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + bus, err := dbus.ConnectSessionBus(dbus.WithContext(ctx)) + if err != nil { + return "" + } + defer bus.Close() + + var names []string + if err := bus.BusObject().CallWithContext(ctx, "org.freedesktop.DBus.ListNames", 0).Store(&names); err != nil { + return "" + } + song := "" + for _, name := range names { + if !strings.HasPrefix(name, "org.mpris.MediaPlayer2.") { + continue + } + var playing bool + o := bus.Object(name, "/org/mpris/MediaPlayer2") + var status string + if err := o.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.mpris.MediaPlayer2.Player", "PlaybackStatus").Store(&status); err != nil { + continue + } + switch status { + case "Playing": + playing = true + case "Paused": + playing = false + default: + continue + } + var metadata map[string]dbus.Variant + if err := o.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.mpris.MediaPlayer2.Player", "Metadata").Store(&metadata); err != nil { + continue + } + var trackURL string + var title string + var album string + var artist string + if e, ok := metadata["xesam:title"].Value().(string); ok { + title = e + } + if e, ok := metadata["xesam:album"].Value().(string); ok { + album = e + } + if e, ok := metadata["xesam:url"].Value().(string); ok { + trackURL = e + } + if e, ok := metadata["xesam:artist"].Value().([]string); ok && len(e) > 0 { + artist = e[0] + } + if title == "" { + continue + } + var sb strings.Builder + fmt.Fprintf(&sb, "\x02%s\x02", title) + if artist != "" { + fmt.Fprintf(&sb, " by \x02%s\x02", artist) + } + if album != "" { + fmt.Fprintf(&sb, " from \x02%s\x02", album) + } + if u, err := url.Parse(trackURL); err == nil { + switch u.Scheme { + case "http", "https": + fmt.Fprintf(&sb, " — %s", trackURL) + } + } + song = sb.String() + if playing { + return song + } + } + return song +} diff --git a/doc/senpai.1.scd b/doc/senpai.1.scd index d58e55a..20befac 100644 --- a/doc/senpai.1.scd +++ b/doc/senpai.1.scd @@ -165,6 +165,10 @@ _name_ is matched case-insensitively. It can be one of the following: Send a message prefixed with your nick (a user action). If sent from home, reply to the last person who sent a private message. +*NP* + Send the current song that is being played on the system. Uses DBus/MPRIS + internally. + *QUOTE* Send _raw message_ verbatim. diff --git a/go.mod b/go.mod index 2c304cd..8cfff1b 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( git.sr.ht/~emersion/go-scfg v0.0.0-20201019143924-142a8aa629fc github.com/delthas/go-localeinfo v0.0.0-20221115102303-5a7785a1acc1 github.com/gdamore/tcell/v2 v2.5.4-0.20221017224006-ede1dd5ee680 + github.com/godbus/dbus/v5 v5.1.0 github.com/mattn/go-runewidth v0.0.14 golang.org/x/net v0.0.0-20220722155237-a158d28d115b golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 diff --git a/go.sum b/go.sum index afa9274..b421a96 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdk github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell/v2 v2.5.4-0.20221017224006-ede1dd5ee680 h1:bCjGvZsZNvhzJZ+gDmXpKdDPyf358nSPqepaTI1AsMQ= github.com/gdamore/tcell/v2 v2.5.4-0.20221017224006-ede1dd5ee680/go.mod h1:XmCynGHvvGG7UFI6az9zzoEHBvZB1PGf5APwOJMFUyE= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -- cgit v1.2.3