diff --git a/lfcircle.py b/lfcircle.py
index 8a853ee..2b1dda0 100644
--- a/lfcircle.py
+++ b/lfcircle.py
@@ -32,6 +32,7 @@ For more information, please refer to
from argparse import ArgumentParser
from bisect import insort
+from collections import Counter
from datetime import datetime, timedelta
from enum import Enum
from functools import wraps
@@ -239,6 +240,7 @@ class ListeningReport(NamedTuple):
albums_top_new: ThingWithScrobbles
tracks_top_new: ThingWithScrobbles
listening_time_hours: int
+ tags: dict[str, tuple[int, ...]]
def to_str(
self,
@@ -262,7 +264,8 @@ class ListeningReport(NamedTuple):
+ f"{self.user} — Σ{self.listening_time_hours}h; {self.scrobbles_daily_avg}s/d "
)
basket.append(
- indent(f"<{self.url}>", prefix=(prefix := " " * len(_prefix))) + "\n"
+ indent(f"<{self.url}>", prefix=(prefix := " " * len(_prefix)))
+ + "\n"
)
rmax = len(f"#{leaderboard_n}")
@@ -320,6 +323,29 @@ class ListeningReport(NamedTuple):
)
basket.append(d4_l + d4_r + d4_url)
+ # detail 5: top five tags
+ if len(self.tags) > 0:
+ tag_counter = Counter[str]()
+
+ for tag_name, tag_values in self.tags.items():
+ tag_counter.update({tag_name: sum(tag_values)})
+
+ basket.append(
+ indent(
+ (
+ "Top tags".ljust(len(d4_l) - 6)
+ + " : "
+ + ", ".join(
+ [
+ f"{tn} ({tc})"
+ for tn, tc in tag_counter.most_common(5)
+ ]
+ )
+ ),
+ prefix=prefix,
+ )
+ )
+
if not behaviour.lowercase:
text = "\n".join(basket)
@@ -340,7 +366,7 @@ class ListeningReport(NamedTuple):
# detail 2: total period artist count
basket.append(
- f"{prefix}{self.artists_count} artists (#{leaderboard_artists_pos}): "
+ f"\n{prefix}{self.artists_count} artists (#{leaderboard_artists_pos}): "
+ (
f"[{self.artists[0].name}]({self.artists[0].url})"
if behaviour.all_the_links
@@ -370,6 +396,21 @@ class ListeningReport(NamedTuple):
)
)
+ # detail 5: top tags
+ if len(self.tags) > 0:
+ tag_counter = Counter[str]()
+
+ for tag_name, tag_values in self.tags.items():
+ tag_counter.update({tag_name: sum(tag_values)})
+
+ basket.append(
+ f"\n{prefix}Top tags"
+ + ": "
+ + ", ".join(
+ [f"{tn} ({tc})" for tn, tc in tag_counter.most_common(5)]
+ )
+ )
+
if not behaviour.lowercase:
text = "\n".join(basket)
@@ -392,7 +433,6 @@ def get_listening_report(
limiter: Limiter,
behaviour: Behaviour,
) -> ListeningReport:
-
target_url: str = f"https://www.last.fm/user/{target}/listening-report/week"
page_res: Response = limiter.limit(get)(
@@ -422,6 +462,7 @@ def get_listening_report(
albums_top_new=_get_albums_top_new(page),
tracks_top_new=_get_tracks_top_new(page),
listening_time_hours=_get_listening_time_hours(page),
+ tags=_get_tags(page),
)
@@ -520,7 +561,9 @@ def _get_top_overview(
if len(top.select(select_needle)) == 0:
continue
- assert (_n := top.select(".listening-report-secondary-top-item-name")) is not None
+ assert (
+ _n := top.select(".listening-report-secondary-top-item-name")
+ ) is not None
assert (
_v := top.select(".listening-report-secondary-top-item-value")
) is not None
@@ -597,6 +640,34 @@ def _get_tracks_top_new(page: BeautifulSoup) -> ThingWithScrobbles:
return _get_top_new_thing(page=page, select_needle=".top-new-item-type__track")
+def _get_tags(page: BeautifulSoup) -> dict[str, tuple[int, ...]]:
+ tags: dict[str, tuple[int, ...]] = {}
+
+ assert (_1 := page.select_one("#top-tags-over-time")) is not None
+ assert (_2 := _1.select_one(".js-top-tags-over-time-table")) is not None
+ assert (_3 := _2.select_one("tbody")) is not None
+
+ for tr in _3.select("tr"):
+ tag_name: str = ""
+ tag_counts: list[int] = []
+
+ for i, td in enumerate(tr.select("td")):
+ if i == 0:
+ tag_name = td.text.strip()
+
+ else:
+ assert (
+ num := td.text.strip()
+ ).isnumeric(), (
+ "second tag table td onwards should be numeric, but isn't"
+ )
+ tag_counts.append(int(num))
+
+ tags.update({tag_name: tuple(tag_counts)})
+
+ return tags
+
+
def _rank(
r: ListeningReport, rs: list[ListeningReport], k: Callable[[ListeningReport], int]
):
diff --git a/poetry.lock b/poetry.lock
index 624b7eb..fc40316 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
[[package]]
name = "beautifulsoup4"
@@ -330,13 +330,13 @@ files = [
[[package]]
name = "platformdirs"
-version = "4.2.1"
+version = "4.2.2"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
optional = false
python-versions = ">=3.8"
files = [
- {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"},
- {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"},
+ {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"},
+ {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"},
]
[package.extras]
@@ -346,13 +346,13 @@ type = ["mypy (>=1.8)"]
[[package]]
name = "requests"
-version = "2.31.0"
+version = "2.32.2"
description = "Python HTTP for Humans."
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
- {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
+ {file = "requests-2.32.2-py3-none-any.whl", hash = "sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c"},
+ {file = "requests-2.32.2.tar.gz", hash = "sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289"},
]
[package.dependencies]
@@ -400,13 +400,13 @@ files = [
[[package]]
name = "types-beautifulsoup4"
-version = "4.12.0.20240504"
+version = "4.12.0.20240511"
description = "Typing stubs for beautifulsoup4"
optional = false
python-versions = ">=3.8"
files = [
- {file = "types-beautifulsoup4-4.12.0.20240504.tar.gz", hash = "sha256:d7b7af4ccc52fc22784d33a529695e34329a9bdd5db6a649c9b25cb2c3a148d5"},
- {file = "types_beautifulsoup4-4.12.0.20240504-py3-none-any.whl", hash = "sha256:84e04e4473b3c79da04d9d1f89d20a38e5cc92f9c080a8e1f9d36a287b350465"},
+ {file = "types-beautifulsoup4-4.12.0.20240511.tar.gz", hash = "sha256:004f6096fdd83b19cdbf6cb10e4eae57b10205eccc365d0a69d77da836012e28"},
+ {file = "types_beautifulsoup4-4.12.0.20240511-py3-none-any.whl", hash = "sha256:7ceda66a93ba28d759d5046d7fec9f4cad2f563a77b3a789efc90bcadafeefd1"},
]
[package.dependencies]
@@ -425,13 +425,13 @@ files = [
[[package]]
name = "types-requests"
-version = "2.31.0.20240406"
+version = "2.32.0.20240523"
description = "Typing stubs for requests"
optional = false
python-versions = ">=3.8"
files = [
- {file = "types-requests-2.31.0.20240406.tar.gz", hash = "sha256:4428df33c5503945c74b3f42e82b181e86ec7b724620419a2966e2de604ce1a1"},
- {file = "types_requests-2.31.0.20240406-py3-none-any.whl", hash = "sha256:6216cdac377c6b9a040ac1c0404f7284bd13199c0e1bb235f4324627e8898cf5"},
+ {file = "types-requests-2.32.0.20240523.tar.gz", hash = "sha256:26b8a6de32d9f561192b9942b41c0ab2d8010df5677ca8aa146289d11d505f57"},
+ {file = "types_requests-2.32.0.20240523-py3-none-any.whl", hash = "sha256:f19ed0e2daa74302069bbbbf9e82902854ffa780bc790742a810a9aaa52f65ec"},
]
[package.dependencies]
@@ -439,13 +439,13 @@ urllib3 = ">=2"
[[package]]
name = "typing-extensions"
-version = "4.11.0"
+version = "4.12.0"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
files = [
- {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"},
- {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"},
+ {file = "typing_extensions-4.12.0-py3-none-any.whl", hash = "sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594"},
+ {file = "typing_extensions-4.12.0.tar.gz", hash = "sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8"},
]
[[package]]
diff --git a/pyproject.toml b/pyproject.toml
index 9fe1240..7ab2092 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "lfcircle"
-version = "0.1.2"
+version = "0.1.3"
description = "last.fm statistics generator for your friend circle!"
authors = ["Mark Joshwel "]
license = "Unlicense"