Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package streamlink for openSUSE:Factory checked in at 2024-07-14 08:51:13 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/streamlink (Old) and /work/SRC/openSUSE:Factory/.streamlink.new.17339 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "streamlink" Sun Jul 14 08:51:13 2024 rev:15 rq:1187174 version:6.8.3 Changes: -------- --- /work/SRC/openSUSE:Factory/streamlink/streamlink.changes 2024-07-08 19:07:42.597532556 +0200 +++ /work/SRC/openSUSE:Factory/.streamlink.new.17339/streamlink.changes 2024-07-14 08:54:33.349350029 +0200 @@ -1,0 +2,7 @@ +Fri Jul 12 06:04:34 UTC 2024 - Richard Rahl <rrahl0@opensuse.org> + +- update to 6.8.3: + * tiktok: new plugin + * twitch: fixed channel names with uppercase characters + +------------------------------------------------------------------- Old: ---- streamlink-6.8.2.tar.gz streamlink-6.8.2.tar.gz.asc New: ---- streamlink-6.8.3.tar.gz streamlink-6.8.3.tar.gz.asc ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ streamlink.spec ++++++ --- /var/tmp/diff_new_pack.cI1aSP/_old 2024-07-14 08:54:34.045375601 +0200 +++ /var/tmp/diff_new_pack.cI1aSP/_new 2024-07-14 08:54:34.049375748 +0200 @@ -24,7 +24,7 @@ %define psuffix %nil %endif Name: streamlink%{psuffix} -Version: 6.8.2 +Version: 6.8.3 Release: 0 Summary: Program to pipe streams from services into a video player License: Apache-2.0 AND BSD-2-Clause ++++++ streamlink-6.8.2.tar.gz -> streamlink-6.8.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/streamlink-6.8.2/CHANGELOG.md new/streamlink-6.8.3/CHANGELOG.md --- old/streamlink-6.8.2/CHANGELOG.md 2024-07-04 20:45:42.000000000 +0200 +++ new/streamlink-6.8.3/CHANGELOG.md 2024-07-11 19:59:53.000000000 +0200 @@ -1,5 +1,16 @@ # Changelog +## streamlink 6.8.3 (2024-07-11) + +Patch release: + +- Updated plugins: + - tiktok: new plugin ([#6073](https://github.com/streamlink/streamlink/pull/6073)) + - twitch: fixed channel names with uppercase characters ([#6071](https://github.com/streamlink/streamlink/pull/6071)) + +[Full changelog](https://github.com/streamlink/streamlink/compare/6.8.2...6.8.3) + + ## streamlink 6.8.2 (2024-07-04) Patch release: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/streamlink-6.8.2/PKG-INFO new/streamlink-6.8.3/PKG-INFO --- old/streamlink-6.8.2/PKG-INFO 2024-07-04 20:46:17.149702800 +0200 +++ new/streamlink-6.8.3/PKG-INFO 2024-07-11 20:00:20.339738800 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: streamlink -Version: 6.8.2 +Version: 6.8.3 Summary: Streamlink is a command-line utility that extracts streams from various services and pipes them into a video player of choice. Author: Streamlink Author-email: streamlink@protonmail.com diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/streamlink-6.8.2/dev-requirements.txt new/streamlink-6.8.3/dev-requirements.txt --- old/streamlink-6.8.2/dev-requirements.txt 2024-07-04 20:45:42.000000000 +0200 +++ new/streamlink-6.8.3/dev-requirements.txt 2024-07-11 19:59:53.000000000 +0200 @@ -15,7 +15,7 @@ coverage[toml] # code-linting -ruff ==0.5.0 +ruff ==0.5.1 # typing mypy ==1.10.1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/streamlink-6.8.2/docs/_build/man/streamlink.1 new/streamlink-6.8.3/docs/_build/man/streamlink.1 --- old/streamlink-6.8.2/docs/_build/man/streamlink.1 2024-07-04 20:46:11.000000000 +0200 +++ new/streamlink-6.8.3/docs/_build/man/streamlink.1 2024-07-11 20:00:15.000000000 +0200 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "STREAMLINK" "1" "Jul 04, 2024" "6.8.2" "Streamlink" +.TH "STREAMLINK" "1" "Jul 11, 2024" "6.8.3" "Streamlink" .SH NAME streamlink \- extracts streams from various services and pipes them into a video player of choice .SH SYNOPSIS diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/streamlink-6.8.2/docs/changelog.md new/streamlink-6.8.3/docs/changelog.md --- old/streamlink-6.8.2/docs/changelog.md 2024-07-04 20:45:42.000000000 +0200 +++ new/streamlink-6.8.3/docs/changelog.md 2024-07-11 19:59:53.000000000 +0200 @@ -1,5 +1,16 @@ # Changelog +## streamlink 6.8.3 (2024-07-11) + +Patch release: + +- Updated plugins: + - tiktok: new plugin ([#6073](https://github.com/streamlink/streamlink/pull/6073)) + - twitch: fixed channel names with uppercase characters ([#6071](https://github.com/streamlink/streamlink/pull/6071)) + +[Full changelog](https://github.com/streamlink/streamlink/compare/6.8.2...6.8.3) + + ## streamlink 6.8.2 (2024-07-04) Patch release: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/streamlink-6.8.2/pyproject.toml new/streamlink-6.8.3/pyproject.toml --- old/streamlink-6.8.2/pyproject.toml 2024-07-04 20:46:17.149702800 +0200 +++ new/streamlink-6.8.3/pyproject.toml 2024-07-11 20:00:20.339738800 +0200 @@ -249,6 +249,10 @@ [tool.ruff.lint.flake8-tidy-imports] ban-relative-imports = "all" +[tool.ruff.lint.flake8-pytest-style] +fixture-parentheses = true +mark-parentheses = true + [tool.ruff.lint.flake8-quotes] avoid-escape = false diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/streamlink-6.8.2/setup.py new/streamlink-6.8.3/setup.py --- old/streamlink-6.8.2/setup.py 2024-07-04 20:46:17.149702800 +0200 +++ new/streamlink-6.8.3/setup.py 2024-07-11 20:00:20.339738800 +0200 @@ -90,5 +90,5 @@ cmdclass=get_cmdclasses(cmdclass), entry_points=entry_points, data_files=data_files, - version="6.8.2", + version="6.8.3", ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/streamlink-6.8.2/src/streamlink/_version.py new/streamlink-6.8.3/src/streamlink/_version.py --- old/streamlink-6.8.2/src/streamlink/_version.py 2024-07-04 20:46:17.149702800 +0200 +++ new/streamlink-6.8.3/src/streamlink/_version.py 2024-07-11 20:00:20.339738800 +0200 @@ -1 +1 @@ -__version__ = "6.8.2" +__version__ = "6.8.3" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/streamlink-6.8.2/src/streamlink/plugins/bloomberg.py new/streamlink-6.8.3/src/streamlink/plugins/bloomberg.py --- old/streamlink-6.8.2/src/streamlink/plugins/bloomberg.py 2024-07-04 20:45:42.000000000 +0200 +++ new/streamlink-6.8.3/src/streamlink/plugins/bloomberg.py 2024-07-11 19:59:53.000000000 +0200 @@ -9,7 +9,7 @@ import re from streamlink.plugin import Plugin, PluginError, pluginmatcher -from streamlink.plugin.api import validate +from streamlink.plugin.api import useragents, validate from streamlink.stream.hls import HLSStream @@ -104,7 +104,8 @@ return secureStreams or streams def _get_streams(self): - del self.session.http.headers["Accept-Encoding"] + self.session.http.headers.clear() + self.session.http.headers["User-Agent"] = useragents.CHROME data = self.session.http.get(self.url, schema=validate.Schema( validate.parse_html(), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/streamlink-6.8.2/src/streamlink/plugins/tiktok.py new/streamlink-6.8.3/src/streamlink/plugins/tiktok.py --- old/streamlink-6.8.2/src/streamlink/plugins/tiktok.py 1970-01-01 01:00:00.000000000 +0100 +++ new/streamlink-6.8.3/src/streamlink/plugins/tiktok.py 2024-07-11 19:59:53.000000000 +0200 @@ -0,0 +1,124 @@ +""" +$description TikTok is a short-form video hosting service owned by ByteDance. +$url www.tiktok.com +$type live +$metadata id +$metadata author +$metadata title +""" + +import logging +import re +from typing import Dict + +from streamlink.plugin import Plugin, pluginmatcher +from streamlink.plugin.api import validate +from streamlink.stream.http import HTTPStream + + +log = logging.getLogger(__name__) + + +@pluginmatcher( + re.compile( + r"https?://(?:www\.)?tiktok\.com/@(?P<channel>[^/?]+)", + ), +) +class TikTok(Plugin): + QUALITY_WEIGHTS: Dict[str, int] = {} + + _URL_WEB_LIVE = "https://www.tiktok.com/@{channel}/live" + _URL_API_LIVE_DETAIL = "https://www.tiktok.com/api/live/detail/?aid=1988&roomID={room_id}" + _URL_WEBCAST_ROOM_INFO = "https://webcast.tiktok.com/webcast/room/info/?aid=1988&room_id={room_id}" + + _STATUS_OFFLINE = 4 + + @classmethod + def stream_weight(cls, key): + weight = cls.QUALITY_WEIGHTS.get(key) + if weight: + return weight, key + + return super().stream_weight(key) + + def _get_streams(self): + self.id = self.session.http.get( + self._URL_WEB_LIVE.format(channel=self.match["channel"]), + allow_redirects=False, + schema=validate.Schema( + validate.parse_html(), + validate.xml_xpath_string( + ".//head/meta[@property='al:android:url'][contains(@content,'live?room_id=')]/@content", + ), + validate.none_or_all( + str, + re.compile(r"room_id=(\d+)"), + validate.get(1), + ), + ), + ) + if not self.id: + return + + live_detail = self.session.http.get( + self._URL_API_LIVE_DETAIL.format(room_id=self.id), + schema=validate.Schema( + validate.parse_json(), + { + "status_code": 0, + "LiveRoomInfo": { + "status": int, + "title": str, + "ownerInfo": {"nickname": str}, + }, + }, + validate.get("LiveRoomInfo"), + validate.union_get( + "status", + ("ownerInfo", "nickname"), + "title", + ), + ), + ) + status, self.author, self.title = live_detail + if status == self._STATUS_OFFLINE: + log.info("The channel is currently offline") + return + + streams = self.session.http.get( + self._URL_WEBCAST_ROOM_INFO.format(room_id=self.id), + schema=validate.Schema( + validate.parse_json(), + {"data": {"stream_url": {"live_core_sdk_data": {"pull_data": {"stream_data": str}}}}}, + validate.get(("data", "stream_url", "live_core_sdk_data", "pull_data", "stream_data")), + validate.parse_json(), + { + "data": { + str: validate.all( + { + "main": { + "flv": validate.url(), + "sdk_params": validate.all( + validate.parse_json(), + { + "vbitrate": int, + }, + ), + }, + }, + validate.union_get( + ("main", "flv"), + ("main", "sdk_params", "vbitrate"), + ), + ), + }, + }, + validate.get("data"), + ), + ) + for name, (url, vbitrate) in streams.items(): + self.QUALITY_WEIGHTS[name] = vbitrate + yield name, HTTPStream(self.session, url) + + +__plugin__ = TikTok diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/streamlink-6.8.2/src/streamlink/plugins/twitch.py new/streamlink-6.8.3/src/streamlink/plugins/twitch.py --- old/streamlink-6.8.2/src/streamlink/plugins/twitch.py 2024-07-04 20:45:42.000000000 +0200 +++ new/streamlink-6.8.3/src/streamlink/plugins/twitch.py 2024-07-11 19:59:53.000000000 +0200 @@ -274,8 +274,8 @@ return req.url - def channel(self, channel, **extra_params): - try: + def channel(self, channel: str, **extra_params) -> str: + with suppress(PluginError): extra_params_debug = validate.Schema( validate.get("token"), validate.parse_json(), @@ -288,11 +288,10 @@ }, ).validate(extra_params) log.debug(f"{extra_params_debug!r}") - except PluginError: - pass - return self._create_url(f"/api/channel/hls/{channel}.m3u8", **extra_params) - def video(self, video_id, **extra_params): + return self._create_url(f"/api/channel/hls/{channel.lower()}.m3u8", **extra_params) + + def video(self, video_id: str, **extra_params) -> str: return self._create_url(f"/vod/{video_id}", **extra_params) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/streamlink-6.8.2/src/streamlink.egg-info/PKG-INFO new/streamlink-6.8.3/src/streamlink.egg-info/PKG-INFO --- old/streamlink-6.8.2/src/streamlink.egg-info/PKG-INFO 2024-07-04 20:46:16.000000000 +0200 +++ new/streamlink-6.8.3/src/streamlink.egg-info/PKG-INFO 2024-07-11 20:00:20.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: streamlink -Version: 6.8.2 +Version: 6.8.3 Summary: Streamlink is a command-line utility that extracts streams from various services and pipes them into a video player of choice. Author: Streamlink Author-email: streamlink@protonmail.com diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/streamlink-6.8.2/src/streamlink.egg-info/SOURCES.txt new/streamlink-6.8.3/src/streamlink.egg-info/SOURCES.txt --- old/streamlink-6.8.2/src/streamlink.egg-info/SOURCES.txt 2024-07-04 20:46:17.000000000 +0200 +++ new/streamlink-6.8.3/src/streamlink.egg-info/SOURCES.txt 2024-07-11 20:00:20.000000000 +0200 @@ -225,6 +225,7 @@ src/streamlink/plugins/telefe.py src/streamlink/plugins/telemadrid.py src/streamlink/plugins/tf1.py +src/streamlink/plugins/tiktok.py src/streamlink/plugins/trovo.py src/streamlink/plugins/turkuvaz.py src/streamlink/plugins/tv360.py @@ -505,6 +506,7 @@ tests/plugins/test_telefe.py tests/plugins/test_telemadrid.py tests/plugins/test_tf1.py +tests/plugins/test_tiktok.py tests/plugins/test_trovo.py tests/plugins/test_turkuvaz.py tests/plugins/test_tv360.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/streamlink-6.8.2/tests/plugins/test_tiktok.py new/streamlink-6.8.3/tests/plugins/test_tiktok.py --- old/streamlink-6.8.2/tests/plugins/test_tiktok.py 1970-01-01 01:00:00.000000000 +0100 +++ new/streamlink-6.8.3/tests/plugins/test_tiktok.py 2024-07-11 19:59:53.000000000 +0200 @@ -0,0 +1,16 @@ +from streamlink.plugins.tiktok import TikTok +from tests.plugins import PluginCanHandleUrl + + +class TestPluginCanHandleUrlTikTok(PluginCanHandleUrl): + __plugin__ = TikTok + + should_match_groups = [ + ("https://www.tiktok.com/@CHANNEL", {"channel": "CHANNEL"}), + ("https://www.tiktok.com/@CHANNEL/live", {"channel": "CHANNEL"}), + + ] + + should_not_match = [ + "https://www.tiktok.com", + ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/streamlink-6.8.2/tests/plugins/test_twitch.py new/streamlink-6.8.3/tests/plugins/test_twitch.py --- old/streamlink-6.8.2/tests/plugins/test_twitch.py 2024-07-04 20:45:42.000000000 +0200 +++ new/streamlink-6.8.3/tests/plugins/test_twitch.py 2024-07-11 19:59:53.000000000 +0200 @@ -1,8 +1,10 @@ +import json import unittest from contextlib import nullcontext from datetime import datetime, timedelta, timezone from typing import Optional from unittest.mock import MagicMock, Mock, call, patch +from urllib.parse import parse_qsl, urlparse import pytest import requests_mock as rm @@ -529,6 +531,70 @@ assert mock_log.warning.mock_calls == [] +class TestUsherService: + @pytest.fixture(autouse=True) + def caplog(self, caplog: pytest.LogCaptureFixture): + caplog.set_level(1, "streamlink.plugins.twitch") + return caplog + + @pytest.fixture() + def plugin(self, request: pytest.FixtureRequest, session: Streamlink): + return Twitch( + session, + "https://twitch.tv/twitch", + options=Options(getattr(request, "param", {})), + ) + + @pytest.fixture() + def endpoint(self, request: pytest.FixtureRequest, caplog: pytest.LogCaptureFixture, plugin: Twitch): + param = getattr(request, "param", {}) + service = param.get("service", "channel") + args = param.get("args", ("twitch", )) + + token = { + "expires": 9876543210, + "channel": "twitch", + "channel_id": 123, + "user_id": 456, + "user_ip": "127.0.0.1", + "adblock": False, + "geoblock_reason": "", + "hide_ads": False, + "server_ads": True, + "show_ads": True, + } + + return getattr(plugin.usher, service)(*args, token=json.dumps(token), sig="tokensignature") + + @pytest.mark.parametrize(("endpoint", "expected_path", "logs"), [ + pytest.param( + {"service": "channel", "args": ("TWITCH", )}, + "/api/channel/hls/twitch.m3u8", + [( + "streamlink.plugins.twitch", + "debug", + "{'adblock': False, 'geoblock_reason': '', 'hide_ads': False, 'server_ads': True, 'show_ads': True}", + )], + id="channel", + ), + pytest.param( + {"service": "video", "args": ("1234567890", )}, + "/vod/1234567890", + [], + id="video", + ), + ], indirect=["endpoint"]) + def test_service(self, caplog: pytest.LogCaptureFixture, endpoint: str, expected_path: str, logs: list): + url = urlparse(endpoint) + assert url.path == expected_path + + qs = dict(parse_qsl(url.query)) + assert qs.get("token") + assert qs.get("sig") + + assert [(r.name, r.levelname, r.message) for r in caplog.get_records(when="setup")] == logs + + class TestTwitchAPIAccessToken: @pytest.fixture(autouse=True) def _client_integrity_token(self, monkeypatch: pytest.MonkeyPatch): ++++++ streamlink.keyring ++++++ --- /var/tmp/diff_new_pack.cI1aSP/_old 2024-07-14 08:54:34.341386476 +0200 +++ /var/tmp/diff_new_pack.cI1aSP/_new 2024-07-14 08:54:34.345386623 +0200 @@ -1,6 +1,6 @@ -----BEGIN PGP PUBLIC KEY BLOCK----- -Version: Hockeypuck 2.2 Comment: Hostname: +Version: Hockeypuck 2.2 xjMEZLWhshYJKwYBBAHaRw8BAQdAu0sD5Ez8mfroVXpEohGHAeH1H2xduEHsYHkG IciKHdzNMlN0cmVhbWxpbmsgc2lnbmluZyBrZXkgPHN0cmVhbWxpbmtAcHJvdG9u