forked from JanDeDobbeleer/oh-my-posh
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: youtube music desktop app integration
resolves JanDeDobbeleer#170
- Loading branch information
1 parent
7071c4d
commit 870af53
Showing
9 changed files
with
331 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
--- | ||
id: ytm | ||
title: YouTube Music | ||
sidebar_label: YouTube Music | ||
--- | ||
|
||
## What | ||
|
||
Shows the currently playing song in the [YouTube Music Desktop App](https://github.com/ytmdesktop/ytmdesktop). | ||
|
||
**NOTE**: You **must** enable Remote Control in YTMDA for this segment to work: `Settings > Integrations > Remote Control` | ||
|
||
It is fine if `Protect remote control with password` is automatically enabled. This segment does not require the | ||
Remote Control password. | ||
|
||
## Sample Configuration | ||
|
||
```json | ||
{ | ||
"type": "ytm", | ||
"style": "powerline", | ||
"powerline_symbol": "\uE0B0", | ||
"foreground": "#ffffff", | ||
"background": "#FF0000", | ||
"properties": { | ||
"prefix": "\uF16A ", | ||
"playing_icon": "\uE602 ", | ||
"paused_icon": "\uF8E3 ", | ||
"stopped_icon": "\uF04D ", | ||
"track_separator" : " - " | ||
} | ||
} | ||
``` | ||
|
||
## Properties | ||
|
||
- playing_icon: `string` - text/icon to show when playing - defaults to `\uE602 ` | ||
- paused_icon: `string` - text/icon to show when paused - defaults to `\uF8E3 ` | ||
- stopped_icon: `string` - text/icon to show when paused - defaults to `\uF04D ` | ||
- track_separator: `string` - text/icon to put between the artist and song name - defaults to ` - ` | ||
- api_url: `string` - the YTMDA Remote Control API URL- defaults to `http://localhost:9863` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,7 @@ module.exports = { | |
"terraform", | ||
"text", | ||
"time", | ||
"ytm", | ||
] | ||
}, | ||
{ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package main | ||
|
||
import ( | ||
"net/http" | ||
) | ||
|
||
// Inspired by: https://www.thegreatcodeadventure.com/mocking-http-requests-in-golang/ | ||
|
||
type httpClient interface { | ||
Do(req *http.Request) (*http.Response, error) | ||
} | ||
|
||
var ( | ||
client httpClient = &http.Client{} | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
) | ||
|
||
type ytm struct { | ||
props *properties | ||
env environmentInfo | ||
status playStatus | ||
artist string | ||
track string | ||
} | ||
|
||
const ( | ||
// APIURL is the YTMDA Remote Control API URL property. | ||
APIURL Property = "api_url" | ||
) | ||
|
||
func (y *ytm) string() string { | ||
icon := "" | ||
separator := y.props.getString(TrackSeparator, " - ") | ||
switch y.status { | ||
case paused: | ||
icon = y.props.getString(PausedIcon, "\uF8E3 ") | ||
case playing: | ||
icon = y.props.getString(PlayingIcon, "\uE602 ") | ||
case stopped: | ||
return y.props.getString(StoppedIcon, "\uF04D ") | ||
} | ||
return fmt.Sprintf("%s%s%s%s", icon, y.artist, separator, y.track) | ||
} | ||
|
||
func (y *ytm) enabled() bool { | ||
err := y.setStatus() | ||
// If we don't get a response back (error), the user isn't running | ||
// YTMDA, or they don't have the RC API enabled. | ||
return err == nil | ||
} | ||
|
||
func (y *ytm) init(props *properties, env environmentInfo) { | ||
y.props = props | ||
y.env = env | ||
} | ||
|
||
type playStatus int | ||
|
||
const ( | ||
playing playStatus = iota | ||
paused | ||
stopped | ||
) | ||
|
||
type ytmdaStatusResponse struct { | ||
player `json:"player"` | ||
track `json:"track"` | ||
} | ||
|
||
type player struct { | ||
HasSong bool `json:"hasSong"` | ||
IsPaused bool `json:"isPaused"` | ||
VolumePercent int `json:"volumePercent"` | ||
SeekbarCurrentPosition int `json:"seekbarCurrentPosition"` | ||
SeekbarCurrentPositionHuman string `json:"seekbarCurrentPositionHuman"` | ||
StatePercent float64 `json:"statePercent"` | ||
LikeStatus string `json:"likeStatus"` | ||
RepeatType string `json:"repeatType"` | ||
} | ||
|
||
type track struct { | ||
Author string `json:"author"` | ||
Title string `json:"title"` | ||
Album string `json:"album"` | ||
Cover string `json:"cover"` | ||
Duration int `json:"duration"` | ||
DurationHuman string `json:"durationHuman"` | ||
URL string `json:"url"` | ||
ID string `json:"id"` | ||
IsVideo bool `json:"isVideo"` | ||
IsAdvertisement bool `json:"isAdvertisement"` | ||
InLibrary bool `json:"inLibrary"` | ||
} | ||
|
||
func (y *ytm) setStatus() error { | ||
// https://github.com/ytmdesktop/ytmdesktop/wiki/Remote-Control-API | ||
url := y.props.getString(APIURL, "http://localhost:9863") | ||
body, err := y.env.doGet(url + "/query") | ||
if err != nil { | ||
return err | ||
} | ||
q := new(ytmdaStatusResponse) | ||
err = json.Unmarshal(body, &q) | ||
if err != nil { | ||
return err | ||
} | ||
y.status = playing | ||
if !q.player.HasSong { | ||
y.status = stopped | ||
} else if q.player.IsPaused { | ||
y.status = paused | ||
} | ||
y.artist = q.track.Author | ||
y.track = q.track.Title | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestYTMStringPlayingSong(t *testing.T) { | ||
expected := "\ue602 Candlemass - Spellbreaker" | ||
y := &ytm{ | ||
artist: "Candlemass", | ||
track: "Spellbreaker", | ||
status: playing, | ||
} | ||
assert.Equal(t, expected, y.string()) | ||
} | ||
|
||
func TestYTMStringPausedSong(t *testing.T) { | ||
expected := "\uF8E3 Candlemass - Spellbreaker" | ||
y := &ytm{ | ||
artist: "Candlemass", | ||
track: "Spellbreaker", | ||
status: paused, | ||
} | ||
assert.Equal(t, expected, y.string()) | ||
} | ||
|
||
func TestYTMStringStoppedSong(t *testing.T) { | ||
expected := "\uf04d " | ||
y := &ytm{ | ||
artist: "Candlemass", | ||
track: "Spellbreaker", | ||
status: stopped, | ||
} | ||
assert.Equal(t, expected, y.string()) | ||
} | ||
|
||
func bootstrapYTMDATest(json string, err error) *ytm { | ||
url := "http://localhost:1337" | ||
env := new(MockedEnvironment) | ||
env.On("doGet", url+"/query").Return([]byte(json), err) | ||
props := &properties{ | ||
values: map[Property]interface{}{ | ||
APIURL: url, | ||
}, | ||
} | ||
ytm := &ytm{ | ||
env: env, | ||
props: props, | ||
} | ||
return ytm | ||
} | ||
|
||
func TestYTMDAPlaying(t *testing.T) { | ||
json := `{ "player": { "hasSong": true, "isPaused": false }, "track": { "author": "Candlemass", "title": "Spellbreaker" } }` | ||
ytm := bootstrapYTMDATest(json, nil) | ||
err := ytm.setStatus() | ||
assert.NoError(t, err) | ||
assert.Equal(t, playing, ytm.status) | ||
assert.Equal(t, "Candlemass", ytm.artist) | ||
assert.Equal(t, "Spellbreaker", ytm.track) | ||
} | ||
|
||
func TestYTMDAPaused(t *testing.T) { | ||
json := `{ "player": { "hasSong": true, "isPaused": true }, "track": { "author": "Candlemass", "title": "Spellbreaker" } }` | ||
ytm := bootstrapYTMDATest(json, nil) | ||
err := ytm.setStatus() | ||
assert.NoError(t, err) | ||
assert.Equal(t, paused, ytm.status) | ||
assert.Equal(t, "Candlemass", ytm.artist) | ||
assert.Equal(t, "Spellbreaker", ytm.track) | ||
} | ||
|
||
func TestYTMDAStopped(t *testing.T) { | ||
json := `{ "player": { "hasSong": false }, "track": { "author": "", "title": "" } }` | ||
ytm := bootstrapYTMDATest(json, nil) | ||
err := ytm.setStatus() | ||
assert.NoError(t, err) | ||
assert.Equal(t, stopped, ytm.status) | ||
assert.Equal(t, "", ytm.artist) | ||
assert.Equal(t, "", ytm.track) | ||
} | ||
|
||
func TestYTMDAError(t *testing.T) { | ||
json := `{ "player": { "hasSong": false }, "track": { "author": "", "title": "" } }` | ||
ytm := bootstrapYTMDATest(json, errors.New("Oh noes")) | ||
enabled := ytm.enabled() | ||
assert.False(t, enabled) | ||
} |
Oops, something went wrong.