Path: blob/master/ invest-robot-contest_TinkoffBotTwitch-main/venv/lib/python3.8/site-packages/twitchio/http.py
7774 views
"""1The MIT License (MIT)23Copyright (c) 2017-2021 TwitchIO45Permission is hereby granted, free of charge, to any person obtaining a6copy of this software and associated documentation files (the "Software"),7to deal in the Software without restriction, including without limitation8the rights to use, copy, modify, merge, publish, distribute, sublicense,9and/or sell copies of the Software, and to permit persons to whom the10Software is furnished to do so, subject to the following conditions:1112The above copyright notice and this permission notice shall be included in13all copies or substantial portions of the Software.1415THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS16OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,17FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE18AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER19LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING20FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER21DEALINGS IN THE SOFTWARE.22"""2324import asyncio25import copy26import datetime27import logging28from typing import TYPE_CHECKING, Union, List, Tuple, Any, Dict, Optional2930import aiohttp31from yarl import URL3233from . import errors34from .cooldowns import RateBucket3536try:37import ujson as json38except:39import json4041if TYPE_CHECKING:42from .client import Client434445logger = logging.getLogger("twitchio.http")464748class Route:4950BASE_URL = "https://api.twitch.tv/helix"5152__slots__ = "path", "body", "headers", "query", "method"5354def __init__(55self,56method: str,57path: Union[str, URL],58body: Union[str, dict] = None,59query: List[Tuple[str, Any]] = None,60headers: dict = None,61token: str = None,62):63self.headers = headers or {}64self.method = method65self.query = query6667if token:68self.headers["Authorization"] = "Bearer " + token6970if isinstance(path, URL):71self.path = path72else:73self.path = URL(self.BASE_URL + "/" + path.rstrip("/"))7475if query:76self.path = self.path.with_query(query)7778if isinstance(body, dict):79self.body = json.dumps(body)80self.headers["Content-Type"] = "application/json"81else:82self.body = body838485class TwitchHTTP:8687TOKEN_BASE = "https://id.twitch.tv/oauth2/token"8889def __init__(90self, client: "Client", *, api_token: str = None, client_secret: str = None, client_id: str = None, **kwargs91):92self.client = client93self.session = None94self.token = api_token95self.app_token = None96self._refresh_token = None97self.client_secret = client_secret98self.client_id = client_id99self.nick = None100self.user_id: Optional[int] = None101102self.bucket = RateBucket(method="http")103self.scopes = kwargs.get("scopes", [])104105async def request(self, route: Route, *, paginate=True, limit=100, full_body=False, force_app_token=False):106"""107Fulfills an API request108109Parameters110-----------111route : :class:`twitchio.http.Route`112The route to follow113paginate : :class:`bool`114whether or not to paginate the requests where possible. Defaults to True115limit : :class:`int`116The data limit per request when paginating. Defaults to 100117full_body : class:`bool`118Whether to return the full response body or to accumulate the `data` key. Defaults to False. `paginate` must be False if this is True.119force_app_token : :class:`bool`120Forcibly use the client_id and client_secret generated token, if available. Otherwise fail the request immediately121"""122if full_body:123assert not paginate124125if (not self.client_id or not self.nick) and self.token:126await self.validate(token=self.token)127128if not self.client_id:129raise errors.NoClientID("A Client ID is required to use the Twitch API")130131headers = route.headers or {}132133if force_app_token and "Authorization" not in headers:134if not self.client_secret:135raise errors.NoToken(136"An app access token is required for this route, please provide a client id and client secret"137)138139if self.app_token is None:140await self._generate_login()141headers["Authorization"] = f"Bearer {self.app_token}"142143elif not self.token and not self.client_secret and "Authorization" not in headers:144raise errors.NoToken(145"Authorization is required to use the Twitch API. Pass token and/or client_secret to the Client constructor"146)147148if "Authorization" not in headers:149if not self.token:150await self._generate_login()151152headers["Authorization"] = f"Bearer {self.token}"153154headers["Client-ID"] = self.client_id155156if not self.session:157self.session = aiohttp.ClientSession()158159if self.bucket.limited:160await self.bucket161162cursor = None163data = []164165def reached_limit():166return limit and len(data) >= limit167168def get_limit():169if limit is None:170return "100"171172to_get = limit - len(data)173return str(to_get) if to_get < 100 else "100"174175is_finished = False176while not is_finished:177path = copy.copy(route.path)178179if limit is not None and paginate:180q = route.query or []181if cursor is not None:182q = [("after", cursor), *q]183q = [("first", get_limit()), *q]184path = path.with_query(q)185186body, is_text = await self._request(route, path, headers)187if is_text:188return body189190if full_body:191return body192193data += body["data"]194195try:196cursor = body["pagination"].get("cursor", None)197except KeyError:198break199else:200if not cursor:201break202203is_finished = reached_limit() if limit is not None else True if paginate else True204205return data206207async def _request(self, route, path, headers, utilize_bucket=True):208reason = None209210for attempt in range(5):211if utilize_bucket and self.bucket.limited:212await self.bucket.wait_reset()213214async with self.session.request(route.method, path, headers=headers, data=route.body) as resp:215try:216logger.debug(f"Received a response from a request with status {resp.status}: {await resp.json()}")217except Exception:218logger.debug(f"Received a response from a request with status {resp.status} and without body")219220if 500 <= resp.status <= 504:221reason = resp.reason222await asyncio.sleep(2**attempt + 1)223continue224225if utilize_bucket:226reset = resp.headers.get("Ratelimit-Reset")227remaining = resp.headers.get("Ratelimit-Remaining")228229self.bucket.update(reset=reset, remaining=remaining)230231if 200 <= resp.status < 300:232if resp.content_type == "application/json":233return await resp.json(), False234235return await resp.text(encoding="utf-8"), True236237if resp.status == 401:238if "WWW-Authenticate" in resp.headers:239try:240await self._generate_login()241except:242raise errors.Unauthorized(243"Your oauth token is invalid, and a new one could not be generated"244)245246print(resp.reason, await resp.json(), resp)247raise errors.Unauthorized("You're not authorized to use this route.")248249if resp.status == 429:250reason = "Ratelimit Reached"251252if not utilize_bucket: # non Helix APIs don't have ratelimit headers253await asyncio.sleep(3**attempt + 1)254continue255256raise errors.HTTPException(257f"Failed to fulfil request ({resp.status}).", reason=resp.reason, status=resp.status258)259260raise errors.HTTPException("Failed to reach Twitch API", reason=reason, status=resp.status)261262async def _generate_login(self):263try:264token = await self.client.event_token_expired()265if token is not None:266assert isinstance(token, str), TypeError(f"Expected a string, got {type(token)}")267self.token = self.app_token = token268return269except Exception as e:270self.client.run_event("error", e)271272if not self.client_id or not self.client_secret:273raise errors.HTTPException("Unable to generate a token, client id and/or client secret not given")274275if self._refresh_token:276url = (277self.TOKEN_BASE278+ "?grant_type=refresh_token&refresh_token={0}&client_id={1}&client_secret={2}".format(279self._refresh_token, self.client_id, self.client_secret280)281)282283else:284url = self.TOKEN_BASE + "?client_id={0}&client_secret={1}&grant_type=client_credentials".format(285self.client_id, self.client_secret286)287if self.scopes:288url += "&scope=" + " ".join(self.scopes)289290if not self.session:291self.session = aiohttp.ClientSession()292293async with self.session.post(url) as resp:294if resp.status > 300 or resp.status < 200:295raise errors.HTTPException("Unable to generate a token: " + await resp.text())296297data = await resp.json()298self.token = self.app_token = data["access_token"]299self._refresh_token = data.get("refresh_token", None)300logger.info("Invalid or no token found, generated new token: %s", self.token)301302async def validate(self, *, token: str = None) -> dict:303if not token:304token = self.token305if not self.session:306self.session = aiohttp.ClientSession()307308url = "https://id.twitch.tv/oauth2/validate"309headers = {"Authorization": f"OAuth {token}"}310311async with self.session.get(url, headers=headers) as resp:312if resp.status == 401:313raise errors.AuthenticationError("Invalid or unauthorized Access Token passed.")314315if resp.status > 300 or resp.status < 200:316raise errors.HTTPException("Unable to validate Access Token: " + await resp.text())317318data: dict = await resp.json()319320if not self.nick:321self.nick = data.get("login")322self.user_id = data.get("user_id") and int(data["user_id"])323self.client_id = data.get("client_id")324325return data326327async def post_commercial(self, token: str, broadcaster_id: str, length: int):328assert length in {30, 60, 90, 120, 150, 180}329data = await self.request(330Route(331"POST", "channels/commercial", body={"broadcaster_id": broadcaster_id, "length": length}, token=token332),333paginate=False,334)335data = data[0]336if data["message"]:337raise errors.HTTPException(data["message"], extra=data["retry_after"])338339async def get_extension_analytics(340self,341token: str,342extension_id: str = None,343type: str = None,344started_at: datetime.datetime = None,345ended_at: datetime.datetime = None,346):347raise NotImplementedError # TODO348349async def get_game_analytics(350self,351token: str,352game_id: str = None,353type: str = None,354started_at: datetime.datetime = None,355ended_at: datetime.datetime = None,356):357raise NotImplementedError # TODO358359async def get_bits_board(360self, token: str, period: str = "all", user_id: str = None, started_at: datetime.datetime = None361):362assert period in {"all", "day", "week", "month", "year"}363route = Route(364"GET",365"bits/leaderboard",366"",367query=[368("period", period),369("started_at", started_at.isoformat() if started_at else None),370("user_id", user_id),371],372token=token,373)374return await self.request(route, full_body=True, paginate=False)375376async def get_cheermotes(self, broadcaster_id: str):377return await self.request(Route("GET", "bits/cheermotes", "", query=[("broadcaster_id", broadcaster_id)]))378379async def get_extension_transactions(self, extension_id: str, ids: List[Any] = None):380q = [("extension_id", extension_id)]381if ids:382for id in ids:383q.append(("id", id))384385return await self.request(Route("GET", "extensions/transactions", "", query=q))386387async def create_reward(388self,389token: str,390broadcaster_id: int,391title: str,392cost: int,393prompt: str = None,394is_enabled: bool = True,395background_color: str = None,396user_input_required: bool = False,397max_per_stream: int = None,398max_per_user: int = None,399global_cooldown: int = None,400fufill_immediatly: bool = False,401):402params = [("broadcaster_id", str(broadcaster_id))]403data = {404"title": title,405"cost": cost,406"prompt": prompt,407"is_enabled": is_enabled,408"is_user_input_required": user_input_required,409"should_redemptions_skip_request_queue": fufill_immediatly,410}411if max_per_stream:412data["max_per_stream"] = max_per_stream413data["max_per_stream_enabled"] = True414415if max_per_user:416data["max_per_user_per_stream"] = max_per_user417data["max_per_user_per_stream_enabled"] = True418419if background_color:420data["background_color"] = background_color421422if global_cooldown:423data["global_cooldown_seconds"] = global_cooldown424data["is_global_cooldown_enabled"] = True425426return await self.request(Route("POST", "channel_points/custom_rewards", query=params, body=data, token=token))427428async def get_rewards(self, token: str, broadcaster_id: int, only_manageable: bool = False, ids: List[int] = None):429params = [("broadcaster_id", str(broadcaster_id)), ("only_manageable_rewards", str(only_manageable))]430431if ids:432for id in ids:433params.append(("id", str(id)))434435return await self.request(Route("GET", "channel_points/custom_rewards", query=params, token=token))436437async def update_reward(438self,439token: str,440broadcaster_id: int,441reward_id: str,442title: str = None,443prompt: str = None,444cost: int = None,445background_color: str = None,446enabled: bool = None,447input_required: bool = None,448max_per_stream_enabled: bool = None,449max_per_stream: int = None,450max_per_user_per_stream_enabled: bool = None,451max_per_user_per_stream: int = None,452global_cooldown_enabled: bool = None,453global_cooldown: int = None,454paused: bool = None,455redemptions_skip_queue: bool = None,456):457data = {458"title": title,459"prompt": prompt,460"cost": cost,461"background_color": background_color,462"enabled": enabled,463"is_user_input_required": input_required,464"is_max_per_stream_enabled": max_per_stream_enabled,465"max_per_stream": max_per_stream,466"is_max_per_user_per_stream_enabled": max_per_user_per_stream_enabled,467"max_per_user_per_stream": max_per_user_per_stream,468"is_global_cooldown_enabled": global_cooldown_enabled,469"global_cooldown_seconds": global_cooldown,470"is_paused": paused,471"should_redemptions_skip_request_queue": redemptions_skip_queue,472}473474data = {k: v for k, v in data.items() if v is not None}475476if not data:477raise ValueError("Nothing changed!")478479params = [("broadcaster_id", str(broadcaster_id)), ("id", str(reward_id))]480return await self.request(481Route(482"PATCH",483"channel_points/custom_rewards",484query=params,485headers={"Authorization": f"Bearer {token}"},486body=data,487)488)489490async def delete_custom_reward(self, token: str, broadcaster_id: int, reward_id: str):491params = [("broadcaster_id", str(broadcaster_id)), ("id", reward_id)]492return await self.request(Route("DELETE", "channel_points/custom_rewards", query=params, token=token))493494async def get_reward_redemptions(495self,496token: str,497broadcaster_id: int,498reward_id: str,499redemption_id: str = None,500status: str = None,501sort: str = None,502):503params = [("broadcaster_id", str(broadcaster_id)), ("reward_id", reward_id)]504505if redemption_id:506params.append(("id", redemption_id))507508if status:509params.append(("status", status))510511if sort:512params.append(("sort", sort))513514return await self.request(Route("GET", "channel_points/custom_rewards/redemptions", query=params, token=token))515516async def update_reward_redemption_status(517self, token: str, broadcaster_id: int, reward_id: str, custom_reward_id: str, status: bool518):519params = [("id", custom_reward_id), ("broadcaster_id", str(broadcaster_id)), ("reward_id", reward_id)]520status = "FULFILLED" if status else "CANCELLED"521return await self.request(522Route(523"PATCH",524"/channel_points/custom_rewards/redemptions",525query=params,526body={"status": status},527token=token,528)529)530531async def get_predictions(532self,533token: str,534broadcaster_id: int,535prediction_id: str = None,536):537params = [("broadcaster_id", str(broadcaster_id))]538539if prediction_id:540params.extend(("prediction_id", prediction_id))541542return await self.request(Route("GET", "predictions", query=params, token=token), paginate=False)543544async def patch_prediction(545self, token: str, broadcaster_id: int, prediction_id: str, status: str, winning_outcome_id: str = None546):547body = {548"broadcaster_id": str(broadcaster_id),549"id": prediction_id,550"status": status,551}552553if status == "RESOLVED":554body["winning_outcome_id"] = winning_outcome_id555556return await self.request(557Route(558"PATCH",559"predictions",560body=body,561token=token,562)563)564565async def post_prediction(566self, token: str, broadcaster_id: int, title: str, blue_outcome: str, pink_outcome: str, prediction_window: int567):568body = {569"broadcaster_id": broadcaster_id,570"title": title,571"prediction_window": prediction_window,572"outcomes": [573{574"title": blue_outcome,575},576{577"title": pink_outcome,578},579],580}581return await self.request(582Route("POST", "predictions", body=body, token=token),583paginate=False,584)585586async def post_create_clip(self, token: str, broadcaster_id: int, has_delay=False):587return await self.request(588Route("POST", "clips", query=[("broadcaster_id", broadcaster_id), ("has_delay", has_delay)], token=token),589paginate=False,590)591592async def get_clips(593self,594broadcaster_id: int = None,595game_id: str = None,596ids: List[str] = None,597started_at: datetime.datetime = None,598ended_at: datetime.datetime = None,599token: str = None,600):601q = [602("broadcaster_id", broadcaster_id),603("game_id", game_id),604("started_at", started_at.isoformat() if started_at else None),605("ended_at", ended_at.isoformat() if ended_at else None),606]607for id in ids:608q.append(("id", id))609610query = [x for x in q if x[1] is not None]611612return await self.request(Route("GET", "clips", query=query, token=token))613614async def post_entitlements_upload(self, manifest_id: str, type="bulk_drops_grant"):615return await self.request(616Route("POST", "entitlements/upload", query=[("manifest_id", manifest_id), ("type", type)])617)618619async def get_entitlements(self, id: str = None, user_id: str = None, game_id: str = None):620return await self.request(621Route("GET", "entitlements/drops", query=[("id", id), ("user_id", user_id), ("game_id", game_id)])622)623624async def get_code_status(self, codes: List[str], user_id: int):625q = [("user_id", user_id)]626for code in codes:627q.append(("code", code))628629return await self.request(Route("GET", "entitlements/codes", query=q))630631async def post_redeem_code(self, user_id: int, codes: List[str]):632q = [("user_id", user_id)]633for c in codes:634q.append(("code", c))635636return await self.request(Route("POST", "entitlements/code", query=q))637638async def get_top_games(self):639return await self.request(Route("GET", "games/top"))640641async def get_games(self, game_ids: List[Any], game_names: List[str]):642q = []643if game_ids:644for id in game_ids:645q.append(("id", id))646if game_names:647for name in game_names:648q.append(("name", name))649650return await self.request(Route("GET", "games", query=q))651652async def get_hype_train(self, broadcaster_id: str, id: str = None, token: str = None):653return await self.request(654Route(655"GET",656"hypetrain/events",657query=[x for x in [("broadcaster_id", broadcaster_id), ("id", id)] if x[1] is not None],658token=token,659)660)661662async def post_automod_check(self, token: str, broadcaster_id: str, *msgs: List[Dict[str, str]]):663print(msgs)664return await self.request(665Route(666"POST",667"moderation/enforcements/status",668query=[("broadcaster_id", broadcaster_id)],669body={"data": msgs},670token=token,671)672)673674async def get_channel_ban_unban_events(self, token: str, broadcaster_id: str, user_ids: List[str] = None):675q = [("broadcaster_id", broadcaster_id)]676if user_ids:677for id in user_ids:678q.append(("user_id", id))679680return await self.request(Route("GET", "moderation/banned/events", query=q, token=token))681682async def get_channel_bans(self, token: str, broadcaster_id: str, user_ids: List[str] = None):683q = [("broadcaster_id", broadcaster_id)]684if user_ids:685for id in user_ids:686q.append(("user_id", id))687688return await self.request(Route("GET", "moderation/banned", query=q, token=token))689690async def get_channel_moderators(self, token: str, broadcaster_id: str, user_ids: List[str] = None):691q = [("broadcaster_id", broadcaster_id)]692if user_ids:693for id in user_ids:694q.append(("user_id", id))695696return await self.request(Route("GET", "moderation/moderators", query=q, token=token))697698async def get_channel_mod_events(self, token: str, broadcaster_id: str, user_ids: List[str] = None):699q = [("broadcaster_id", broadcaster_id)]700for id in user_ids:701q.append(("user_id", id))702703return await self.request(Route("GET", "moderation/moderators/events", query=q, token=token))704705async def get_search_categories(self, query: str, token: str = None):706return await self.request(Route("GET", "search/categories", query=[("query", query)], token=token))707708async def get_search_channels(self, query: str, token: str = None, live: bool = False):709return await self.request(710Route("GET", "search/channels", query=[("query", query), ("live_only", str(live))], token=token)711)712713async def get_stream_key(self, token: str, broadcaster_id: str):714return await self.request(715Route("GET", "streams/key", query=[("broadcaster_id", broadcaster_id)], token=token), paginate=False716)717718async def get_streams(719self,720game_ids: List[str] = None,721user_ids: List[str] = None,722user_logins: List[str] = None,723languages: List[str] = None,724token: str = None,725):726q = []727if game_ids:728for g in game_ids:729q.append(("game_id", g))730731if user_ids:732for u in user_ids:733q.append(("user_id", u))734735if user_logins:736for l in user_logins:737q.append(("user_login", l))738739if languages:740for l in languages:741q.append(("language", l))742743return await self.request(Route("GET", "streams", query=q, token=token))744745async def post_stream_marker(self, token: str, user_id: str, description: str = None):746return await self.request(747Route("POST", "streams/markers", body={"user_id": user_id, "description": description}, token=token)748)749750async def get_stream_markers(self, token: str, user_id: str = None, video_id: str = None):751return await self.request(752Route(753"GET",754"streams/markers",755query=[x for x in [("user_id", user_id), ("video_id", video_id)] if x[1] is not None],756token=token,757)758)759760async def get_channels(self, broadcaster_id: str, token: str = None):761return await self.request(Route("GET", "channels", query=[("broadcaster_id", broadcaster_id)], token=token))762763async def patch_channel(764self, token: str, broadcaster_id: str, game_id: str = None, language: str = None, title: str = None765):766assert any((game_id, language, title))767body = {768k: v769for k, v in {"game_id": game_id, "broadcaster_language": language, "title": title}.items()770if v is not None771}772773return await self.request(774Route("PATCH", "channels", query=[("broadcaster_id", broadcaster_id)], body=body, token=token)775)776777async def get_channel_schedule(778self,779broadcaster_id: str,780segment_ids: List[str] = None,781start_time: datetime.datetime = None,782utc_offset: int = None,783first: int = 20,784):785786if first is not None and (first > 25 or first < 1):787raise ValueError("The parameter 'first' was malformed: the value must be less than or equal to 25")788if segment_ids is not None and len(segment_ids) > 100:789raise ValueError("segment_id can only have 100 entries")790791if start_time:792start_time = start_time.strftime("%Y-%m-%dT%H:%M:%SZ")793794if utc_offset:795utc_offset = str(utc_offset)796797q = [798x799for x in [800("broadcaster_id", broadcaster_id),801("first", first),802("start_time", start_time),803("utc_offset", utc_offset),804]805if x[1] is not None806]807808if segment_ids:809for id in segment_ids:810q.append(811("id", id),812)813814return await self.request(Route("GET", "schedule", query=q), paginate=False, full_body=True)815816async def get_channel_subscriptions(self, token: str, broadcaster_id: str, user_ids: List[str] = None):817q = [("broadcaster_id", broadcaster_id)]818if user_ids:819for u in user_ids:820q.append(("user_id", u))821822return await self.request(Route("GET", "subscriptions", query=q, token=token))823824async def get_stream_tags(self, tag_ids: List[str] = None):825q = []826if tag_ids:827for u in tag_ids:828q.append(("tag_id", u))829830return await self.request(Route("GET", "tags/streams", query=q or None))831832async def get_channel_tags(self, broadcaster_id: str):833return await self.request(Route("GET", "streams/tags", query=[("broadcaster_id", broadcaster_id)]))834835async def put_replace_channel_tags(self, token: str, broadcaster_id: str, tag_ids: List[str] = None):836return await self.request(837Route(838"PUT",839"streams/tags",840query=[("broadcaster_id", broadcaster_id)],841body={"tag_ids": tag_ids},842token=token,843)844)845846async def post_follow_channel(self, token: str, from_id: str, to_id: str, notifications=False):847return await self.request(848Route(849"POST",850"users/follows",851query=[("from_id", from_id), ("to_id", to_id), ("allow_notifications", str(notifications))],852token=token,853)854)855856async def delete_unfollow_channel(self, token: str, from_id: str, to_id: str):857return await self.request(858Route("DELETE", "users/follows", query=[("from_id", from_id), ("to_id", to_id)], token=token)859)860861async def get_users(self, ids: List[int], logins: List[str], token: str = None):862q = []863if ids:864for id in ids:865q.append(("id", id))866867if logins:868for login in logins:869q.append(("login", login))870871return await self.request(Route("GET", "users", query=q, token=token))872873async def get_user_follows(self, from_id: str = None, to_id: str = None, token: str = None):874return await self.request(875Route(876"GET",877"users/follows",878query=[x for x in [("from_id", from_id), ("to_id", to_id)] if x[1] is not None],879token=token,880)881)882883async def put_update_user(self, token: str, description: str):884return await self.request(Route("PUT", "users", query=[("description", description)], token=token))885886async def get_channel_extensions(self, token: str):887return await self.request(Route("GET", "users/extensions/list", token=token))888889async def get_user_active_extensions(self, token: str, user_id: str = None):890return (891await self.request(892Route("GET", "users/extensions", query=[("user_id", user_id)], token=token),893paginate=False,894full_body=True,895)896)["data"]897898async def put_user_extensions(self, token: str, data: Dict[str, Any]):899return (900await self.request(901Route("PUT", "users/extensions", token=token, body={"data": data}), paginate=False, full_body=True902)903)["data"]904905async def get_videos(906self,907ids: List[str] = None,908user_id: str = None,909game_id: str = None,910sort: str = "time",911type: str = "all",912period: str = "all",913language: str = None,914token: str = None,915):916q = [917x918for x in [919("user_id", user_id),920("game_id", game_id),921("sort", sort),922("type", type),923("period", period),924("lanaguage", language),925]926if x[1] is not None927]928929if ids:930for id in ids:931q.append(("id", id))932933return await self.request(Route("GET", "videos", query=q, token=token))934935async def delete_videos(self, token: str, ids: List[int]):936q = [("id", str(x)) for x in ids]937938return (await self.request(Route("DELETE", "videos", query=q, token=token), paginate=False, full_body=True))[939"data"940]941942async def get_webhook_subs(self):943return await self.request(Route("GET", "webhooks/subscriptions"))944945async def get_teams(self, team_name: str = None, team_id: str = None):946if team_name:947q = [("name", team_name)]948elif team_id:949q = [("id", team_id)]950else:951raise ValueError("You need to provide a team name or id")952return await self.request(Route("GET", "teams", query=q))953954async def get_channel_teams(self, broadcaster_id: str):955q = [("broadcaster_id", broadcaster_id)]956return await self.request(Route("GET", "teams/channel", query=q))957958959