class TwitchHelix(object):
    """
    Twitch Helix API
    """

    def __init__(
        self, client_id=None, oauth_token=None, client_secret=None, scopes=None
    ):
        self._client_id = client_id
        self._oauth_token = oauth_token
        self._client_secret = client_secret
        self._scopes = scopes

        if not client_id:
            self._client_id, self._oauth_token = credentials_from_config_file()

    def get_oauth(self):
        if not self._client_secret or not self._client_id:
            raise TwitchOAuthException(
                "Client Id and Client Secret are not both present."
            )

        if not self._scopes:
            response = post(
                BASE_OAUTH_URL + f"token?client_id={self._client_id}"
                f"&client_secret={self._client_secret}"
                f"&grant_type=client_credentials"
            )
            response = response.json()
        else:
            scopes_str = "+".join(self._scopes)
            response = post(
                BASE_OAUTH_URL + f"token?client_id={self._client_id}"
                f"&client_secret={self._client_secret}"
                f"&grant_type=client_credentials&scope={scopes_str}"
            )
            response = response.json()

        if "access_token" in response:
            self._oauth_token = response["access_token"]
        elif "message" in response:
            raise TwitchOAuthException(response["message"])
        else:
            raise TwitchOAuthException()

    def get_streams(
        self,
        after=None,
        before=None,
        community_ids=None,
        page_size=20,
        game_ids=None,
        languages=None,
        user_ids=None,
        user_logins=None,
    ):

        if community_ids and len(community_ids) > 100:
            raise TwitchAttributeException(
                "Maximum of 100 Community IDs can be supplied"
            )
        if game_ids and len(game_ids) > 100:
            raise TwitchAttributeException("Maximum of 100 Game IDs can be supplied")
        if languages and len(languages) > 100:
            raise TwitchAttributeException("Maximum of 100 languages can be supplied")
        if user_ids and len(user_ids) > 100:
            raise TwitchAttributeException("Maximum of 100 User IDs can be supplied")
        if user_logins and len(user_logins) > 100:
            raise TwitchAttributeException(
                "Maximum of 100 User login names can be supplied"
            )
        if page_size > 100:
            raise TwitchAttributeException("Maximum number of objects to return is 100")

        params = {
            "after": after,
            "before": before,
            "community_id": community_ids,
            "first": page_size,
            "game_id": game_ids,
            "language": languages,
            "user_id": user_ids,
            "user_login": user_logins,
        }

        return APICursor(
            client_id=self._client_id,
            oauth_token=self._oauth_token,
            path="streams",
            resource=Stream,
            params=params,
        )

    def get_games(self, game_ids=None, names=None):
        if game_ids and len(game_ids) > 100:
            raise TwitchAttributeException("Maximum of 100 Game IDs can be supplied")
        if names and len(names) > 100:
            raise TwitchAttributeException("Maximum of 100 Game names can be supplied")

        params = {
            "id": game_ids,
            "name": names,
        }
        return APIGet(
            client_id=self._client_id,
            oauth_token=self._oauth_token,
            path="games",
            resource=Game,
            params=params,
        ).fetch()

    def get_clips(
        self,
        broadcaster_id=None,
        game_id=None,
        clip_ids=None,
        after=None,
        before=None,
        started_at=None,
        ended_at=None,
        page_size=20,
    ):
        if not broadcaster_id and not clip_ids and not game_id:
            raise TwitchAttributeException(
                "At least one of the following parameters must be provided "
                "[broadcaster_id, clip_ids, game_id]"
            )
        if clip_ids and len(clip_ids) > 100:
            raise TwitchAttributeException("Maximum of 100 Clip IDs can be supplied")
        if page_size > 100:
            raise TwitchAttributeException("Maximum number of objects to return is 100")

        params = {
            "broadcaster_id": broadcaster_id,
            "game_id": game_id,
            "id": clip_ids,
            "after": after,
            "before": before,
            "started_at": started_at,
            "ended_at": ended_at,
        }

        if broadcaster_id or game_id:
            params["first"] = page_size

            return APICursor(
                client_id=self._client_id,
                oauth_token=self._oauth_token,
                path="clips",
                resource=Clip,
                params=params,
            )

        else:
            return APIGet(
                client_id=self._client_id,
                oauth_token=self._oauth_token,
                path="clips",
                resource=Clip,
                params=params,
            ).fetch()

    def get_top_games(self, after=None, before=None, page_size=20):
        if page_size > 100:
            raise TwitchAttributeException("Maximum number of objects to return is 100")

        params = {
            "after": after,
            "before": before,
            "first": page_size,
        }

        return APICursor(
            client_id=self._client_id,
            oauth_token=self._oauth_token,
            path="games/top",
            resource=Game,
            params=params,
        )

    def get_videos(
        self,
        video_ids=None,
        user_id=None,
        game_id=None,
        after=None,
        before=None,
        page_size=20,
        language=None,
        period=PERIOD_ALL,
        sort=VIDEO_SORT_TIME,
        video_type=VIDEO_TYPE_ALL,
    ):
        if video_ids and len(video_ids) > 100:
            raise TwitchAttributeException("Maximum of 100 Video IDs can be supplied")

        params = {
            "id": video_ids,
            "user_id": user_id,
            "game_id": game_id,
        }

        if user_id or game_id:
            if page_size > 100:
                raise TwitchAttributeException(
                    "Maximum number of objects to return is 100"
                )
            if period not in PERIODS:
                raise TwitchAttributeException(
                    "Invalid value for period. Valid values are {}".format(PERIODS)
                )
            if sort not in VIDEO_SORTS:
                raise TwitchAttributeException(
                    "Invalid value for sort. Valid values are {}".format(VIDEO_SORTS)
                )
            if video_type not in VIDEO_TYPES:
                raise TwitchAttributeException(
                    "Invalid value for video_type. Valid values are {}".format(
                        VIDEO_TYPES
                    )
                )

            params["after"] = after
            params["before"] = before
            params["first"] = page_size
            params["language"] = language
            params["period"] = period
            params["sort"] = sort
            params["type"] = video_type

            return APICursor(
                client_id=self._client_id,
                oauth_token=self._oauth_token,
                path="videos",
                resource=Video,
                params=params,
            )
        else:
            return APIGet(
                client_id=self._client_id,
                oauth_token=self._oauth_token,
                path="videos",
                resource=Video,
                params=params,
            ).fetch()

    def get_streams_metadata(
        self,
        after=None,
        before=None,
        community_ids=None,
        page_size=20,
        game_ids=None,
        languages=None,
        user_ids=None,
        user_logins=None,
    ):

        if community_ids and len(community_ids) > 100:
            raise TwitchAttributeException(
                "Maximum of 100 Community IDs can be supplied"
            )
        if game_ids and len(game_ids) > 100:
            raise TwitchAttributeException("Maximum of 100 Game IDs can be supplied")
        if languages and len(languages) > 100:
            raise TwitchAttributeException("Maximum of 100 languages can be supplied")
        if user_ids and len(user_ids) > 100:
            raise TwitchAttributeException("Maximum of 100 User IDs can be supplied")
        if user_logins and len(user_logins) > 100:
            raise TwitchAttributeException(
                "Maximum of 100 User login names can be supplied"
            )
        if page_size > 100:
            raise TwitchAttributeException("Maximum number of objects to return is 100")

        params = {
            "after": after,
            "before": before,
            "community_id": community_ids,
            "first": page_size,
            "game_id": game_ids,
            "language": languages,
            "user_id": user_ids,
            "user_login": user_logins,
        }

        return APICursor(
            client_id=self._client_id,
            oauth_token=self._oauth_token,
            path="streams/metadata",
            resource=StreamMetadata,
            params=params,
        )

    def get_user_follows(self, after=None, page_size=20, from_id=None, to_id=None):
        if not from_id and not to_id:
            raise TwitchAttributeException("from_id or to_id must be provided.")
        if page_size > 100:
            raise TwitchAttributeException("Maximum number of objects to return is 100")

        params = {
            "after": after,
            "first": page_size,
            "from_id": from_id,
            "to_id": to_id,
        }

        return APICursor(
            client_id=self._client_id,
            oauth_token=self._oauth_token,
            path="users/follows",
            resource=Follow,
            params=params,
        )

    def get_users(self, login_names=None, ids=None):
        """https://dev.twitch.tv/docs/api/reference#get-users"""
        if not login_names:
            login_names = []
        if not ids:
            ids = []
        if len(login_names) + len(ids) > 100:
            raise TwitchAttributeException("Sum of names and ids must not exceed 100!")
        params = {"login": login_names, "id": ids}

        return APIGet(
            client_id=self._client_id,
            oauth_token=self._oauth_token,
            path="users",
            resource=User,
            params=params,
        ).fetch()

    def get_tags(self, after=None, page_size=20, tag_ids=None):
        """https://dev.twitch.tv/docs/api/reference#get-all-stream-tags"""

        if tag_ids and len(tag_ids) > 100:
            raise TwitchAttributeException("Maximum of 100 Tag IDs can be supplied")
        if page_size > 100:
            raise TwitchAttributeException("Maximum number of objects to return is 100")

        params = {"after": after, "first": page_size, "tag_id": tag_ids}

        return APICursor(
            client_id=self._client_id,
            oauth_token=self._oauth_token,
            path="tags/streams",
            resource=Tag,
            params=params,
        ) BASE_HELIX_URL = "https://api.twitch.tv/helix/" BASE_OAUTH_URL = "https://id.twitch.tv/oauth2/" class TwitchAttributeException(TwitchException):
    pass class TwitchOAuthException(TwitchException):
    pass class APICursor(TwitchAPIMixin):
    def __init__(
        self, client_id, path, resource, oauth_token=None, cursor=None, params=None
    ):
        super(APICursor, self).__init__()
        self._path = path
        self._queue = []
        self._cursor = cursor
        self._resource = resource
        self._client_id = client_id
        self._oauth_token = oauth_token
        self._params = params
        self._total = None
        self._requests_count = 0

        # Pre-fetch the first page as soon as cursor is instantiated
        self.next_page()

    def __repr__(self):
        return str(self._queue)

    def __len__(self):
        return len(self._queue)

    def __iter__(self):
        return self

    def __next__(self):
        if not self._queue and not self.next_page():
            raise StopIteration()

        return self._queue.pop(0)

    # Python 2 compatibility.
    next = __next__

    def __getitem__(self, index):
        return self._queue[index]

    def next_page(self):
        # Twitch stops returning a cursor when you're on the last page. So if we've made
        # more than 1 request to their API and we don't get a cursor back, it means
        # we're on the last page, so return whatever's left in the queue.
        if self._requests_count > 0 and not self._cursor:
            return self._queue

        if self._cursor:
            self._params["after"] = self._cursor

        response = self._request_get(self._path, params=self._params)
        self._requests_count += 1
        self._queue = [self._resource.construct_from(data) for data in response["data"]]
        self._cursor = response["pagination"].get("cursor")
        self._total = response.get("total")
        return self._queue

    @property
    def cursor(self):
        return self._cursor

    @property
    def total(self):
        if not self._total:
            raise TwitchNotProvidedException()
        return self._total class Clip(TwitchObject):
    pass class Follow(TwitchObject):
    pass class Game(TwitchObject):
    pass class Stream(TwitchObject):
    pass class StreamMetadata(TwitchObject):
    pass class Video(TwitchObject):
    pass
