Skip to content

Commit 3b2d92b

Browse files
authored
Merge pull request CloudBotIRC#182 from linuxdaemon/gonzobot+spotify-cleanup
Clean up of Spotify plugin
2 parents 812de2b + 3604c8b commit 3b2d92b

File tree

1 file changed

+117
-106
lines changed

1 file changed

+117
-106
lines changed

plugins/spotify.py

Lines changed: 117 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,146 +1,157 @@
11
import re
2-
from datetime import datetime
2+
from datetime import datetime, timedelta
3+
from threading import RLock
34

45
import requests
56
from requests import HTTPError
67
from requests.auth import HTTPBasicAuth
8+
from yarl import URL
79

810
from cloudbot import hook
911

10-
api_url = "https://api.spotify.com/v1/search?"
11-
token_url = "https://accounts.spotify.com/api/token"
12-
spuri = 'spotify:{}:{}'
13-
access_token = ""
14-
expires_at = datetime.min
15-
16-
spotify_re = re.compile(r'(spotify:(track|album|artist|user):([a-zA-Z0-9]+))',
17-
re.I)
18-
http_re = re.compile(r'(open\.spotify\.com/(track|album|artist|user)/'
19-
'([a-zA-Z0-9]+))', re.I)
20-
21-
22-
def sprequest(bot, params, alturl=None):
23-
global access_token, expires_at
24-
if alturl is None:
25-
alturl = api_url
26-
if datetime.now() >= expires_at:
27-
basic_auth = HTTPBasicAuth(
28-
bot.config.get("api_keys", {}).get("spotify_client_id"),
29-
bot.config.get("api_keys", {}).get("spotify_client_secret"))
30-
gtcc = {"grant_type": "client_credentials"}
31-
auth = requests.post(token_url, data=gtcc, auth=basic_auth).json()
32-
if 'access_token' in auth.keys():
33-
access_token = auth["access_token"]
34-
expires_at = datetime.fromtimestamp(datetime.now().timestamp() +
35-
auth["expires_in"])
36-
headers = {'Authorization': 'Bearer ' + access_token}
37-
return requests.get(alturl, params=params, headers=headers)
12+
api = None
3813

14+
spotify_re = re.compile(
15+
r'(spotify:(track|album|artist|user):([a-zA-Z0-9]+))', re.I
16+
)
17+
http_re = re.compile(
18+
r'(open\.spotify\.com/(track|album|artist|user)/([a-zA-Z0-9]+))', re.I
19+
)
3920

40-
@hook.command('spotify', 'sptrack')
41-
def spotify(bot, text, reply):
42-
"""<song> - Search Spotify for <song>"""
43-
params = {"q": text.strip(), "offset": 0, "limit": 1, "type": "track"}
21+
TYPE_MAP = {
22+
'artist': 'artists',
23+
'album': 'albums',
24+
'track': 'tracks',
25+
}
26+
27+
28+
class SpotifyAPI:
29+
api_url = URL("https://api.spotify.com/v1")
30+
token_refresh_url = URL("https://accounts.spotify.com/api/token")
31+
32+
def __init__(self, client_id=None, client_secret=None):
33+
self._client_id = client_id
34+
self._client_secret = client_secret
35+
self._access_token = None
36+
self._token_expires = datetime.min
37+
self._lock = RLock() # Make sure only one requests is parsed at a time
38+
39+
def request(self, endpoint, params=None):
40+
with self._lock:
41+
if datetime.now() >= self._token_expires:
42+
self._refresh_token()
4443

45-
request = sprequest(bot, params)
44+
r = requests.get(
45+
self.api_url / endpoint, params=params, headers={'Authorization': 'Bearer ' + self._access_token}
46+
)
47+
r.raise_for_status()
48+
49+
return r
50+
51+
def search(self, params):
52+
return self.request('search', params)
53+
54+
def _refresh_token(self):
55+
with self._lock:
56+
basic_auth = HTTPBasicAuth(self._client_id, self._client_secret)
57+
gtcc = {"grant_type": "client_credentials"}
58+
r = requests.post(self.token_refresh_url, data=gtcc, auth=basic_auth)
59+
r.raise_for_status()
60+
auth = r.json()
61+
self._access_token = auth["access_token"]
62+
self._token_expires = datetime.now() + timedelta(seconds=auth["expires_in"])
63+
64+
65+
def _search(text, _type, reply):
66+
params = {"q": text.strip(), "offset": 0, "limit": 1, "type": _type}
4667

4768
try:
48-
request.raise_for_status()
69+
request = api.search(params)
4970
except HTTPError as e:
5071
reply("Could not get track information: {}".format(e.request.status_code))
5172
raise
5273

53-
if request.status_code != requests.codes.ok:
54-
return "Could not get track information: {}".format(
55-
request.status_code)
74+
return request.json()[TYPE_MAP[_type]]["items"][0]
5675

57-
data = request.json()["tracks"]["items"][0]
5876

59-
try:
60-
return "\x02{}\x02 by \x02{}\x02 - {} / {}".format(
61-
data["artists"][0]["name"], data["external_urls"]["spotify"],
62-
data["name"], data["uri"])
63-
except IndexError:
64-
return "Unable to find any tracks!"
77+
def _do_format(data, _type):
78+
name = data["name"]
79+
if _type == "track":
80+
artist = data["artists"][0]["name"]
81+
album = data["album"]["name"]
6582

