Path: blob/master/ invest-robot-contest_TinkoffBotTwitch-main/venv/lib/python3.8/site-packages/twitchio/user.py
7771 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 datetime25import time26from typing import TYPE_CHECKING, List, Optional, Union, Tuple2728from .enums import BroadcasterTypeEnum, UserTypeEnum29from .errors import HTTPException, Unauthorized30from .rewards import CustomReward31from .utils import parse_timestamp323334if TYPE_CHECKING:35from .http import TwitchHTTP36from .channel import Channel37from .models import BitsLeaderboard, Clip, ExtensionBuilder, Tag, FollowEvent, Prediction383940__all__ = (41"PartialUser",42"BitLeaderboardUser",43"UserBan",44"SearchUser",45"User",46)474849class PartialUser:5051__slots__ = "id", "name", "_http", "_cached_rewards"5253def __init__(self, http: "TwitchHTTP", id: Union[int, str], name: Optional[str]):54self.id = int(id)55self.name = name56self._http = http5758self._cached_rewards = None5960def __repr__(self):61return f"<PartialUser id={self.id}, name={self.name}>"6263@property64def channel(self) -> Optional["Channel"]:65"""66Returns the :class:`twitchio.Channel` associated with this user. Could be None if you are not part of the channel's chat6768Returns69--------70Optional[:class:`twitchio.Channel`]71"""72from .channel import Channel7374if self.name in self._http.client._connection._cache:75return Channel(self.name, self._http.client._connection)7677async def fetch(self, token: str = None, force=False) -> "User":78"""|coro|7980Fetches the full user from the api or cache8182Parameters83-----------84token : :class:`str`85Optional OAuth token to be used instead of the bot-wide OAuth token86force : :class:`bool`87Whether to force a fetch from the api or try to get from the cache first. Defaults to False8889Returns90--------91:class:`twitchio.User` The full user associated with this PartialUser92"""93data = await self._http.client.fetch_users(ids=[self.id], force=force, token=token)94return data[0]9596async def edit(self, token: str, description: str) -> None:97"""|coro|9899Edits a channels description100101Parameters102-----------103token: :class:`str`104An oauth token for the user with the user:edit scope105description: :class:`str`106The new description for the user107"""108await self._http.put_update_user(token, description)109110async def fetch_tags(self):111"""|coro|112113Fetches tags the user currently has active.114115Returns116--------117List[:class:`twitchio.Tag`]118"""119from .models import Tag120121data = await self._http.get_channel_tags(str(self.id))122return [Tag(x) for x in data]123124async def replace_tags(self, token: str, tags: List[Union[str, "Tag"]]):125"""|coro|126127Replaces the channels active tags. Tags expire 72 hours after being applied,128unless the stream is live during that time period.129130Parameters131-----------132token: :class:`str`133An oauth token with the user:edit:broadcast scope134tags: List[Union[:class:`twitchio.Tag`, :class:`str`]]135A list of :class:`twitchio.Tag` or tag ids to put on the channel. Max 100136"""137tags = [x if isinstance(x, str) else x.id for x in tags]138await self._http.put_replace_channel_tags(token, str(self.id), tags)139140async def get_custom_rewards(141self, token: str, *, only_manageable=False, ids: List[int] = None, force=False142) -> List["CustomReward"]:143"""|coro|144145Fetches the channels custom rewards (aka channel points) from the api.146Parameters147----------148token : :class:`str`149The users oauth token.150only_manageable : :class:`bool`151Whether to fetch all rewards or only ones you can manage. Defaults to false.152ids : List[:class:`int`]153An optional list of reward ids154force : :class:`bool`155Whether to force a fetch or try to get from cache. Defaults to False156157Returns158-------159160"""161if not force and self._cached_rewards and self._cached_rewards[0] + 300 > time.monotonic():162return self._cached_rewards[1]163164try:165data = await self._http.get_rewards(token, self.id, only_manageable, ids)166except Unauthorized as error:167raise Unauthorized("The given token is invalid", "", 401) from error168except HTTPException as error:169status = error.args[2]170if status == 403:171raise HTTPException(172"The custom reward was created by a different application, or channel points are "173"not available for the broadcaster (403)",174error.args[1],175403,176) from error177raise178else:179values = [CustomReward(self._http, x, self) for x in data]180self._cached_rewards = time.monotonic(), values181return values182183async def fetch_bits_leaderboard(184self, token: str, period: str = "all", user_id: int = None, started_at: datetime.datetime = None185) -> "BitsLeaderboard":186"""|coro|187188Fetches the bits leaderboard for the channel. This requires an OAuth token with the bits:read scope.189190Parameters191-----------192token: :class:`str`193the OAuth token with the bits:read scope194period: Optional[:class:`str`]195one of `day`, `week`, `month`, `year`, or `all`, defaults to `all`196started_at: Optional[:class:`datetime.datetime`]197the timestamp to start the period at. This is ignored if the period is `all`198user_id: Optional[:class:`int`]199the id of the user to fetch for200"""201from .models import BitsLeaderboard202203data = await self._http.get_bits_board(token, period, user_id, started_at)204return BitsLeaderboard(self._http, data)205206async def start_commercial(self, token: str, length: int) -> dict:207"""|coro|208209Starts a commercial on the channel. Requires an OAuth token with the `channel:edit:commercial` scope.210211Parameters212-----------213token: :class:`str`214the OAuth token215length: :class:`int`216the length of the commercial. Should be one of `30`, `60`, `90`, `120`, `150`, `180`217218Returns219--------220:class:`dict` a dictionary with `length`, `message`, and `retry_after`221"""222data = await self._http.post_commercial(token, str(self.id), length)223return data[0]224225async def create_clip(self, token: str, has_delay=False) -> dict:226"""|coro|227228Creates a clip on the channel. Note that clips are not created instantly, so you will have to query229:meth:`~get_clips` to confirm the clip was created. Requires an OAuth token with the `clips:edit` scope230231Parameters232-----------233token: :class:`str`234the OAuth token235has_delay: :class:`bool`236Whether the clip should have a delay to match that of a viewer. Defaults to False237238Returns239--------240:class:`dict` a dictionary with `id` and `edit_url`241"""242data = await self._http.post_create_clip(token, self.id, has_delay)243return data[0]244245async def fetch_clips(self) -> List["Clip"]:246"""|coro|247248Fetches clips from the api. This will only return clips from the specified user.249Use :class:`Client.fetch_clips` to fetch clips by id250251Returns252--------253List[:class:`twitchio.Clip`]254"""255from .models import Clip256257data = await self._http.get_clips(self.id)258259return [Clip(self._http, x) for x in data]260261async def fetch_hypetrain_events(self, id: str = None, token: str = None):262"""|coro|263264Fetches hypetrain event from the api. Needs a token with the channel:read:hype_train scope.265266Parameters267-----------268id: Optional[:class:`str`]269The hypetrain id, if known, to fetch for270token: Optional[:class:`str`]271The oauth token to use. Will default to the one passed to the bot/client.272273Returns274--------275List[:class:`twitchio.HypeTrainEvent`]276A list of hypetrain events277"""278from .models import HypeTrainEvent279280data = await self._http.get_hype_train(self.id, id=id, token=token)281return [HypeTrainEvent(self._http, d) for d in data]282283async def fetch_bans(self, token: str, userids: List[Union[str, int]] = None) -> List["UserBan"]:284"""|coro|285286Fetches a list of people the User has banned from their channel.287288Parameters289-----------290token: :class:`str`291The oauth token with the moderation:read scope.292userids: List[Union[:class:`str`, :class:`int`]]293An optional list of userids to fetch. Will fetch all bans if this is not passed294"""295data = await self._http.get_channel_bans(token, str(self.id), user_ids=userids)296return [UserBan(self._http, d) for d in data]297298async def fetch_ban_events(self, token: str, userids: List[int] = None):299"""|coro|300301Fetches ban/unban events from the User's channel.302303Parameters304-----------305token: :class:`str`306The oauth token with the moderation:read scope.307userids: List[:class:`int`]308An optional list of users to fetch ban/unban events for309310Returns311--------312List[:class:`twitchio.BanEvent`]313"""314from .models import BanEvent315316data = await self._http.get_channel_ban_unban_events(token, str(self.id), userids)317return [BanEvent(self._http, x, self) for x in data]318319async def fetch_moderators(self, token: str, userids: List[int] = None):320"""|coro|321322Fetches the moderators for this channel.323324Parameters325-----------326token: :class:`str`327The oauth token with the moderation:read scope.328userids: List[:class:`int`]329An optional list of users to check mod status of330331Returns332--------333List[:class:`twitchio.PartialUser`]334"""335data = await self._http.get_channel_moderators(token, str(self.id), user_ids=userids)336return [PartialUser(self._http, d["user_id"], d["user_name"]) for d in data]337338async def fetch_mod_events(self, token: str):339"""|coro|340341Fetches mod events (moderators being added and removed) for this channel.342343Parameters344-----------345token: :class:`str`346The oauth token with the moderation:read scope.347348Returns349--------350List[:class:`twitchio.ModEvent`]351"""352from .models import ModEvent353354data = await self._http.get_channel_mod_events(token, str(self.id))355return [ModEvent(self._http, d, self) for d in data]356357async def automod_check(self, token: str, query: list):358"""|coro|359360Checks if a string passes the automod filter361362Parameters363-----------364token: :class:`str`365The oauth token with the moderation:read scope.366query: List[:class:`AutomodCheckMessage`]367A list of :class:`twitchio.AutomodCheckMessage`368369Returns370--------371List[:class:`twitchio.AutomodCheckResponse`]372"""373from .models import AutomodCheckResponse374375data = await self._http.post_automod_check(token, str(self.id), *[x._to_dict() for x in query])376return [AutomodCheckResponse(d) for d in data]377378async def fetch_stream_key(self, token: str):379"""|coro|380381Fetches the users stream key382383Parameters384-----------385token: :class:`str`386The oauth token with the channel:read:stream_key scope387388Returns389--------390:class:`str`391"""392data = await self._http.get_stream_key(token, str(self.id))393return data394395async def fetch_following(self, token: str = None) -> List["FollowEvent"]:396"""|coro|397398Fetches a list of users that this user is following.399400Parameters401-----------402token: Optional[:class:`str`]403An oauth token to use instead of the bots token404405Returns406--------407List[:class:`twitchio.FollowEvent`]408"""409from .models import FollowEvent410411data = await self._http.get_user_follows(token=token, from_id=str(self.id))412return [FollowEvent(self._http, d, from_=self) for d in data]413414async def fetch_followers(self, token: str = None):415"""|coro|416417Fetches a list of users that are following this user.418419Parameters420-----------421token: Optional[:class:`str`]422An oauth token to use instead of the bots token423424Returns425--------426List[:class:`twitchio.FollowEvent`]427"""428from .models import FollowEvent429430data = await self._http.get_user_follows(to_id=str(self.id))431return [FollowEvent(self._http, d, to=self) for d in data]432433async def fetch_follow(self, to_user: "PartialUser", token: str = None):434"""|coro|435436Check if a user follows another user or when they followed a user.437438Parameters439-----------440to_user: :class:`PartialUser`441token: Optional[:class:`str`]442An oauth token to use instead of the bots token443444Returns445--------446:class:`twitchio.FollowEvent`447"""448if not isinstance(to_user, PartialUser):449raise TypeError(f"to_user must be a PartialUser not {type(to_user)}")450451from .models import FollowEvent452453data = await self._http.get_user_follows(from_id=str(self.id), to_id=str(to_user.id))454return FollowEvent(self._http, data[0]) if data else None455456async def follow(self, userid: int, token: str, *, notifications=False):457"""|coro|458459Follows the user460461Parameters462-----------463userid: :class:`int`464The user id to follow this user with465token: :class:`str`466An oauth token with the user:edit:follows scope467notifications: :class:`bool`468Whether to allow push notifications when this user goes live. Defaults to False469"""470await self._http.post_follow_channel(471token, from_id=str(userid), to_id=str(self.id), notifications=notifications472)473474async def unfollow(self, userid: int, token: str):475"""|coro|476477Unfollows the user478479Parameters480-----------481userid: :class:`int`482The user id to unfollow this user with483token: :class:`str`484An oauth token with the user:edit:follows scope485"""486await self._http.delete_unfollow_channel(token, from_id=str(userid), to_id=str(self.id))487488async def fetch_subscriptions(self, token: str, userids: List[int] = None):489"""|coro|490491Fetches the subscriptions for this channel.492493Parameters494-----------495token: :class:`str`496An oauth token with the channel:read:subscriptions scope497userids: Optional[List[:class:`int`]]498An optional list of userids to look for499500Returns501--------502List[:class:`twitchio.SubscriptionEvent`]503"""504from .models import SubscriptionEvent505506data = await self._http.get_channel_subscriptions(token, str(self.id), user_ids=userids)507return [SubscriptionEvent(self._http, d, broadcaster=self) for d in data]508509async def create_marker(self, token: str, description: str = None):510"""|coro|511512Creates a marker on the stream. This only works if the channel is live (among other conditions)513514Parameters515-----------516token: :class:`str`517An oauth token with the user:edit:broadcast scope518description: :class:`str`519An optional description of the marker520521Returns522--------523:class:`twitchio.Marker`524"""525from .models import Marker526527data = await self._http.post_stream_marker(token, user_id=str(self.id), description=description)528return Marker(data[0])529530async def fetch_markers(self, token: str, video_id: str = None):531"""|coro|532533Fetches markers from the given video id, or the most recent video.534The Twitch api will only return markers created by the user of the authorized token535536Parameters537-----------538token: :class:`str`539An oauth token with the user:edit:broadcast scope540video_id: :class:`str`541A specific video o fetch from. Defaults to the most recent stream if not passed542543Returns544--------545Optional[:class:`twitchio.VideoMarkers`]546"""547from .models import VideoMarkers548549data = await self._http.get_stream_markers(token, user_id=str(self.id), video_id=video_id)550if data:551return VideoMarkers(data[0]["videos"])552553async def fetch_extensions(self, token: str):554"""|coro|555556Fetches extensions the user has (active and inactive)557558Parameters559-----------560token: :class:`str`561An oauth token with the user:read:broadcast scope562563Returns564--------565List[:class:`twitchio.Extension`]566"""567from .models import Extension568569data = await self._http.get_channel_extensions(token)570return [Extension(d) for d in data]571572async def fetch_active_extensions(self, token: str = None):573"""|coro|574575Fetches active extensions the user has.576Returns a dictionary containing the following keys: `panel`, `overlay`, `component`.577578Parameters579-----------580token: Optional[:class:`str`]581An oauth token with the user:read:broadcast *or* user:edit:broadcast scope582583Returns584--------585Dict[:class:`str`, Dict[:class:`int`, :class:`twitchio.ActiveExtension`]]586"""587from .models import ActiveExtension588589data = await self._http.get_user_active_extensions(token, str(self.id))590return {typ: {int(n): ActiveExtension(d) for n, d in vals.items()} for typ, vals in data.items()}591592async def update_extensions(self, token: str, extensions: "ExtensionBuilder"):593"""|coro|594595Updates a users extensions. See the :class:`twitchio.ExtensionBuilder`596597Parameters598-----------599token: :class:`str`600An oauth token with user:edit:broadcast scope601extensions: :class:`twitchio.ExtensionBuilder`602A :class:`twitchio.ExtensionBuilder` to be given to the twitch api603604Returns605--------606Dict[:class:`str`, Dict[:class:`int`, :class:`twitchio.ActiveExtension`]]607"""608from .models import ActiveExtension609610data = await self._http.put_user_extensions(token, extensions._to_dict())611return {typ: {int(n): ActiveExtension(d) for n, d in vals.items()} for typ, vals in data.items()}612613async def fetch_videos(self, period="all", sort="time", type="all", language=None):614"""|coro|615616Fetches videos that belong to the user. If you have specific video ids use :func:`Client.fetch_videos`617618Parameters619-----------620period: :class:`str`621The period for which to fetch videos. Valid values are `all`, `day`, `week`, `month`. Defaults to `all`622sort: :class:`str`623Sort orders of the videos. Valid values are `time`, `trending`, `views`, Defaults to `time`624type: Optional[:class:`str`]625Type of the videos to fetch. Valid values are `upload`, `archive`, `highlight`. Defaults to `all`626language: Optional[:class:`str`]627Language of the videos to fetch. Must be an `ISO-639-1 <https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes>`_ two letter code.628629Returns630--------631List[:class:`twitchio.Video`]632"""633from .models import Video634635data = await self._http.get_videos(user_id=str(self.id), period=period, sort=sort, type=type, language=language)636return [Video(self._http, x, self) for x in data]637638async def end_prediction(639self, token: str, prediction_id: str, status: str, winning_outcome_id: str = None640) -> "Prediction":641"""|coro|642643End a prediction with an outcome.644645Parameters646-----------647token: :class:`str`648An oauth token with the channel:manage:predictions scope649prediction_id: :class:`str`650ID of the prediction to end.651652Returns653--------654:class:`twitchio.Prediction`655"""656from .models import Prediction657658data = await self._http.patch_prediction(659token,660broadcaster_id=str(self.id),661prediction_id=prediction_id,662status=status,663winning_outcome_id=winning_outcome_id,664)665return Prediction(self._http, data[0])666667async def get_predictions(self, token: str, prediction_id: str = None) -> List["Prediction"]:668"""|coro|669670Gets information on a prediction or the list of predictions671if none is provided.672673Parameters674-----------675token: :class:`str`676An oauth token with the channel:manage:predictions scope677prediction_id: :class:`str`678ID of the prediction to receive information about.679680Returns681--------682:class:`twitchio.Prediction`683"""684from .models import Prediction685686data = await self._http.get_predictions(token, broadcaster_id=str(self.id), prediction_id=prediction_id)687return [Prediction(self._http, d) for d in data]688689async def create_prediction(690self, token: str, title: str, blue_outcome: str, pink_outcome: str, prediction_window: int691) -> "Prediction":692"""|coro|693694Creates a prediction for the channel.695696Parameters697-----------698token: :class:`str`699An oauth token with the channel:manage:predictions scope700title: :class:`str`701Title for the prediction (max of 45 characters)702blue_outcome: :class:`str`703Text for the first outcome people can vote for. (max 25 characters)704pink_outcome: :class:`str`705Text for the second outcome people can vote for. (max 25 characters)706prediction_window: :class:`int`707Total duration for the prediction (in seconds)708709Returns710--------711:class:`twitchio.Prediction`712"""713from .models import Prediction714715data = await self._http.post_prediction(716token,717broadcaster_id=str(self.id),718title=title,719blue_outcome=blue_outcome,720pink_outcome=pink_outcome,721prediction_window=prediction_window,722)723return Prediction(self._http, data[0])724725async def modify_stream(self, token: str, game_id: int = None, language: str = None, title: str = None):726"""|coro|727728Modify stream information729Parameters730-----------731token: :class:`str`732An oauth token with the channel:manage:broadcast scope733game_id: :class:`int`734Optional game ID being played on the channel. Use 0 to unset the game.735language: :class:`str`736Optional language of the channel. A language value must be either the ISO 639-1 two-letter code for a supported stream language or “other”.737title: :class:`str`738Optional title of the stream.739"""740if game_id is not None:741game_id = str(game_id)742743await self._http.patch_channel(744token,745broadcaster_id=str(self.id),746game_id=game_id,747language=language,748title=title,749)750751async def fetch_schedule(752self,753segment_ids: List[str] = None,754start_time: datetime.datetime = None,755utc_offset: int = None,756first: int = 20,757):758"""|coro|759760Fetches the schedule of a streamer761Parameters762-----------763segment_ids: Optional[List[:class:`str`]]764List of segment IDs of the stream schedule to return. Maximum: 100765start_time: Optional[:class:`datetime.datetime`]766A datetime object to start returning stream segments from. If not specified, the current date and time is used.767utc_offset: Optional[:class:`int`]768A timezone offset for the requester specified in minutes. +4 hours from GMT would be `240`769first: Optional[:class:`int`]770Maximum number of stream segments to return. Maximum: 25. Default: 20.771Returns772--------773:class:`twitchio.Schedule`774"""775from .models import Schedule776777data = await self._http.get_channel_schedule(778broadcaster_id=str(self.id),779segment_ids=segment_ids,780start_time=start_time,781utc_offset=utc_offset,782first=first,783)784785return Schedule(self._http, data)786787async def fetch_channel_teams(self):788"""|coro|789790Fetches a list of Twitch Teams of which the specified channel/broadcaster is a member.791792Returns793--------794List[:class:`twitchio.ChannelTeams`]795"""796from .models import ChannelTeams797798data = await self._http.get_channel_teams(799broadcaster_id=str(self.id),800)801802return [ChannelTeams(self._http, x) for x in data]803804805class BitLeaderboardUser(PartialUser):806807__slots__ = "rank", "score"808809def __init__(self, http: "TwitchHTTP", data: dict):810super(BitLeaderboardUser, self).__init__(http, id=data["user_id"], name=data["user_name"])811self.rank: int = data["rank"]812self.score: int = data["score"]813814815class UserBan(PartialUser):816817__slots__ = ("expires_at",)818819def __init__(self, http: "TwitchHTTP", data: dict):820super(UserBan, self).__init__(http, name=data["user_login"], id=data["user_id"])821self.expires_at = (822datetime.datetime.strptime(data["expires_at"], "%Y-%m-%dT%H:%M:%SZ") if data["expires_at"] else None823)824825826class SearchUser(PartialUser):827828__slots__ = "game_id", "name", "display_name", "language", "title", "thumbnail_url", "live", "started_at", "tag_ids"829830def __init__(self, http: "TwitchHTTP", data: dict):831self._http = http832self.display_name: str = data["display_name"]833self.name: str = data["broadcaster_login"]834self.id: int = int(data["id"])835self.game_id: str = data["game_id"]836self.title: str = data["title"]837self.thumbnail_url: str = data["thumbnail_url"]838self.language: str = data["broadcaster_language"]839self.live: bool = data["is_live"]840self.started_at = datetime.datetime.strptime(data["started_at"], "%Y-%m-%dT%H:%M:%SZ") if self.live else None841self.tag_ids: List[str] = data["tag_ids"]842843844class User(PartialUser):845846__slots__ = (847"_http",848"id",849"name",850"display_name",851"type",852"broadcaster_type",853"description",854"profile_image",855"offline_image",856"view_count",857"created_at",858"email",859"_cached_rewards",860)861862def __init__(self, http: "TwitchHTTP", data: dict):863self._http = http864self.id = int(data["id"])865self.name: str = data["login"]866self.display_name: str = data["display_name"]867self.type = UserTypeEnum(data["type"])868self.broadcaster_type = BroadcasterTypeEnum(data["broadcaster_type"])869self.description: str = data["description"]870self.profile_image: str = data["profile_image_url"]871self.offline_image: str = data["offline_image_url"]872self.view_count: Tuple[int] = (data["view_count"],) # this isn't supposed to be a tuple but too late to fix it!873self.created_at = parse_timestamp(data["created_at"])874self.email: Optional[str] = data.get("email")875self._cached_rewards = None876877def __repr__(self):878return f"<User id={self.id} name={self.name} display_name={self.display_name} type={self.type}>"879880881