import json
import pytest
import responses
from datetime import datetime
from twitch import TwitchHelix
from twitch.constants import BASE_HELIX_URL, BASE_OAUTH_URL
from twitch.exceptions import TwitchAttributeException, TwitchOAuthException
from twitch.helix.base import APICursor
from twitch.resources import Clip, Follow, Game, Stream, StreamMetadata, Video
    assert len(responses.calls) == 1
    assert isinstance(videos, list)
    video = videos[0]
    assert isinstance(video, Video)
    assert (
        responses.calls[0].request.url
        == "https://api.twitch.tv/helix/videos?id=234482848"
    )


@responses.activate
@pytest.mark.parametrize(
    "param,value",
    [("game_id", ["23161357", "12345678"]), ("user_id", ["23161357", "12345678"])],
)
def test_get_videos_next_returns_video_object(param, value):
    responses.add(
        responses.GET,
        "{}videos".format(BASE_HELIX_URL),
        body=json.dumps(example_get_videos_cursor_response),
        status=200,
        content_type="application/json",
    )

    client = TwitchHelix("client id")

    kwargs = {param: value}
    videos = client.get_videos(**kwargs)
    video = videos.next()

    assert len(responses.calls) == 1
    assert isinstance(videos, APICursor)
    assert videos._cursor == example_get_videos_cursor_response["pagination"]["cursor"]

    assert isinstance(video, Video)
    assert video.id == example_get_videos_cursor_response["data"][0]["id"]
    assert video.title == example_get_videos_response["data"][0]["title"]
    assert video.created_at == datetime(2018, 3, 2, 20, 53, 41)


