mirror of
https://github.com/Luzifer/twitch-bot.git
synced 2024-11-09 16:50:01 +00:00
[spotify] Add spotifyCurrentPlaying
template function
together with Spotify integration for the bot Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
caadb6b590
commit
7f4470e692
10 changed files with 412 additions and 1 deletions
|
@ -441,7 +441,22 @@ Example:
|
||||||
|
|
||||||
```
|
```
|
||||||
# Your int this hour: {{ printf "%.0f" (mulf (seededRandom (list "int" .username (now | date "2006-01-02 15") | join ":")) 100) }}%
|
# Your int this hour: {{ printf "%.0f" (mulf (seededRandom (list "int" .username (now | date "2006-01-02 15") | join ":")) 100) }}%
|
||||||
< Your int this hour: 66%
|
< Your int this hour: 88%
|
||||||
|
```
|
||||||
|
|
||||||
|
### `spotifyCurrentPlaying`
|
||||||
|
|
||||||
|
Retrieves the current playing track for the given channel
|
||||||
|
|
||||||
|
Syntax: `spotifyCurrentPlaying <channel>`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
! ^!spotify
|
||||||
|
> !spotify
|
||||||
|
# {{ spotifyCurrentPlaying .channel }}
|
||||||
|
* Beast in Black - Die By The Blade
|
||||||
```
|
```
|
||||||
|
|
||||||
### `streamUptime`
|
### `streamUptime`
|
||||||
|
|
52
docs/content/modules/spotify.md
Normal file
52
docs/content/modules/spotify.md
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
---
|
||||||
|
title: "Spotify Integration"
|
||||||
|
---
|
||||||
|
|
||||||
|
You are using Spotify and are tired of not working third-party overlays and chat commands? The bot has you covered with its Spotify integration. The integration can retrieve the current playing track and show that in templating as for example through the [EventClient]({{< ref "../overlays/eventclient.md" >}}) and its `renderTemplate` function or the `respond` actor in a rule.
|
||||||
|
|
||||||
|
## Setting up
|
||||||
|
|
||||||
|
You will need
|
||||||
|
|
||||||
|
- a Spotify account
|
||||||
|
- an instance of the bot with access to the configuration
|
||||||
|
|
||||||
|
For this documentation we assume your bots web-interface is available at `https://example.com/` and everywhere you see that below, you need to replace it with your own instance URL.
|
||||||
|
|
||||||
|
Start with going to the [Spotify for Developers Dashboard](https://developer.spotify.com/dashboard) and create a new app:
|
||||||
|
|
||||||
|
- "App name" is something you can choose yourself
|
||||||
|
- "App description" is also required, choose yourself
|
||||||
|
- "Redirect URI" must be `https://example.com/spotify/<channel>` so for exmaple `https://example.com/spotify/luziferus`
|
||||||
|
- Select "Web API" for the "API/SDKs you are planning to use"
|
||||||
|
- Check the ToS box (of course after reading those!)
|
||||||
|
- Click "Save"
|
||||||
|
- From the "Settings" button of your app get the "Client ID" and "Client secret" and note them down
|
||||||
|
- Optional: If you need to authorize multiple channels (i.e. for multiple users of the bot instance) you can edit the "Redirect URIs" on the "Settings" page and add more.
|
||||||
|
|
||||||
|
Now head into the configuration file and configure the Spotify module:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Module configuration by channel or defining bot-wide defaults. See
|
||||||
|
# module specific documentation for options to configure in this
|
||||||
|
# section. All modules come with internal defaults so there is no
|
||||||
|
# need to configure this but you can overwrite the internal defaults.
|
||||||
|
module_config:
|
||||||
|
spotify:
|
||||||
|
default:
|
||||||
|
clientId: 'put the client ID you noted down here'
|
||||||
|
clientSecret: 'put the secret here'
|
||||||
|
```
|
||||||
|
|
||||||
|
Now send the user which currently playing track should be displayed to the `https://example.com/spotify/<channel>` URL. So I for example would visit `https://example.com/spotify/luziferus`. They are redirected to Spotify, need to authorize the app and if everything went well the bot tells them "Spotify is now authorized for this channel, you can close this page".
|
||||||
|
|
||||||
|
Now you can for example add a new rule for the channel:
|
||||||
|
```yaml
|
||||||
|
- uuid: 0cd18de8-d70b-4651-a51a-3de1a2eb87c5
|
||||||
|
description: Spotify
|
||||||
|
actions:
|
||||||
|
- type: respond
|
||||||
|
attributes:
|
||||||
|
message: '{{ spotifyCurrentPlaying .channel }}'
|
||||||
|
match_message: '!spotify'
|
||||||
|
```
|
3
go.mod
3
go.mod
|
@ -26,6 +26,7 @@ require (
|
||||||
github.com/wzshiming/openapi v0.0.0-20200703171632-c7220b3c9cfb
|
github.com/wzshiming/openapi v0.0.0-20200703171632-c7220b3c9cfb
|
||||||
golang.org/x/crypto v0.19.0
|
golang.org/x/crypto v0.19.0
|
||||||
golang.org/x/net v0.21.0
|
golang.org/x/net v0.21.0
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be
|
||||||
gopkg.in/irc.v4 v4.0.0
|
gopkg.in/irc.v4 v4.0.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gorm.io/driver/mysql v1.5.4
|
gorm.io/driver/mysql v1.5.4
|
||||||
|
@ -51,6 +52,7 @@ require (
|
||||||
github.com/go-git/go-billy/v5 v5.5.0 // indirect
|
github.com/go-git/go-billy/v5 v5.5.0 // indirect
|
||||||
github.com/go-jose/go-jose/v3 v3.0.1 // indirect
|
github.com/go-jose/go-jose/v3 v3.0.1 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
|
github.com/golang/protobuf v1.3.1 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
|
@ -97,6 +99,7 @@ require (
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
golang.org/x/tools v0.18.0 // indirect
|
golang.org/x/tools v0.18.0 // indirect
|
||||||
|
google.golang.org/appengine v1.4.0 // indirect
|
||||||
gopkg.in/validator.v2 v2.0.1 // indirect
|
gopkg.in/validator.v2 v2.0.1 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
modernc.org/libc v1.41.0 // indirect
|
modernc.org/libc v1.41.0 // indirect
|
||||||
|
|
3
go.sum
3
go.sum
|
@ -90,6 +90,7 @@ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
@ -303,6 +304,7 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
@ -372,6 +374,7 @@ golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
|
65
internal/actors/spotify/client.go
Normal file
65
internal/actors/spotify/client.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package spotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getCurrentTrackForChannel(channel string) (track string, err error) {
|
||||||
|
channel = strings.TrimLeft(channel, "#")
|
||||||
|
|
||||||
|
conf, err := oauthConfig(channel, "")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("getting oauth config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var token *oauth2.Token
|
||||||
|
if err = db.ReadEncryptedCoreMeta(strings.Join([]string{"spotify-auth", channel}, ":"), &token); err != nil {
|
||||||
|
return "", fmt.Errorf("loading oauth token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), spotifyRequestTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.spotify.com/v1/me/player/currently-playing", nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("creating currently-playing request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := conf.Client(context.Background(), token).Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("executing request: %w", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := resp.Body.Close(); err != nil {
|
||||||
|
logrus.WithError(err).Error("closing Spotify response body (leaked fd)")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := db.StoreEncryptedCoreMeta(strings.Join([]string{"spotify-auth", channel}, ":"), token); err != nil {
|
||||||
|
logrus.WithError(err).Error("storing back Spotify auth token")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var payload currentPlayingTrackResponse
|
||||||
|
if err = json.NewDecoder(resp.Body).Decode(&payload); err != nil {
|
||||||
|
return "", fmt.Errorf("decoding response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var artistNames []string
|
||||||
|
for _, artist := range payload.Item.Artists {
|
||||||
|
artistNames = append(artistNames, artist.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join([]string{
|
||||||
|
strings.Join(artistNames, ", "),
|
||||||
|
payload.Item.Name,
|
||||||
|
}, " - "), nil
|
||||||
|
}
|
76
internal/actors/spotify/http.go
Normal file
76
internal/actors/spotify/http.go
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
package spotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const spotifyRequestTimeout = 2 * time.Second
|
||||||
|
|
||||||
|
func handleStartAuth(w http.ResponseWriter, r *http.Request) {
|
||||||
|
channel := mux.Vars(r)["channel"]
|
||||||
|
|
||||||
|
redirURL := baseURL.ResolveReference(&url.URL{Path: r.URL.Path})
|
||||||
|
conf, err := oauthConfig(channel, strings.Split(redirURL.String(), "?")[0])
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("getting Spotify oauth config")
|
||||||
|
http.Error(w, "unable to get Spotify config for this channel", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
code := r.URL.Query().Get("code")
|
||||||
|
if code == "" {
|
||||||
|
http.Redirect(
|
||||||
|
w, r,
|
||||||
|
conf.AuthCodeURL(fmt.Sprintf("%x", sha256.Sum256(append([]byte(conf.ClientID), []byte(channel)...)))),
|
||||||
|
http.StatusFound,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := conf.Exchange(r.Context(), r.URL.Query().Get("code"))
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("getting Spotify oauth token")
|
||||||
|
http.Error(w, "unable to get Spotify auth token", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = db.StoreEncryptedCoreMeta(strings.Join([]string{"spotify-auth", channel}, ":"), token); err != nil {
|
||||||
|
logrus.WithError(err).Error("storing Spotify oauth token")
|
||||||
|
http.Error(w, "unable to store Spotify auth token", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(w, "Spotify is now authorized for this channel, you can close this page")
|
||||||
|
}
|
||||||
|
|
||||||
|
func oauthConfig(channel, redirectURL string) (conf *oauth2.Config, err error) {
|
||||||
|
clientID, err := getModuleConfig(actorName, channel).String("clientId")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting clientId for channel: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientSecret, err := getModuleConfig(actorName, channel).String("clientSecret")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting clientSecret for channel: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oauth2.Config{
|
||||||
|
ClientID: clientID,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
Endpoint: oauth2.Endpoint{
|
||||||
|
AuthURL: "https://accounts.spotify.com/authorize",
|
||||||
|
TokenURL: "https://accounts.spotify.com/api/token",
|
||||||
|
},
|
||||||
|
RedirectURL: redirectURL,
|
||||||
|
Scopes: []string{"user-read-currently-playing"},
|
||||||
|
}, nil
|
||||||
|
}
|
68
internal/actors/spotify/spotify.go
Normal file
68
internal/actors/spotify/spotify.go
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
// Package spotify contains an actor to query the current playing
|
||||||
|
// track for a channel with authorized spotify account
|
||||||
|
package spotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/Luzifer/twitch-bot/v3/pkg/database"
|
||||||
|
"github.com/Luzifer/twitch-bot/v3/plugins"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
actorName = "spotify"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
baseURL *url.URL
|
||||||
|
db database.Connector
|
||||||
|
getModuleConfig plugins.ModuleConfigGetterFunc
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register provides the plugins.RegisterFunc
|
||||||
|
func Register(args plugins.RegistrationArguments) (err error) {
|
||||||
|
if baseURL, err = url.Parse(args.GetBaseURL()); err != nil {
|
||||||
|
return fmt.Errorf("parsing base-url: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db = args.GetDatabaseConnector()
|
||||||
|
getModuleConfig = args.GetModuleConfigForChannel
|
||||||
|
|
||||||
|
args.RegisterTemplateFunction(
|
||||||
|
"spotifyCurrentPlaying",
|
||||||
|
plugins.GenericTemplateFunctionGetter(getCurrentTrackForChannel),
|
||||||
|
plugins.TemplateFuncDocumentation{
|
||||||
|
Name: "spotifyCurrentPlaying",
|
||||||
|
Description: "Retrieves the current playing track for the given channel",
|
||||||
|
Syntax: "spotifyCurrentPlaying <channel>",
|
||||||
|
Example: &plugins.TemplateFuncDocumentationExample{
|
||||||
|
MatchMessage: "^!spotify",
|
||||||
|
MessageContent: "!spotify",
|
||||||
|
Template: "{{ spotifyCurrentPlaying .channel }}",
|
||||||
|
FakedOutput: "Beast in Black - Die By The Blade",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err = args.RegisterAPIRoute(plugins.HTTPRouteRegistrationArgs{
|
||||||
|
Description: "Starts authorization of an Spotify Account for a {channel}",
|
||||||
|
HandlerFunc: handleStartAuth,
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Module: actorName,
|
||||||
|
Name: "Authorize Spotify Account",
|
||||||
|
Path: "/{channel}",
|
||||||
|
RequiresWriteAuth: false,
|
||||||
|
ResponseType: plugins.HTTPRouteResponseTypeTextPlain,
|
||||||
|
RouteParams: []plugins.HTTPRouteParamDocumentation{
|
||||||
|
{
|
||||||
|
Description: "Channel to authorize the Account for",
|
||||||
|
Name: "channel",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
return fmt.Errorf("registering API route: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
124
internal/actors/spotify/struct.go
Normal file
124
internal/actors/spotify/struct.go
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
package spotify
|
||||||
|
|
||||||
|
type (
|
||||||
|
currentPlayingTrackResponse struct {
|
||||||
|
Device struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
IsPrivateSession bool `json:"is_private_session"`
|
||||||
|
IsRestricted bool `json:"is_restricted"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
VolumePercent int `json:"volume_percent"`
|
||||||
|
SupportsVolume bool `json:"supports_volume"`
|
||||||
|
} `json:"device"`
|
||||||
|
RepeatState string `json:"repeat_state"`
|
||||||
|
ShuffleState bool `json:"shuffle_state"`
|
||||||
|
Context struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Href string `json:"href"`
|
||||||
|
ExternalUrls struct {
|
||||||
|
Spotify string `json:"spotify"`
|
||||||
|
} `json:"external_urls"`
|
||||||
|
URI string `json:"uri"`
|
||||||
|
} `json:"context"`
|
||||||
|
Timestamp int `json:"timestamp"`
|
||||||
|
ProgressMs int `json:"progress_ms"`
|
||||||
|
IsPlaying bool `json:"is_playing"`
|
||||||
|
Item struct {
|
||||||
|
Album struct {
|
||||||
|
AlbumType string `json:"album_type"`
|
||||||
|
TotalTracks int `json:"total_tracks"`
|
||||||
|
AvailableMarkets []string `json:"available_markets"`
|
||||||
|
ExternalUrls struct {
|
||||||
|
Spotify string `json:"spotify"`
|
||||||
|
} `json:"external_urls"`
|
||||||
|
Href string `json:"href"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Images []struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
Width int `json:"width"`
|
||||||
|
} `json:"images"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ReleaseDate string `json:"release_date"`
|
||||||
|
ReleaseDatePrecision string `json:"release_date_precision"`
|
||||||
|
Restrictions struct {
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
} `json:"restrictions"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
URI string `json:"uri"`
|
||||||
|
Artists []struct {
|
||||||
|
ExternalUrls struct {
|
||||||
|
Spotify string `json:"spotify"`
|
||||||
|
} `json:"external_urls"`
|
||||||
|
Href string `json:"href"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
URI string `json:"uri"`
|
||||||
|
} `json:"artists"`
|
||||||
|
} `json:"album"`
|
||||||
|
Artists []struct {
|
||||||
|
ExternalUrls struct {
|
||||||
|
Spotify string `json:"spotify"`
|
||||||
|
} `json:"external_urls"`
|
||||||
|
Followers struct {
|
||||||
|
Href string `json:"href"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
} `json:"followers"`
|
||||||
|
Genres []string `json:"genres"`
|
||||||
|
Href string `json:"href"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Images []struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
Width int `json:"width"`
|
||||||
|
} `json:"images"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Popularity int `json:"popularity"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
URI string `json:"uri"`
|
||||||
|
} `json:"artists"`
|
||||||
|
AvailableMarkets []string `json:"available_markets"`
|
||||||
|
DiscNumber int `json:"disc_number"`
|
||||||
|
DurationMs int `json:"duration_ms"`
|
||||||
|
Explicit bool `json:"explicit"`
|
||||||
|
ExternalIDs struct {
|
||||||
|
Isrc string `json:"isrc"`
|
||||||
|
Ean string `json:"ean"`
|
||||||
|
Upc string `json:"upc"`
|
||||||
|
} `json:"external_ids"`
|
||||||
|
ExternalUrls struct {
|
||||||
|
Spotify string `json:"spotify"`
|
||||||
|
} `json:"external_urls"`
|
||||||
|
Href string `json:"href"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
IsPlayable bool `json:"is_playable"`
|
||||||
|
LinkedFrom struct{} `json:"linked_from"`
|
||||||
|
Restrictions struct {
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
} `json:"restrictions"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Popularity int `json:"popularity"`
|
||||||
|
PreviewURL string `json:"preview_url"`
|
||||||
|
TrackNumber int `json:"track_number"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
URI string `json:"uri"`
|
||||||
|
IsLocal bool `json:"is_local"`
|
||||||
|
} `json:"item"`
|
||||||
|
CurrentlyPlayingType string `json:"currently_playing_type"`
|
||||||
|
Actions struct {
|
||||||
|
InterruptingPlayback bool `json:"interrupting_playback"`
|
||||||
|
Pausing bool `json:"pausing"`
|
||||||
|
Resuming bool `json:"resuming"`
|
||||||
|
Seeking bool `json:"seeking"`
|
||||||
|
SkippingNext bool `json:"skipping_next"`
|
||||||
|
SkippingPrev bool `json:"skipping_prev"`
|
||||||
|
TogglingRepeatContext bool `json:"toggling_repeat_context"`
|
||||||
|
TogglingShuffle bool `json:"toggling_shuffle"`
|
||||||
|
TogglingRepeatTrack bool `json:"toggling_repeat_track"`
|
||||||
|
TransferringPlayback bool `json:"transferring_playback"`
|
||||||
|
} `json:"actions"`
|
||||||
|
}
|
||||||
|
)
|
|
@ -112,6 +112,8 @@ type (
|
||||||
FormatMessage MsgFormatter
|
FormatMessage MsgFormatter
|
||||||
// FrontendNotify is a way to send a notification to the frontend
|
// FrontendNotify is a way to send a notification to the frontend
|
||||||
FrontendNotify func(string)
|
FrontendNotify func(string)
|
||||||
|
// GetBaseURL returns the configured BaseURL for the bot
|
||||||
|
GetBaseURL func() string
|
||||||
// GetDatabaseConnector returns an active database.Connector to access the backend storage database
|
// GetDatabaseConnector returns an active database.Connector to access the backend storage database
|
||||||
GetDatabaseConnector func() database.Connector
|
GetDatabaseConnector func() database.Connector
|
||||||
// GetLogger returns a sirupsen log.Entry pre-configured with the module name
|
// GetLogger returns a sirupsen log.Entry pre-configured with the module name
|
||||||
|
|
|
@ -33,6 +33,7 @@ import (
|
||||||
"github.com/Luzifer/twitch-bot/v3/internal/actors/respond"
|
"github.com/Luzifer/twitch-bot/v3/internal/actors/respond"
|
||||||
"github.com/Luzifer/twitch-bot/v3/internal/actors/shield"
|
"github.com/Luzifer/twitch-bot/v3/internal/actors/shield"
|
||||||
"github.com/Luzifer/twitch-bot/v3/internal/actors/shoutout"
|
"github.com/Luzifer/twitch-bot/v3/internal/actors/shoutout"
|
||||||
|
"github.com/Luzifer/twitch-bot/v3/internal/actors/spotify"
|
||||||
"github.com/Luzifer/twitch-bot/v3/internal/actors/stopexec"
|
"github.com/Luzifer/twitch-bot/v3/internal/actors/stopexec"
|
||||||
"github.com/Luzifer/twitch-bot/v3/internal/actors/timeout"
|
"github.com/Luzifer/twitch-bot/v3/internal/actors/timeout"
|
||||||
"github.com/Luzifer/twitch-bot/v3/internal/actors/variables"
|
"github.com/Luzifer/twitch-bot/v3/internal/actors/variables"
|
||||||
|
@ -95,6 +96,7 @@ var (
|
||||||
numeric.Register,
|
numeric.Register,
|
||||||
random.Register,
|
random.Register,
|
||||||
slice.Register,
|
slice.Register,
|
||||||
|
spotify.Register,
|
||||||
strings.Register,
|
strings.Register,
|
||||||
subscriber.Register,
|
subscriber.Register,
|
||||||
twitchFns.Register,
|
twitchFns.Register,
|
||||||
|
@ -159,6 +161,7 @@ func getRegistrationArguments() plugins.RegistrationArguments {
|
||||||
return plugins.RegistrationArguments{
|
return plugins.RegistrationArguments{
|
||||||
FormatMessage: formatMessage,
|
FormatMessage: formatMessage,
|
||||||
FrontendNotify: func(mt string) { frontendNotifyHooks.Ping(mt) },
|
FrontendNotify: func(mt string) { frontendNotifyHooks.Ping(mt) },
|
||||||
|
GetBaseURL: func() string { return cfg.BaseURL },
|
||||||
GetDatabaseConnector: func() database.Connector { return db },
|
GetDatabaseConnector: func() database.Connector { return db },
|
||||||
GetLogger: func(moduleName string) *log.Entry { return log.WithField("module", moduleName) },
|
GetLogger: func(moduleName string) *log.Entry { return log.WithField("module", moduleName) },
|
||||||
GetTwitchClient: func() *twitch.Client { return twitchClient },
|
GetTwitchClient: func() *twitch.Client { return twitchClient },
|
||||||
|
|
Loading…
Reference in a new issue