83+
return "Spotify Track", "\x02{}\x02 by \x02{}\x02 from the album \x02{}\x02".format(name, artist, album)
84+
elif _type == "artist":
85+
return "Spotify Artist", "\x02{}\x02, followers: \x02{}\x02, genres: \x02{}\x02".format(
86+
name, data["followers"]["total"], ', '.join(data["genres"])
87+
)
88+
elif _type == "album":
89+
return "Spotify Album", "\x02{}\x02 - \x02{}\x02".format(data["artists"][0]["name"], name)
90+
else:
91+
raise ValueError("Attempt to format unknown Spotify API type: " + _type)
6692

67-
@hook.command("spalbum")
68-
def spalbum(bot, text, reply):
69-
"""<album> - Search Spotify for <album>"""
70-
params = {"q": text.strip(), "offset": 0, "limit": 1, "type": "album"}
7193

72-
request = sprequest(bot, params)
94+
def _format_response(data, _type, show_pre=False, show_url=False, show_uri=False):
95+
pre, text = _do_format(data, _type)
96+
if show_pre:
97+
out = pre + ": "
98+
else:
99+
out = ""
73100

74-
try:
75-
request.raise_for_status()
76-
except HTTPError as e:
77-
reply("Could not get album information: {}".format(e.request.status_code))
78-
raise
101+
out += text
79102

80-
if request.status_code != requests.codes.ok:
81-
return "Could not get album information: {}".format(
82-
request.status_code)
103+
if show_uri or show_url:
104+
out += ' -'
83105

84-
data = request.json()["albums"]["items"][0]
106+
if show_url:
107+
out += ' ' + data["external_urls"]["spotify"]
85108

86-
try:
87-
return "\x02{}\x02 by \x02{}\x02 - {} / {}".format(
88-
data["artists"][0]["name"], data["name"],
89-
data["external_urls"]["spotify"], data["uri"])
90-
except IndexError:
91-
return "Unable to find any albums!"
109+
if show_uri:
110+
out += ' ' + "[{}]".format(data["uri"])
92111

112+
return out
93113

94-
@hook.command("spartist", "artist")
95-
def spartist(bot, text, reply):
96-
"""<artist> - Search Spotify for <artist>"""
97-
params = {"q": text.strip(), "offset": 0, "limit": 1, "type": "artist"}
98114

99-
request = sprequest(bot, params)
100-
try:
101-
request.raise_for_status()
102-
except HTTPError as e:
103-
reply("Could not get artist information: {}".format(e.request.status_code))
104-
raise
115+
def _format_search(text, _type, reply):
116+
data = _search(text, _type, reply)
117+
return _format_response(data, _type, show_url=True, show_uri=True)
105118

106-
if request.status_code != requests.codes.ok:
107-
return "Could not get artist information: {}".format(
108-
request.status_code)
109119

110-
data = request.json()["artists"]["items"][0]
120+
@hook.onload
121+
def create_api(bot):
122+
keys = bot.config['api_keys']
123+
client_id = keys['spotify_client_id']
124+
client_secret = keys['spotify_client_secret']
125+
global api
126+
api = SpotifyAPI(client_id, client_secret)
111127

112-
try:
113-
return "\x02{}\x02 - {} / {}".format(
114-
data["name"], data["external_urls"]["spotify"], data["uri"])
115-
except IndexError:
116-
return "Unable to find any artists!"
128+
129+
@hook.command('spotify', 'sptrack')
130+
def spotify(text, reply):
131+
"""<song> - Search Spotify for <song>"""
132+
return _format_search(text, "track", reply)
133+
134+
135+
@hook.command("spalbum")
136+
def spalbum(text, reply):
137+
"""<album> - Search Spotify for <album>"""
138+
return _format_search(text, "album", reply)
139+
140+
141+
@hook.command("spartist", "artist")
142+
def spartist(text, reply):
143+
"""<artist> - Search Spotify for <artist>"""
144+
return _format_search(text, "artist", reply)
117145

118146

119147
@hook.regex(http_re)
120148
@hook.regex(spotify_re)
121-
def spotify_url(bot, match):
122-
api_method = {'track': 'tracks', 'album': 'albums', 'artist': 'artists'}
149+
def spotify_url(match):
123150
_type = match.group(2)
124151
spotify_id = match.group(3)
125-
# no error catching here, if the API is down fail silently
126-
request = sprequest(bot, {}, 'http://api.spotify.com/v1/{}/{}'.format(
127-
api_method[_type], spotify_id))
128-
request.raise_for_status()
152+
153+
request = api.request("{}/{}".format(TYPE_MAP[_type], spotify_id))
154+
129155
data = request.json()
130-
if _type == "track":
131-
name = data["name"]
132-
artist = data["artists"][0]["name"]
133-
album = data["album"]["name"]
134-
url = data['external_urls']['spotify']
135-
uri = data['uri']
136156

137-
return "Spotify Track: \x02{}\x02 by \x02{}\x02 from the album \x02{}\x02 {} [{}]".format(
138-
name, artist, album, url, uri)
139-
elif _type == "artist":
140-
return "Spotify Artist: \x02{}\x02, followers: \x02{}\x02, genres: \x02{}\x02".format(
141-
data["name"], data["followers"]["total"],
142-
', '.join(data["genres"]))
143-
elif _type == "album":
144-
return "Spotify Album: \x02{}\x02 - \x02{}\x02 {} [{}]".format(
145-
data["artists"][0]["name"], data["name"],
146-
data['external_urls']['spotify'], data['uri'])
157+
return _format_response(data, _type, show_pre=True)

0 commit comments

Comments
 (0)