@responses.activate
@pytest.mark.parametrize(
    "param,value", [("game_id", "23161357"), ("user_id", "23161357")]
)
def test_get_videos_passes_correct_params_when_user_or_game_is_set(param, value):
    responses.add(
        responses.GET,
        "{}videos".format(BASE_HELIX_URL),
        body=json.dumps(example_get_videos_cursor_response),
        status=200,
        content_type="application/json",
    )

    client = TwitchHelix("client id")
    kwargs = {
        param: value,
        "after": "eyJiIjpudWxsLCJhIjp7Ik9mZnNldCI6MjB9fQ==",
        "before": "eyJiIjp7Ik9mZnNldCI6MH0sImEiOnsiT2Zmc2V0Ijo0MH19==",
        "page_size": 100,
        "language": "en",
    }
    videos = client.get_videos(**kwargs)
    video = videos.next()

    assert len(responses.calls) == 1
    assert isinstance(videos, APICursor)
    assert isinstance(video, Video)

    url = responses.calls[0].request.url
    url.startswith("https://api.twitch.tv/helix/videos?")
    assert "{}=23161357".format(param) in url
    assert "after=eyJiIjpudWxsLCJhIjp7Ik9mZnNldCI6MjB9fQ%3D%3D" in url
    assert "before=eyJiIjp7Ik9mZnNldCI6MH0sImEiOnsiT2Zmc2V0Ijo0MH19%3D%3D" in url
    assert "first=100" in url
    assert "language=en" in url
    assert "period=all" in url
    assert "sort=time" in url
    assert "type=all" in url


@responses.activate
def test_get_videos_raises_attribute_exception_for_invalid_video_ids():
    responses.add(
        responses.GET,
        "{}videos".format(BASE_HELIX_URL),
        body=json.dumps(example_get_videos_response),
        status=200,
        content_type="application/json",
    )

    client = TwitchHelix("client id")
    kwargs = {"video_ids": ["12345"] * 101}
    with pytest.raises(TwitchAttributeException):
        client.get_videos(**kwargs)

    assert len(responses.calls) == 0


@responses.activate
@pytest.mark.parametrize(
    "param,value",
    [
        ("page_size", 101),
        ("period", "pogchamp"),
        ("sort", "pogchamp"),
        ("video_type", "pogchamp"),
    ],
)
def test_get_videos_raises_attribute_exception_for_invalid_params(param, value):
    responses.add(
        responses.GET,
        "{}videos".format(BASE_HELIX_URL),
        body=json.dumps(example_get_videos_cursor_response),
        status=200,
        content_type="application/json",
    )

    client = TwitchHelix("client id")

    kwargs = {"game_id": "123456", param: value}

    with pytest.raises(TwitchAttributeException):