mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2025-01-29 12:01:07 -08:00
feat(segment): add lastfm
This commit is contained in:
parent
30facb2905
commit
cf3dc7c069
|
@ -168,6 +168,8 @@ const (
|
|||
KOTLIN SegmentType = "kotlin"
|
||||
// KUBECTL writes the Kubernetes context we're currently in
|
||||
KUBECTL SegmentType = "kubectl"
|
||||
// LASTFM writes the lastfm status
|
||||
LASTFM SegmentType = "lastfm"
|
||||
// LUA writes the active lua version
|
||||
LUA SegmentType = "lua"
|
||||
// MERCURIAL writes the Mercurial source control information
|
||||
|
@ -299,6 +301,7 @@ var Segments = map[SegmentType]func() SegmentWriter{
|
|||
JULIA: func() SegmentWriter { return &segments.Julia{} },
|
||||
KOTLIN: func() SegmentWriter { return &segments.Kotlin{} },
|
||||
KUBECTL: func() SegmentWriter { return &segments.Kubectl{} },
|
||||
LASTFM: func() SegmentWriter { return &segments.LastFM{} },
|
||||
LUA: func() SegmentWriter { return &segments.Lua{} },
|
||||
MERCURIAL: func() SegmentWriter { return &segments.Mercurial{} },
|
||||
NBA: func() SegmentWriter { return &segments.Nba{} },
|
||||
|
|
142
src/segments/lastfm.go
Normal file
142
src/segments/lastfm.go
Normal file
|
@ -0,0 +1,142 @@
|
|||
package segments
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/platform"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/properties"
|
||||
)
|
||||
|
||||
type LastFM struct {
|
||||
props properties.Properties
|
||||
env platform.Environment
|
||||
|
||||
Artist string
|
||||
Track string
|
||||
Full string
|
||||
Icon string
|
||||
Status string
|
||||
}
|
||||
|
||||
const (
|
||||
// LastFM username
|
||||
Username properties.Property = "username"
|
||||
)
|
||||
|
||||
type lmfDate struct {
|
||||
UnixString string `json:"uts"`
|
||||
}
|
||||
|
||||
type lfmTrackInfo struct {
|
||||
IsPlaying *string `json:"nowplaying,omitempty"`
|
||||
}
|
||||
|
||||
type Artist struct {
|
||||
Name string `json:"#text"`
|
||||
}
|
||||
|
||||
type lfmTrack struct {
|
||||
Artist `json:"artist"`
|
||||
Name string `json:"name"`
|
||||
Info *lfmTrackInfo `json:"@attr"`
|
||||
Date lmfDate `json:"date"`
|
||||
}
|
||||
|
||||
type tracks struct {
|
||||
Tracks []*lfmTrack `json:"track"`
|
||||
}
|
||||
|
||||
type lfmDataResponse struct {
|
||||
TracksInfo tracks `json:"recenttracks"`
|
||||
}
|
||||
|
||||
func (d *LastFM) Enabled() bool {
|
||||
err := d.setStatus()
|
||||
|
||||
if err != nil {
|
||||
d.env.Error(err)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *LastFM) Template() string {
|
||||
return " {{ .Icon }}{{ if ne .Status \"stopped\" }}{{ .Full }}{{ end }} "
|
||||
}
|
||||
|
||||
func (d *LastFM) getResult() (*lfmDataResponse, error) {
|
||||
cacheTimeout := d.props.GetInt(properties.CacheTimeout, 0)
|
||||
response := new(lfmDataResponse)
|
||||
|
||||
apikey := d.props.GetString(APIKey, ".")
|
||||
username := d.props.GetString(Username, ".")
|
||||
httpTimeout := d.props.GetInt(properties.HTTPTimeout, properties.DefaultHTTPTimeout)
|
||||
|
||||
url := fmt.Sprintf("https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&api_key=%s&user=%s&format=json&limit=1", apikey, username)
|
||||
|
||||
if cacheTimeout > 0 {
|
||||
val, found := d.env.Cache().Get(url)
|
||||
|
||||
if found {
|
||||
err := json.Unmarshal([]byte(val), response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
}
|
||||
|
||||
body, err := d.env.HTTPRequest(url, nil, httpTimeout)
|
||||
if err != nil {
|
||||
return new(lfmDataResponse), err
|
||||
}
|
||||
err = json.Unmarshal(body, &response)
|
||||
if err != nil {
|
||||
return new(lfmDataResponse), err
|
||||
}
|
||||
|
||||
if cacheTimeout > 0 {
|
||||
d.env.Cache().Set(url, string(body), cacheTimeout)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (d *LastFM) setStatus() error {
|
||||
q, err := d.getResult()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(q.TracksInfo.Tracks) == 0 {
|
||||
return errors.New("No data found")
|
||||
}
|
||||
|
||||
track := q.TracksInfo.Tracks[0]
|
||||
|
||||
d.Artist = track.Artist.Name
|
||||
d.Track = track.Name
|
||||
d.Full = fmt.Sprintf("%s - %s", d.Artist, d.Track)
|
||||
|
||||
isPlaying := false
|
||||
if track.Info != nil && track.Info.IsPlaying != nil && *track.Info.IsPlaying == "true" {
|
||||
isPlaying = true
|
||||
}
|
||||
|
||||
if isPlaying {
|
||||
d.Icon = d.props.GetString(PlayingIcon, "\uE602 ")
|
||||
d.Status = "playing"
|
||||
} else {
|
||||
d.Icon = d.props.GetString(StoppedIcon, "\uF04D ")
|
||||
d.Status = "stopped"
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *LastFM) Init(props properties.Properties, env platform.Environment) {
|
||||
d.props = props
|
||||
d.env = env
|
||||
}
|
105
src/segments/lastfm_test.go
Normal file
105
src/segments/lastfm_test.go
Normal file
|
@ -0,0 +1,105 @@
|
|||
package segments
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/mock"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/properties"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
mock2 "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
const (
|
||||
LFMAPIURL = "https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&api_key=key&user=KibbeWater&format=json&limit=1"
|
||||
)
|
||||
|
||||
func TestLFMSegmentSingle(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
APIJSONResponse string
|
||||
ExpectedString string
|
||||
ExpectedEnabled bool
|
||||
Template string
|
||||
Error error
|
||||
}{
|
||||
{
|
||||
Case: "All Defaults",
|
||||
APIJSONResponse: `{"recenttracks":{"track":[{"artist":{"#text":"C.Gambino"},"name":"Automatic","@attr":{"nowplaying":"true"}}]}}`,
|
||||
ExpectedString: "\uE602 C.Gambino - Automatic",
|
||||
ExpectedEnabled: true,
|
||||
},
|
||||
{
|
||||
Case: "Custom Template",
|
||||
APIJSONResponse: `{"recenttracks":{"track":[{"artist":{"#text":"C.Gambino"},"name":"Automatic","@attr":{"nowplaying":"true"}}]}}`,
|
||||
ExpectedString: "\uE602 C.Gambino - Automatic",
|
||||
ExpectedEnabled: true,
|
||||
Template: "{{ .Icon }}{{ if ne .Status \"stopped\" }}{{ .Full }}{{ end }}",
|
||||
},
|
||||
{
|
||||
Case: "Song Stopped",
|
||||
APIJSONResponse: `{"recenttracks":{"track":[{"artist":{"#text":"C.Gambino"},"name":"Automatic","date":{"uts":"1699350223"}}]}}`,
|
||||
ExpectedString: "\uF04D",
|
||||
ExpectedEnabled: true,
|
||||
Template: "{{ .Icon }}",
|
||||
},
|
||||
{
|
||||
Case: "Error in retrieving data",
|
||||
APIJSONResponse: "nonsense",
|
||||
Error: errors.New("Something went wrong"),
|
||||
ExpectedEnabled: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
env := &mock.MockedEnvironment{}
|
||||
var props properties.Map = properties.Map{
|
||||
APIKey: "key",
|
||||
Username: "KibbeWater",
|
||||
properties.CacheTimeout: 0,
|
||||
properties.HTTPTimeout: 20000,
|
||||
}
|
||||
|
||||
env.On("HTTPRequest", LFMAPIURL).Return([]byte(tc.APIJSONResponse), tc.Error)
|
||||
env.On("Error", mock2.Anything)
|
||||
|
||||
o := &LastFM{
|
||||
props: props,
|
||||
env: env,
|
||||
}
|
||||
|
||||
enabled := o.Enabled()
|
||||
assert.Equal(t, tc.ExpectedEnabled, enabled, tc.Case)
|
||||
if !enabled {
|
||||
continue
|
||||
}
|
||||
|
||||
if tc.Template == "" {
|
||||
tc.Template = o.Template()
|
||||
}
|
||||
assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, o), tc.Case)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLFMSegmentFromCache(t *testing.T) {
|
||||
response := `{"recenttracks":{"track":[{"artist":{"mbid":"","#text":"C.Gambino"},"streamable":"0","name":"Automatic","date":{"uts":"1699350223","#text":"07 Nov 2023, 09:43"}}]}}`
|
||||
expectedString := "\uF04D"
|
||||
|
||||
env := &mock.MockedEnvironment{}
|
||||
cache := &mock.MockedCache{}
|
||||
o := &LastFM{
|
||||
props: properties.Map{
|
||||
APIKey: "key",
|
||||
Username: "KibbeWater",
|
||||
properties.CacheTimeout: 1,
|
||||
},
|
||||
env: env,
|
||||
}
|
||||
cache.On("Get", LFMAPIURL).Return(response, true)
|
||||
cache.On("Set").Return()
|
||||
env.On("Cache").Return(cache)
|
||||
|
||||
assert.Nil(t, o.setStatus())
|
||||
assert.Equal(t, expectedString, renderTemplate(env, o.Template(), o), "should return the cached response")
|
||||
}
|
|
@ -280,6 +280,7 @@
|
|||
"java",
|
||||
"kotlin",
|
||||
"kubectl",
|
||||
"lastfm",
|
||||
"lua",
|
||||
"mercurial",
|
||||
"node",
|
||||
|
@ -3155,6 +3156,55 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "lastfm"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"title": "LastFM Segment",
|
||||
"description": "https://ohmyposh.dev/docs/segments/lastfm",
|
||||
"properties": {
|
||||
"properties": {
|
||||
"properties": {
|
||||
"playing_icon": {
|
||||
"type": "string",
|
||||
"title": "User Info Separator",
|
||||
"description": "Text/icon to show when playing",
|
||||
"default": "\uE602"
|
||||
},
|
||||
"stopped_icon": {
|
||||
"type": "string",
|
||||
"title": "SSH Icon",
|
||||
"description": "Text/icon to show when stopped",
|
||||
"default": "\uF04D"
|
||||
},
|
||||
"apiKey": {
|
||||
"type": "string",
|
||||
"title": "apiKey",
|
||||
"description": "The apikey used for the api call (Required)",
|
||||
"default": "."
|
||||
},
|
||||
"username": {
|
||||
"type": "string",
|
||||
"title": "username",
|
||||
"description": "The username used for the api call (Required)",
|
||||
"default": "."
|
||||
},
|
||||
"http_timeout": {
|
||||
"$ref": "#/definitions/http_timeout"
|
||||
},
|
||||
"cache_timeout": {
|
||||
"$ref": "#/definitions/cache_timeout"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
|
|
70
website/docs/segments/lastfm.mdx
Normal file
70
website/docs/segments/lastfm.mdx
Normal file
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
id: lastfm
|
||||
title: LastFM
|
||||
sidebar_label: LastFM
|
||||
---
|
||||
|
||||
## What
|
||||
|
||||
Show the currently playing song from a [LastFM][lastfm] user.
|
||||
|
||||
:::caution
|
||||
Be aware that LastFM updates may be severely delayed when paused and songs may linger in the "now playing" state for a prolonged time.
|
||||
|
||||
Additionally, we are using HTTP requests to get the data,
|
||||
so you may need to adjust the `http_timeout` and `cache_timeout` to your liking to get better results.
|
||||
|
||||
You **must** request an [API key][api-key] at the LastFM website.
|
||||
:::
|
||||
|
||||
## Sample Configuration
|
||||
|
||||
import Config from '@site/src/components/Config.js';
|
||||
|
||||
<Config data={{
|
||||
"background": "p:sky",
|
||||
"foreground": "p:white",
|
||||
"powerline_symbol": "\ue0b0",
|
||||
"properties": {
|
||||
"apikey": "<YOUR_API_KEY>",
|
||||
"username": "<LASTFM_USERNAME>",
|
||||
"http_timeout": 20000,
|
||||
"cache_timeout": 1
|
||||
},
|
||||
"style": "powerline",
|
||||
"template": " {{ .Icon }}{{ if ne .Status \"stopped\" }}{{ .Full }}{{ end }} ",
|
||||
"type": "lastfm"
|
||||
}}/>
|
||||
|
||||
## Properties
|
||||
|
||||
| Name | Type | Description |
|
||||
| -------------- | -------- | ------------------------------------------------------ |
|
||||
| `playing_icon` | `string` | text/icon to show when playing - defaults to `\uE602 ` |
|
||||
| `stopped_icon` | `string` | text/icon to show when stopped - defaults to `\uF04D ` |
|
||||
| `apikey` | `string` | your LastFM [API key][api-key] |
|
||||
| `username` | `string` | your LastFM username |
|
||||
|
||||
## Template ([info][templates])
|
||||
|
||||
:::note default template
|
||||
|
||||
```template
|
||||
{{ .Icon }}{{ if ne .Status \"stopped\" }}{{ .Full }}{{ end }}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Description |
|
||||
| --------- | -------- | ---------------------------------------------- |
|
||||
| `.Status` | `string` | player status (`playing`, `paused`, `stopped`) |
|
||||
| `.Artist` | `string` | current artist |
|
||||
| `.Track` | `string` | current track |
|
||||
| `.Full` | `string` | will output `Artist - Track` |
|
||||
| `.Icon` | `string` | icon (based on `.Status`) |
|
||||
|
||||
[templates]: /docs/configuration/templates
|
||||
[lastfm]: https://www.last.fm
|
||||
[api-key]: https://www.last.fm/api/account/create
|
|
@ -88,6 +88,7 @@ module.exports = {
|
|||
"segments/julia",
|
||||
"segments/kotlin",
|
||||
"segments/kubectl",
|
||||
"segments/lastfm",
|
||||
"segments/lua",
|
||||
"segments/mercurial",
|
||||
"segments/nbgv",
|
||||
|
|
Loading…
Reference in a new issue