Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/lastfm.py (Python)

Code before:
        start_page_number = int_or_none(self._search_regex(
            r'\bpage=(\d+)', url, 'page', default=None)) or 1

        last_page_number = int_or_none(self._search_regex(
            r'>(\d+)</a>[^<]*</li>[^<]*<li[^>]+class="pagination-next', webpage, 'last_page', default=None))

        for page_number in range(start_page_number, (last_page_number or start_page_number) + 1):
            webpage = self._download_webpage(
                url, playlist_id,
                note='Downloading page %d%s' % (page_number, format_field(last_page_number, template=' of %d')),
                query={'page': page_number})
            page_entries = [
                self.url_result(player_url, 'Youtube')
                for player_url in set(re.findall(r'data-youtube-url="([^"]+)"', webpage))
            ]

            for e in page_entries:
                yield e

    def _real_extract(self, url):
        playlist_id = self._match_id(url)
        return self.playlist_result(self._entries(url, playlist_id), playlist_id)


class LastFMPlaylistIE(LastFMPlaylistBaseIE):
    _VALID_URL = r'https?://(?:www\.)?last\.fm/(music|tag)/(?P<id>[^/]+)/?[^/]+?$'
    _TESTS = [{
        'url': 'https://www.last.fm/music/Oasis/(What%27s+the+Story)+Morning+Glory%3F',
        'info_dict': {
            'id': 'Oasis',
        },
        'playlist_count': 11,
    }, {
        'url': 'https://www.last.fm/music/Oasis',
        'only_matching': True,
    }, {
        'url': 'https://www.last.fm/music/Oasis/+tracks',
        'only_matching': True,
    }, {
        'url': 'https://www.last.fm/tag/rock',
        'only_matching': True,
    }, {
        'url': 'https://www.last.fm/tag/rock/tracks',
        'only_matching': True,
    }]


class LastFMUserIE(LastFMPlaylistBaseIE):
    _VALID_URL = r'https?://(?:www\.)?last\.fm/user/[^/]+/playlists/(?P<id>[^/#?]+)'
    _TESTS = [{
        'url': 'https://www.last.fm/user/mehq/playlists/12319471',

Review comment (suggestion):
it should match
```
https://www.last.fm/music/Oasis/xxx
https://www.last.fm/music/Oasis
https://www.last.fm/music/Oasis/
https://www.last.fm/music/Oasis?ss
https://www.last.fm/music/Oasis#aa
https://www.last.fm/music/Oasis/?dd
https://www.last.fm/music/Oasis/xxx?aaa
```
but not
```
https://www.last.fm/music/Oasis/_/Wonderwall
```
right?
```suggestion
    _VALID_URL = r'https?://(?:www\.)?last\.fm/(music|tag)/(?P<id>[^/]+)(?:/[^/]+)?/?(?:[?#]|$)'
```

Code after:
        start_page_number = int_or_none(self._search_regex(
            r'\bpage=(\d+)', url, 'page', default=None)) or 1
        last_page_number = int_or_none(self._search_regex(
            r'>(\d+)</a>[^<]*</li>[^<]*<li[^>]+class="pagination-next', webpage, 'last_page', default=None))

        for page_number in range(start_page_number, (last_page_number or start_page_number) + 1):
            webpage = self._download_webpage(
                url, playlist_id,
                note='Downloading page %d%s' % (page_number, format_field(last_page_number, template=' of %d')),
                query={'page': page_number})
            page_entries = [
                self.url_result(player_url, 'Youtube')
                for player_url in set(re.findall(r'data-youtube-url="([^"]+)"', webpage))
            ]

            for e in page_entries:
                yield e

    def _real_extract(self, url):
        playlist_id = self._match_id(url)
        return self.playlist_result(self._entries(url, playlist_id), playlist_id)


class LastFMPlaylistIE(LastFMPlaylistBaseIE):
    _VALID_URL = r'https?://(?:www\.)?last\.fm/(music|tag)/(?P<id>[^/]+)(?:/[^/]+)?/?(?:[?#]|$)'
    _TESTS = [{
        'url': 'https://www.last.fm/music/Oasis/(What%27s+the+Story)+Morning+Glory%3F',
        'info_dict': {
            'id': 'Oasis',
        },
        'playlist_count': 11,
    }, {
        'url': 'https://www.last.fm/music/Oasis',
        'only_matching': True,
    }, {
        'url': 'https://www.last.fm/music/Oasis/',
        'only_matching': True,
    }, {
        'url': 'https://www.last.fm/music/Oasis?top_tracks_date_preset=ALL#top-tracks',
        'only_matching': True,
    }, {
        'url': 'https://www.last.fm/music/Oasis/+tracks',
        'only_matching': True,
    }, {
        'url': 'https://www.last.fm/music/Oasis/+tracks?page=2',
        'only_matching': True,
    }, {
        'url': 'https://www.last.fm/music/Oasis/+tracks?date_preset=LAST_90_DAYS#top-tracks',
        'only_matching': True,
    }, {
        'url': 'https://www.last.fm/tag/rock',