Skip to content

Commit 1efa550

Browse files
authored
Merge pull request #133 from TotallyNotRobots/gonzobot+lastfm-privacy
Remove the ability to look up another user's last.fm
2 parents 68c6903 + 4c7b30a commit 1efa550

File tree

3 files changed

+218
-31
lines changed

3 files changed

+218
-31
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
### Changed
1212
- Refactor tests to remove dependency on mock library
1313
- Change link_announcer.py to only warn on connection errors
14+
- Change user lookup logic in last.fm plugin
1415
### Fixed
1516
- Fix matching exception in horoscope test
1617
- Fix youtube.py ISO time parse

plugins/lastfm.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222

2323

2424
def format_user(user):
25+
"""
26+
>>> format_user('someuser')
27+
's\u200bomeuser'
28+
"""
2529
return '\u200B'.join((user[:1], user[1:]))
2630

2731

@@ -32,6 +36,10 @@ def filter_tags(tags, artist, limit=4):
3236
* All lowercase
3337
* Artist name removed
3438
* Blacklist of words removed
39+
40+
>>> filter_tags(['sometag', 'artist', 'seen live', 'Some RaNDoM tAG',
41+
... 'tag5', 'tag6', 'tag7'], 'artist')
42+
['sometag', 'some random tag', 'tag5', 'tag6']
3543
"""
3644

3745
# We never want to see these specific tags
@@ -49,15 +57,15 @@ def filter_tags(tags, artist, limit=4):
4957
artist = artist.translate(translator).lower()
5058

5159
# Perform the actual filtering, stop when we reach the desired number of tags
52-
count = 0
5360
filtered_tags = []
5461
for tag in tags:
55-
if not tag == artist:
56-
if tag not in blacklist:
57-
filtered_tags.append(tag)
58-
count += 1
59-
if count == limit:
60-
return filtered_tags
62+
if not tag == artist and tag not in blacklist:
63+
filtered_tags.append(tag)
64+
65+
if len(filtered_tags) >= limit:
66+
break
67+
68+
return filtered_tags
6169

6270

6371
last_cache = {}
@@ -116,7 +124,7 @@ def get_tags(method, artist, **params):
116124
for item in tags['toptags']['tag']:
117125
tag_list.append(item['name'])
118126

119-
tag_list = filter_tags(tag_list, artist, limit=4)
127+
tag_list = filter_tags(tag_list, artist)
120128

121129
return ', '.join(tag_list) if tag_list else 'no tags'
122130

@@ -165,27 +173,21 @@ def getartistinfo(artist, user=''):
165173
return artist
166174

167175

168-
def check_key_and_user(nick, text, lookup=False):
176+
def check_key_and_user(nick, text):
169177
"""
170178
Verify an API key is set and perform basic user lookups
171179
172180
Used as a prerequisite for multiple API commands
181+
173182
:param nick: The nick of the calling user
174183
:param text: The text passed to the command, possibly a different username to use
175-
:param lookup: Whether to look up `text` as another user's nick in the user table
176184
:return: The parsed username and any error message that occurred
177185
"""
178186
api_key = bot.config.get_api_key("lastfm")
179187
if not api_key:
180188
return None, "Error: No API key set."
181189

182-
if text:
183-
if lookup:
184-
username = get_account(text, text)
185-
else:
186-
username = text
187-
else:
188-
username = get_account(nick)
190+
username = text or get_account(nick)
189191

190192
if not username:
191193
return None, "No last.fm username specified and no last.fm username is set in the database."
@@ -194,7 +196,7 @@ def check_key_and_user(nick, text, lookup=False):
194196

195197

196198
def _topartists(text, nick, period=None, limit=10):
197-
username, err = check_key_and_user(nick, text, True)
199+
username, err = check_key_and_user(nick, text)
198200
if err:
199201
return err
200202

@@ -422,7 +424,7 @@ def lastfmcompare(text, nick):
422424
@hook.command("ltop", "ltt", autohelp=False)
423425
def toptrack(text, nick):
424426
"""[username] - Grabs a list of the top tracks for a last.fm username"""
425-
username, err = check_key_and_user(nick, text, True)
427+
username, err = check_key_and_user(nick, text)
426428
if err:
427429
return err
428430

tests/plugin_tests/test_lastfm.py

Lines changed: 196 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from cloudbot.bot import bot
99
from cloudbot.config import Config
10+
from plugins import lastfm
1011
from plugins.lastfm import api_request
1112

1213

@@ -20,37 +21,220 @@ def __init__(self, config):
2021
self.config = MockConfig(self, config)
2122

2223

24+
def test_get_account(mock_db):
25+
lastfm.table.create(mock_db.engine)
26+
mock_db.add_row(lastfm.table, nick="foo", acc="bar")
27+
lastfm.load_cache(mock_db.session())
28+
29+
assert lastfm.get_account("foo", "baz") == "bar"
30+
assert lastfm.get_account("FOO", "baz") == "bar"
31+
assert lastfm.get_account("foo1", "baz") == "baz"
32+
assert lastfm.get_account("foo1", "baa") == "baa"
33+
34+
2335
def test_api(unset_bot):
2436
bot.set(MockBot({"api_keys": {"lastfm": "hunter20"}}))
2537

2638
with RequestsMock() as reqs:
2739
with pytest.raises(requests.ConnectionError):
28-
api_request('track.getTopTags')
40+
api_request("track.getTopTags")
2941

30-
reqs.add(
31-
reqs.GET, "http://ws.audioscrobbler.com/2.0/",
32-
json={"data": "thing"}
33-
)
42+
reqs.add(reqs.GET, "http://ws.audioscrobbler.com/2.0/", json={"data": "thing"})
3443

35-
res, _ = api_request('track.getTopTags')
44+
res, _ = api_request("track.getTopTags")
3645

3746
assert res["data"] == "thing"
3847

3948
with RequestsMock() as reqs:
40-
reqs.add(
41-
reqs.GET, "http://ws.audioscrobbler.com/2.0/",
42-
body="<html></html>"
43-
)
49+
reqs.add(reqs.GET, "http://ws.audioscrobbler.com/2.0/", body="<html></html>")
4450

4551
with pytest.raises(JSONDecodeError):
4652
api_request("track.getTopTags")
4753

4854
with RequestsMock() as reqs:
4955
reqs.add(
50-
reqs.GET, "http://ws.audioscrobbler.com/2.0/",
56+
reqs.GET,
57+
"http://ws.audioscrobbler.com/2.0/",
5158
body="<html></html>",
52-
status=403
59+
status=403,
5360
)
5461

5562
with pytest.raises(HTTPError):
5663
api_request("track.getTopTags")
64+
65+
66+
def test_api_error_message(mock_requests, mock_api_keys):
67+
mock_requests.add(
68+
"GET",
69+
"http://ws.audioscrobbler.com/2.0/",
70+
json={"error": 10, "message": "Invalid API Key",},
71+
)
72+
73+
_, error = api_request("track.getTopTags")
74+
75+
assert error == "Error: Invalid API Key."
76+
77+
78+
def test_getartisttags(mock_requests, mock_api_keys):
79+
url = "http://ws.audioscrobbler.com/2.0/?format=json&artist=foobar&autocorrect=1&method=artist.getTopTags&api_key=APIKEY"
80+
mock_requests.add(
81+
"GET", url, json={"toptags": {},}, match_querystring=True,
82+
)
83+
res = lastfm.getartisttags("foobar")
84+
assert res == "no tags"
85+
86+
87+
class TestGetArtistTags:
88+
url = "http://ws.audioscrobbler.com/2.0/?format=json&artist=foobar&autocorrect=1&method=artist.getTopTags&api_key=APIKEY"
89+
90+
def get_tags(self):
91+
return lastfm.getartisttags("foobar")
92+
93+
def test_missing_tags(self, mock_requests, mock_api_keys):
94+
mock_requests.add(
95+
"GET", self.url, json={"toptags": {},}, match_querystring=True,
96+
)
97+
res = self.get_tags()
98+
assert res == "no tags"
99+
100+
def test_no_tags(self, mock_requests, mock_api_keys):
101+
mock_requests.add(
102+
"GET", self.url, json={"toptags": {"tags": []},}, match_querystring=True,
103+
)
104+
res = self.get_tags()
105+
assert res == "no tags"
106+
107+
def test_non_existent_artist(self, mock_requests, mock_api_keys):
108+
mock_requests.add(
109+
"GET",
110+
self.url,
111+
json={"error": 6, "message": "Missing artist."},
112+
match_querystring=True,
113+
)
114+
res = self.get_tags()
115+
assert res == "no tags"
116+
117+
def test_tags(self, mock_requests, mock_api_keys):
118+
mock_requests.add(
119+
"GET",
120+
self.url,
121+
json={
122+
"toptags": {
123+
"tag": [
124+
{"name": name}
125+
for name in [
126+
"foobar",
127+
"tag2",
128+
"seen live",
129+
"tag4",
130+
"tag5",
131+
"tag6",
132+
"tag7",
133+
]
134+
]
135+
},
136+
},
137+
match_querystring=True,
138+
)
139+
res = self.get_tags()
140+
assert res == "tag2, tag4, tag5, tag6"
141+
142+
143+
def test_gettracktags(mock_requests, mock_api_keys):
144+
url = "http://ws.audioscrobbler.com/2.0/?format=json&artist=foobar&autocorrect=1&track=foobaz&method=track.getTopTags&api_key=APIKEY"
145+
mock_requests.add(
146+
"GET", url, json={"toptags": {}}, match_querystring=True,
147+
)
148+
res = lastfm.gettracktags("foobar", "foobaz")
149+
assert res == "no tags"
150+
151+
152+
class TestCheckKeyAndUser:
153+
def test_text(self, mock_api_keys, mock_requests, mock_db):
154+
lastfm.table.create(mock_db.engine)
155+
mock_db.add_row(lastfm.table, nick="foo", acc="bar")
156+
lastfm.load_cache(mock_db.session())
157+
158+
res, err = lastfm.check_key_and_user("foo", "baz")
159+
assert err is None
160+
assert res == "baz"
161+
162+
def test_db_lookup(self, mock_api_keys, mock_requests, mock_db):
163+
lastfm.table.create(mock_db.engine)
164+
mock_db.add_row(lastfm.table, nick="foo", acc="bar")
165+
lastfm.load_cache(mock_db.session())
166+
167+
res, err = lastfm.check_key_and_user("foo", "")
168+
assert err is None
169+
assert res == "bar"
170+
171+
def test_missing_user(self, mock_api_keys, mock_requests, mock_db):
172+
lastfm.table.create(mock_db.engine)
173+
mock_db.add_row(lastfm.table, nick="foo", acc="bar")
174+
lastfm.load_cache(mock_db.session())
175+
176+
res, err = lastfm.check_key_and_user("foo1", "")
177+
assert res is None
178+
expected = "No last.fm username specified and no last.fm username is set in the database."
179+
assert err == expected
180+
181+
def test_no_key(self, mock_api_keys, mock_requests, mock_db):
182+
bot.config.get_api_key.return_value = None
183+
res, err = lastfm.check_key_and_user("foo", "baz")
184+
assert res is None
185+
assert err == "Error: No API key set."
186+
187+
188+
class TestTopArtists:
189+
def test_topweek_self(self, mock_api_keys, mock_requests, mock_db):
190+
lastfm.table.create(mock_db.engine)
191+
mock_db.add_row(lastfm.table, nick="foo", acc="bar")
192+
lastfm.load_cache(mock_db.session())
193+
194+
mock_requests.add(
195+
"GET",
196+
"http://ws.audioscrobbler.com/2.0/?format=json&user=bar&limit=10&period=7day&method=user.gettopartists&api_key=APIKEY",
197+
match_querystring=True,
198+
json={
199+
"topartists": {
200+
"artist": [
201+
{"name": "foo", "playcount": 5},
202+
{"name": "bar", "playcount": 2},
203+
]
204+
}
205+
},
206+
)
207+
208+
out = lastfm.topweek("", "foo")
209+
210+
assert out == "b\u200bar's favorite artists: foo [5] bar [2] "
211+
212+
213+
class TestTopTrack:
214+
def test_toptrack_self(self, mock_api_keys, mock_requests, mock_db):
215+
lastfm.table.create(mock_db.engine)
216+
mock_db.add_row(lastfm.table, nick="foo", acc="bar")
217+
lastfm.load_cache(mock_db.session())
218+
219+
mock_requests.add(
220+
"GET",
221+
"http://ws.audioscrobbler.com/2.0/?format=json&user=bar&limit=5&method=user.gettoptracks&api_key=APIKEY",
222+
match_querystring=True,
223+
json={
224+
"toptracks": {
225+
"track": [
226+
{
227+
"name": "some song",
228+
"artist": {"name": "some artist"},
229+
"playcount": 10,
230+
}
231+
]
232+
}
233+
},
234+
)
235+
236+
out = lastfm.toptrack("", "foo")
237+
238+
expected = "b\u200bar's favorite songs: some song by some artist listened to 10 times. "
239+
240+
assert out == expected

0 commit comments

Comments
 (0)