PK!11LICENSEMIT License Copyright (c) 2018 Andrew Grinevich Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!y*aV README.rst=============================== Api-wrapper for coub.com =============================== .. image:: https://travis-ci.com/Derfirm/coub_api.svg?branch=master :target: https://travis-ci.com/Derfirm/coub_api :alt: Build Status .. image:: https://codecov.io/gh/Derfirm/coub_api/branch/master/graph/badge.svg :target: https://codecov.io/gh/Derfirm/coub_api :alt: Coverage Status .. image:: https://img.shields.io/pypi/v/coub_api.svg :target: https://github.com/Derfirm/coub_api :alt: pypi version .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/ambv/black :alt: Codestyle: Black Key Features ============ - response are fully-annotated with pydantic_ - test work on snapshots from real http-answers (can easy inspect responses) - own OAuth2-server .. _pydantic: https://pydantic-docs.helpmanual.io/ Getting started =============== Initiate Api client ________ .. code-block:: python from coub_api import CoubApi api = CoubApi() access_token = "" api.authenticate(access_token) # required for some authenticated requests Get coub details ________________ .. code-block:: python coub = api.coubs.get_coub("1jf5v1") print(coub.id, coub.channel_id) Create Coub ___________ .. code-block:: python api.coubs.init_upload()) # {"permalink":"1jik0b","id":93927327} api.coubs.upload_video(93927327, "video.mp4") api.coubs.upload_audio(93927327, "audio.mp3")) api.coubs.finalize_upload(93927327, title="Awesome CAT", tags=["cat", "animal"])) api.coubs.get_upload_status(93927327)) # {"done": False, "percent_done": 0} # wait a minute api.coubs.get_upload_status(93927327)) # {"done": True, "percent_done": 100} Get weekly hot coubs ___________ .. code-block:: python from coub_api.schemas.constants import Period api.timeline.hot(period=Period.WEEKLY, order_by="likes_count") Get 5 page of random section with cars ___________ .. code-block:: python from coub_api.schemas.constants import Section, Category current_page = 1 max_page = 5 while current_page <= max_page: response = api.timeline.section(section=Section.RANDOM, category=Category.CARS, page=current_page) print(f"processing {current_page} of {max_page}") for coub in response.coubs: print(coub.permalink) current_page += 1 max_page = min(max_page, response.total_pages) OAuth2-Server =============== How to use: ___________ - Create Your Own_ application - Run server .. code-block:: RST coub-oauth2-server - Enter Your Application Id and Secret and grant access the Coub server. - Copy access token and start use it! .. _Own: http://coub.com/dev/applicationsPK!& str: return urljoin(COUB_URL, f"{API_PATH}{path}") @staticmethod def request(method: str, url: str, **kwargs): response = requests.request(method, url, **kwargs) response.raise_for_status() data = response.json() return data def authenticated_request( self, method: str, url: str, *, params: Optional[dict] = None, **kwargs ): if self.token is None: raise ValueError("token required") params = params or {} params.update({"access_token": self.token}) return self.request(method, url, params=params, **kwargs) PK!? coub_api/modules/channel.pyfrom typing import Optional from coub_api.schemas.channel import ChannelBig, ChannelResponse from .base import BaseConnector __all__ = ("Channel",) # https://coub.com/dev/docs/Coub+API%2FChannels class Channel(BaseConnector): __slots__ = () def get_data(self, channel_id: int): url = self.build_url(f"/channels/{channel_id}") return self.request("get", url) def create(self, title: str, permalink: str, category: str): if len(permalink) < 8: raise ValueError(f"To short permalink {permalink}, required min 8 symbols") url = self.build_url("/channels") params = { "channels[title]": title, "channels[permalink]": permalink, "channels[category]": category, } return ChannelResponse(**self.authenticated_request("post", url, params=params)) def delete(self, channel_id: int): url = self.build_url(f"/channels/{channel_id}") return ChannelResponse(**self.authenticated_request("delete", url)) def update_data(self): raise NotImplementedError url = self.build_url("/channels/update_info") params = {} return self.authenticated_request("put", url, params=params) # TODO def change_avatar(self, channel_id: int, image_path: str): # raise NotImplementedError # import base64 # with open(image_path, "rb") as image_file: # encoded_string = base64.b64encode(image_file.read()) url = self.build_url("/channels/upload_avatar") params = { # "utf8": True, "channels[id]": channel_id, # "channels[avatar]": encoded_string # "channels[avatar]": encoded_string # "authenticity_token": self.token } data = {"channels[avatar]": open(image_path, "rb")} self.authenticated_request("post", url, params=params, data=data) # return ChannelBig(**self.authenticated_request("post", url, files=files)) def delete_avatar(self, channel_id: int): url = self.build_url("/channels/delete_avatar") params = {"channels[id]": channel_id} return ChannelBig(**self.authenticated_request("delete", url, params=params)) # TODO def add_background( self, channel_id: int, coub_permalink: Optional[str], image_path: Optional[str] ): if coub_permalink and image_path: raise ValueError("only one params is required!") if coub_permalink: params = {"background[coub]": coub_permalink} else: params = {} url = self.build_url(f"channels/{channel_id}/backgrounds") return self.authenticated_request("post", url, params=params) def change_background_position(self, channel_id: int, offset_y: float): url = self.build_url(f"channels/{channel_id}/backgrounds") params = {"offset_y": offset_y} return self.authenticated_request("post", url, params=params)["status"] def delete_background(self, channel_id: int): url = self.build_url(f"channels/{channel_id}/backgrounds") return self.authenticated_request("delete", url)["status"] # TODO channels Recommendation PK!'A A coub_api/modules/coubs.pyfrom typing import List from coub_api.schemas.coub import BigCoub from coub_api.schemas.constants import VisibilityType from .base import BaseConnector __all__ = ("Coubs",) class Coubs(BaseConnector): __slots__ = () def get_coub(self, coub_id: str) -> BigCoub: url = self.build_url(f"/coubs/{coub_id}") return BigCoub(**self.request("get", url)) def edit_coub( self, coub_permalink: str, channel_id: int, *, title: str, tags: List[str], visibility_type: VisibilityType = VisibilityType.PRIVATE, ) -> BigCoub: url = self.build_url(f"/coubs/{coub_permalink}/update_info") params = { "coub[channel_id]": channel_id, "coub[title]": title, "coub[tags]": ",".join(tags), "coub[original_visibility_type]": visibility_type, } response = self.authenticated_request("post", url, params=params) return BigCoub(**response) def delete_coub(self, coub_permalink: str): raise NotImplementedError # see https://coub.com/dev/docs/Coub+API%2FCreating+coub def init_upload(self): url = self.build_url("/coubs/init_upload") return self.authenticated_request("post", url) def upload_video( self, coub_id: int, video_path: str, content_type: str = "video/mp4" ): # content_types: # https://gist.github.com/Derfirm/5b11f77d64816153024e979141b69800 url = self.build_url(f"/coubs/{coub_id}/upload_video") headers = {"Content-type": content_type} return self.authenticated_request( "post", url, data=open(video_path, "rb"), headers=headers ) def upload_audio( self, coub_id: int, audio_path: str, content_type: str = "audio/mpeg" ): # content_types: # https://gist.github.com/Derfirm/5b11f77d64816153024e979141b69800 headers = {"Content-type": content_type} url = self.build_url(f"/coubs/{coub_id}/upload_audio") return self.authenticated_request( "post", url, data=open(audio_path, "rb"), headers=headers ) def finalize_upload( self, coub_id: int, *, title: str, tags: List[str], visibility_type: VisibilityType = VisibilityType.PRIVATE, sound_enabled: bool = True, ): url = self.build_url(f"/coubs/{coub_id}/finalize_upload") params = { "sound_enabled": sound_enabled, "title": title, "original_visibility_type": visibility_type, "tags": ",".join(tags), } return self.authenticated_request("post", url, params=params) def get_upload_status(self, coub_id: int): url = self.build_url(f"/coubs/{coub_id}/finalize_status") return self.authenticated_request("get", url) PK!˜bbcoub_api/modules/follows.pyfrom .base import BaseConnector __all__ = ("Follow",) # see https://coub.com/dev/docs/Coub+API%2FFollowing class Follow(BaseConnector): __slots__ = () def follow(self, user_id: int, channel_id: int): url = self.build_url("/follows") params = {"id": user_id, "channel_id": channel_id} return self.authenticated_request("post", url, params=params) def unfollow(self, user_id: int, channel_id: int): url = self.build_url("/follows") params = {"id": user_id, "channel_id": channel_id} return self.authenticated_request("delete", url, params=params) PK!u**coub_api/modules/friends.pyfrom typing import Dict, Union, Optional from coub_api.schemas.friends import ( FollowResponse, FriendsResponse, RecommendedResponse, ) from coub_api.schemas.constants import Provider from .base import BaseConnector __all__ = ("Friends",) # see https://coub.com/dev/docs/Coub+API%2FFriends class Friends(BaseConnector): __slots__ = () def find(self, provider: Optional[Provider] = None) -> FriendsResponse: raise NotImplementedError def get_data( self, provider: Optional[Provider] = None, *, page: int = 1, per_page: int = 10 ): url = self.build_url("/friends") params: Dict[str, Union[str, int]] = {"page": page, "per_page": per_page} if provider is not None: params.update({"provider": provider}) return FriendsResponse(**self.authenticated_request("get", url, params=params)) def get_recommended( self, q: str, *, page: int = 1, per_page: int = 10 ) -> RecommendedResponse: url = self.build_url("/friends/recommended") params = {"q": q, "page": page, "per_page": per_page} return RecommendedResponse( **self.authenticated_request("get", url, params=params) ) def to_follow( self, *, req_type: str = "initial.json", count: int = 20 ) -> FollowResponse: # req_type may be "initial.json" and "next" url = self.build_url("/friends/friends_to_follow") params = {"type": req_type, "count": count} return FollowResponse(**self.authenticated_request("get", url, params=params)) PK!Dukkcoub_api/modules/likes.pyfrom .base import BaseConnector __all__ = ("Likes",) # see https://coub.com/dev/docs/Coub+API%2FLikes class Likes(BaseConnector): __slots__ = () def do_like(self, coub_id: int, from_channel_id: int): url = self.build_url("/likes") params = {"id": coub_id, "channel_id": from_channel_id} return self.authenticated_request("post", url, params=params) def unlike(self, coub_id: int, from_channel_id: int): url = self.build_url("/likes") params = {"id": coub_id, "channel_id": from_channel_id} return self.authenticated_request("delete", url, params=params) PK!"Tcoub_api/modules/metadata.pyfrom typing import List, Optional from coub_api.schemas.metadata import MetaResponse from .base import BaseConnector __all__ = ("MetaData",) class MetaData(BaseConnector): __slots__ = () def likes_list(self, coub_id: int, page: int = 1) -> MetaResponse: url = self.build_url("/action_subjects_data/coub_likes_list") params = {"id": coub_id, "page": page} return MetaResponse(**self.authenticated_request("get", url, params=params)) def recoubs_list( self, coub_id: int, page: int = 1, ids: Optional[List[int]] = None ) -> MetaResponse: url = self.build_url("/action_subjects_data/recoubs_list") params = {"id": coub_id, "page": page} if ids: assert len(ids) == 1, "coub cant process more than 1 ids" params.update({"ids[]": ids[0]}) return MetaResponse(**self.authenticated_request("get", url, params=params)) def followers_list( self, channel_id: int, page: int = 1, ids: Optional[List[int]] = None ) -> MetaResponse: url = self.build_url("/action_subjects_data/followers_list") params = {"id": channel_id, "page": page} if ids: assert len(ids) == 1, "coub cant process more than 1 ids" params.update({"ids[]": ids[0]}) return MetaResponse(**self.authenticated_request("get", url, params=params)) def followings_list(self, channel_id: int, page: int = 1) -> MetaResponse: url = self.build_url("/action_subjects_data/followings_list") params = {"id": channel_id, "page": page} return MetaResponse(**self.authenticated_request("get", url, params=params)) PK!,^RR!coub_api/modules/notifications.pyfrom .base import BaseConnector __all__ = ("Notifications",) class Notifications(BaseConnector): __slots__ = () def get_list(self, *, page: int = 1, per_page: int = 10): url = self.build_url("/notifications") params = {"page": page, "per_page": per_page} return self.authenticated_request("get", url, params=params) def set_viewed(self): raise NotImplementedError url = self.build_url("/channels/notifications_viewed") headers = {"access_token": self.token} return self.authenticated_request("get", url, headers=headers) PK!coub_api/modules/recoub.pyfrom coub_api.schemas.coub import BigCoub from .base import BaseConnector __all__ = ("Recoub",) # see https://coub.com/dev/docs/Coub+API%2FRecoubs class Recoub(BaseConnector): __slots__ = () def make(self, recoub_to_id: int, channel_id: int): url = self.build_url("/recoubs") params = {"recoub_to_id": recoub_to_id, "channel_id": channel_id} return BigCoub(**self.authenticated_request("post", url, params=params)) def delete(self, recoub_id: int, channel_id): url = self.build_url("/recoubs") params = {"id": recoub_id, "channel_id": channel_id} return self.authenticated_request("delete", url, params=params) PK!<cccoub_api/modules/search.pyfrom typing import List from coub_api.schemas.coub import CoubTags from coub_api.schemas.search import ( CoubSearchResponse, ChannelSearchResponse, GeneralSearchResponse, ) from .base import BaseConnector __all__ = ("Search",) class Search(BaseConnector): __slots__ = () def all( self, q: str, *, page: int = 1, per_page: int = 10, order_by: str = "newest_popular", ): # likes_count, views_count , newest, oldest, newest_popular url = self.build_url("/search") params = {"q": q, "page": page, "per_page": per_page, "order_by": order_by} return GeneralSearchResponse(**self.request("get", url, params=params)) def channels( self, q: str, *, page: int = 1, per_page: int = 10, order_by: str = "newest" ): # newest, followers_count url = self.build_url("/search/channels") params = {"q": q, "page": page, "order_by": order_by, "per_page": per_page} return ChannelSearchResponse(**self.request("get", url, params=params)) def coubs( self, q: str, *, page: int = 1, per_page: int = 10, order_by: str = "newest" ): # newest, followers_count url = self.build_url("/search/coubs") params = {"q": q, "page": page, "order_by": order_by, "per_page": per_page} return CoubSearchResponse(**self.request("get", url, params=params)) def tags(self, title: str) -> List[CoubTags]: url = self.build_url("/tags/search") params = {"title": title} return [CoubTags(**v) for v in self.request("get", url, params=params)] PK!6C8 8 coub_api/modules/timelines.pyfrom coub_api.schemas.timeline import TimeLineResponse, SectionTimeLineResponse from coub_api.schemas.constants import Period, Section, Category from .base import BaseConnector __all__ = ("Timeline",) class Timeline(BaseConnector): __slots__ = () def hot( self, *, order_by: str = "newest_popular", category: Category = Category.ALL, period: Period = Period.DAILY, page: int = 1, per_page: int = 10, ) -> SectionTimeLineResponse: # order_by: likes_count, views_count, newest_popular, oldest if category and period: _path = f"{category}/{period}" else: _path = category or period url = self.build_url(f"/timeline/hot/{_path}") params = {"order_by": order_by, "per_page": per_page, "page": page} return SectionTimeLineResponse(**self.request("get", url, params=params)) def section( self, section: Section, *, category: Category = Category.ALL, page: int = 1, per_page: int = 10, ) -> SectionTimeLineResponse: if category == Category.ALL: _path = f"explore/{section}/" else: _path = f"{section}/{category}" url = self.build_url(f"/timeline/{_path}") params = {"per_page": per_page, "page": page} return SectionTimeLineResponse(**self.request("get", url, params=params)) def user(self, *, page: int = 1, per_page: int = 10) -> TimeLineResponse: url = self.build_url("/timeline") params = {"page": page, "per_page": per_page} return TimeLineResponse(**self.authenticated_request("get", url, params=params)) def channel( self, channel_id: int, *, page: int = 1, per_page: int = 10, order_by: str = "views_count", ) -> TimeLineResponse: # likes_count, views_count, newest_popular. url = self.build_url(f"/timeline/channel/{channel_id}") params = {"page": page, "per_page": per_page, "order_by": order_by} return TimeLineResponse(**self.request("get", url, params=params)) def tag_feed( self, tag_name: str, *, page: int = 1, per_page: int = 10, order_by: str = "oldest", ) -> TimeLineResponse: # likes_count, views_count, newest_popular, oldest url = self.build_url(f"/timeline/tag/{tag_name}") params = {"page": page, "per_page": per_page, "order_by": order_by} return TimeLineResponse(**self.request("get", url, params=params)) def me_liked(self, *, page: int = 1, per_page: int = 10) -> TimeLineResponse: url = self.build_url("/timeline/likes") params = {"page": page, "per_page": per_page} return TimeLineResponse(**self.authenticated_request("get", url, params=params)) PK!9.coub_api/modules/user.pyfrom coub_api.schemas.users import UserResponse from .base import BaseConnector __all__ = ("User",) class User(BaseConnector): __slots__ = () def me(self): url = self.build_url("/users/me") return UserResponse(**self.authenticated_request("get", url)) def change_channel(self, channel_id: int): url = self.build_url("/users/change_channel") params = {"channel_id": channel_id} return self.authenticated_request("put", url, params=params) PK!coub_api/schemas/__init__.pyPK!{coub_api/schemas/channel.pyfrom typing import List, Optional from datetime import datetime from pydantic import UrlStr, BaseModel __all__ = ("ChannelBig", "ChannelSmall", "ChannelResponse", "AvatarVersions") class AvatarVersions(BaseModel): template: UrlStr versions: Optional[List[str]] class ChannelSmall(BaseModel): id: int permalink: str title: str description: Optional[str] followers_count: int following_count: int avatar_versions: AvatarVersions # only if auth i_follow_him: Optional[bool] # see https://coub.com/dev/docs/data+structures/channel+big+json # response describe non-auth request # (excluded i_follow_him, he_follows_me and some other fields) class ChannelBig(BaseModel): from .coub import BigCoub class Authentication(BaseModel): id: int channel_id: int provider: str username_from_provider: str class Meta(BaseModel): description: Optional[str] homepage: Optional[str] twitter: Optional[str] facebook: Optional[str] tumblr: Optional[str] youtube: Optional[str] vimeo: Optional[str] class Contacts(BaseModel): homepage: Optional[str] tumblr: Optional[str] youtube: Optional[str] vimeo: Optional[str] simple_coubs_count: Optional[int] id: int user_id: int permalink: str title: str description: Optional[str] contacts: Optional[Contacts] created_at: datetime updated_at: datetime avatar_versions: AvatarVersions followers_count: int following_count: int recoubs_count: int likes_count: int stories_count: Optional[int] authentications: Optional[List[Authentication]] background_coub: Optional[BigCoub] background_image: Optional[str] timeline_banner_image: Optional[str] meta: Meta views_count: int hide_owner: bool class ChannelResponse(BaseModel): redirect_url: UrlStr channel: ChannelBig PK!&Xcoub_api/schemas/constants.pyfrom enum import Enum, unique @unique class Section(str, Enum): HOT = "hot" RANDOM = "random" NEWEST = "fresh" RISING = "rising" @unique class Period(str, Enum): DAILY = "" MONTHLY = "monthly" WEEKLY = "weekly" QUARTER = "quarter" HALF_YEAR = "half" @unique class Category(str, Enum): ALL = "" ANIMALS = "animals-pets" MASHUP = "mashup" ANIME = "anime" MOVIES = "movies" GAMING = "gaming" CARTOONS = "cartoons" ART = "art" MUSIC = "music" SPORTS = "sports" SCIENCE = "science-technology" CELEBRITY = "celebrity" NATURE = "nature-travel" FASHION = "fashion" CARS = "cars" NSFW = "nsfw" DANCE = "dance" NEWS = "news" ARCHITECTURE = "architecture" OMG = "omg" TV_SERIES = "tv-series" WTF = "wtf" FUNNY = "funny" LIVE = "live" FOOD = "food" GEEK = "geek" PERFECT_LOOP = "perfect-loop" @unique class VisibilityType(str, Enum): PUBLIC = "public" FRIENDS = "friends" UNLISTED = "unlisted" PRIVATE = "private" @unique class BigCoubType(str, Enum): default = "Coub::Simple" recoub = "Coub::Recoub" @unique class SmallCoubType(str, Enum): default = "Coub::Simple" temp = "Coub::Temp" recoub = "Coub::Recoub" @unique class Provider(str, Enum): FACEBOOK = "facebook" TWITTER = "twitter" VKONTAKTE = "vkontakte" GOOGLE = "google" @unique class ServiceType(str, Enum): YOUTUBE = "Youtube" VIMEO = "Vimeo" VK = "Vk" INSTAGRAM = "Instagram" VINE = "Vine" WIMP = "Wimp" FACEBOOK = "Facebook" ODNOKLASSNIKI = "Odnoklassniki" FUNNYORDIE = "Funnyordie" CARAMBATV = "Carambatv" COLLEGEHUMOR = "CollegeHumor" LIVELEAK = "LiveLeak" DAILYMOTION = "Dailymotion" TETTV = "TetTv" TWITTER = "Twitter" TWITCH = "Twitch" TUMBLR = "Tumblr" GFYCAT = "Gfycat" IMGUR = "Imgur" GIPHY = "Giphy" REDDIT = "Reddit" # UNKNOWN URLDOWNLOAD = "UrlDownload" PK!.E@Ѩcoub_api/schemas/coub.pyfrom typing import Dict, List, Union, Optional from datetime import date, datetime from pydantic import UrlStr, BaseModel from .constants import Category, BigCoubType, ServiceType, SmallCoubType, VisibilityType __all__ = ("BigCoub", "SmallCoub", "CoubTags") class Categories(BaseModel): id: int title: str permalink: Category class CoubTags(BaseModel): id: int title: str value: str class Params(BaseModel): url: UrlStr size: Optional[int] class CoubFileVersion(BaseModel): class Html5(BaseModel): class Video(BaseModel): high: Optional[Params] med: Optional[Params] class Audio(BaseModel): high: Optional[Params] med: Optional[Params] sample_duration: Optional[float] video: Video audio: Optional[Audio] class Mobile(BaseModel): gifv: str audio: Optional[List[UrlStr]] html5: Html5 mobile: Mobile class CoubAudioVersions(BaseModel): class Chunks(BaseModel): template: UrlStr versions: List[str] chunks: List[int] template: UrlStr versions: List[str] chunks: Chunks class CoubImageVersion(BaseModel): template: UrlStr versions: List[str] class CoubFirstFrameVersion(BaseModel): template: UrlStr versions: List[str] class CoubExternalDownload(BaseModel): type: ServiceType service_name: str url: UrlStr has_embed: bool class CoubMediaBlocks(BaseModel): class AudioTrack(BaseModel): class Meta(BaseModel): year: Optional[str] album: Optional[str] title: Optional[str] artist: Optional[str] echonest_id: Optional[str] id: int title: str url: Optional[UrlStr] image: Optional[UrlStr] image_retina: Optional[UrlStr] meta: Meta duration: Optional[float] amazon_url: Optional[UrlStr] google_play_url: Optional[UrlStr] bandcamp_url: Optional[UrlStr] soundcloud_url: Optional[UrlStr] track_name: Optional[str] track_artist: Optional[str] track_album: Optional[str] itunes_url: Optional[UrlStr] class ExternalVideo(BaseModel): class Meta(BaseModel): service: ServiceType duration: float id: int title: Optional[str] url: Optional[UrlStr] image: Optional[UrlStr] image_retina: Optional[UrlStr] meta: Meta duration: float raw_video_id: Optional[int] has_embed: Optional[bool] class RemixedFromCoubs(BaseModel): class Meta(BaseModel): duration: float id: int title: str url: Optional[UrlStr] image: Optional[UrlStr] image_retina: Optional[UrlStr] meta: Meta duration: Optional[float] coub_channel_title: Optional[str] coub_channel_permalink: Optional[str] coub_views_count: Optional[int] coub_permalink: Optional[str] uploaded_raw_videos: List[dict] # TODO external_raw_videos: List[ExternalVideo] remixed_from_coubs: List[RemixedFromCoubs] external_video: Optional[ExternalVideo] audio_track: Optional[AudioTrack] class SubCoub(BaseModel): from .channel import ChannelSmall id: int audio_file_url: Optional[UrlStr] type: BigCoubType permalink: str title: str visibility_type: VisibilityType channel_id: int is_done: bool created_at: datetime updated_at: datetime views_count: int cotd: Optional[bool] cotd_at: Optional[date] recoub: Optional[bool] like: Optional[bool] recoub_to: Optional[dict] original_sound: bool has_sound: bool file_versions: CoubFileVersion audio_versions: Union[CoubAudioVersions, dict] image_versions: Union[CoubImageVersion, dict] first_frame_versions: CoubFirstFrameVersion dimensions: Dict[str, List[int]] age_restricted: bool allow_reuse: bool banned: bool external_download: Union[bool, CoubExternalDownload] channel: ChannelSmall # not documented or available for non-auth favourite: Optional[bool] flag: Optional[bool] published: Optional[bool] age_restricted_by_admin: Optional[bool] published_at: Optional[datetime] is_editable: Optional[bool] page_w_h: Optional[List[int]] global_safe: Optional[bool] site_w_h: Optional[List[int]] site_w_h_small: Optional[List[int]] # see https://coub.com/dev/docs/data+structures/coub+big+json class BigCoub(BaseModel): from .channel import ChannelSmall abuses: Optional[bool] audio_file_url: Optional[UrlStr] id: int type: BigCoubType permalink: str title: str visibility_type: VisibilityType channel_id: int is_done: bool created_at: datetime updated_at: datetime duration: float views_count: int cotd: Optional[bool] cotd_at: Optional[date] recoub: bool like: bool recoubs_count: int likes_count: int recoub_to: Optional[SubCoub] original_sound: bool has_sound: bool file_versions: CoubFileVersion audio_versions: Union[CoubAudioVersions, dict] image_versions: Union[CoubImageVersion, dict] first_frame_versions: Union[CoubFirstFrameVersion, dict] dimensions: Dict[str, Optional[List[int]]] age_restricted: bool allow_reuse: bool banned: bool external_download: Union[bool, CoubExternalDownload] channel: ChannelSmall percent_done: int tags: List[CoubTags] raw_video_id: Union[int, str] media_blocks: CoubMediaBlocks raw_video_thumbnail_url: str raw_video_title: Optional[str] video_block_banned: bool audio_copyright_claim: Optional[str] categories: List[Categories] # not documented or available for non-auth flag: Optional[bool] published: Optional[bool] age_restricted_by_admin: Optional[bool] published_at: Optional[datetime] is_editable: Optional[bool] page_w_h: Optional[List[int]] global_safe: Optional[bool] site_w_h: Optional[List[int]] site_w_h_small: Optional[List[int]] # see https://coub.com/dev/docs/data+structures/channel+small+json class SmallCoub(BaseModel): from .channel import ChannelSmall class ImageVersions(BaseModel): template: UrlStr versions: List[str] id: int type: SmallCoubType permalink: str title: str channel: ChannelSmall image_versions: ImageVersions PK!ofÿcoub_api/schemas/friends.pyfrom typing import List from pydantic import BaseModel from .channel import ChannelSmall __all__ = ("FriendsResponse", "FollowResponse", "RecommendedResponse") class FriendsResponse(BaseModel): page: int total_pages: int total_friends: int per_page: int friends: List[ChannelSmall] class FollowResponse(BaseModel): friends: List[ChannelSmall] class RecommendedResponse(BaseModel): channels: List[ChannelSmall] PK!a coub_api/schemas/metadata.pyfrom typing import List from pydantic import BaseModel from .channel import ChannelSmall __all__ = ("MetaResponse",) class MetaResponse(BaseModel): page: int total_pages: int channels: List[ChannelSmall] PK!{ ٝcoub_api/schemas/search.pyfrom typing import List from pydantic import BaseModel __all__ = ("GeneralSearchResponse", "CoubSearchResponse", "ChannelSearchResponse") class GeneralSearchResponse(BaseModel): from .channel import ChannelBig from .coub import BigCoub page: int per_page: int total_pages: int coubs: List[BigCoub] channels: List[ChannelBig] class ChannelSearchResponse(BaseModel): from .channel import ChannelBig page: int per_page: int total_pages: int channels: List[ChannelBig] class CoubSearchResponse(BaseModel): from .coub import BigCoub page: int per_page: int total_pages: int coubs: List[BigCoub] PK!Lrcoub_api/schemas/timeline.pyfrom typing import List, Optional from pydantic import BaseModel from .coub import BigCoub __all__ = ("SectionTimeLineResponse", "TimeLineResponse") class SectionTimeLineResponse(BaseModel): page: int per_page: int total_pages: int next: Optional[float] coubs: List[BigCoub] class TimeLineResponse(BaseModel): page: int per_page: int total_pages: int coubs: List[BigCoub] PK!crrcoub_api/schemas/users.pyimport enum from typing import List, Optional from datetime import datetime from pydantic import BaseModel from .channel import ChannelBig, ChannelSmall @enum.unique class Sex(str, enum.Enum): MALE = "male" FEMALE = "female" UNSPECIFIED = "unspecified" class UserResponse(BaseModel): id: int permalink: str name: str sex: Sex city: Optional[str] current_channel: ChannelSmall created_at: datetime updated_at: datetime api_token: str # unsubscribed_fields has_linked_vine_accounts: bool likes_count: int favourites_count: int channels: List[ChannelBig] PK!H¨i;G)coub_api-0.1.1.dist-info/entry_points.txtN+I/N.,()J/MO,-0-N-*K-K)ͭCr3PK!11 coub_api-0.1.1.dist-info/LICENSEMIT License Copyright (c) 2018 Andrew Grinevich Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!HnHTUcoub_api-0.1.1.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]\fi4WZ^EgM_-]#0(q7PK!Hτ!coub_api-0.1.1.dist-info/METADATAWko6_`cl'm Q7/#h%]-%%Vd0>= 4 < GHsH$ʅG lFweSYdR0(@VD9xMHu0a:w LC#>7/Y\FX-y3gRTHc N2k͆9eY@vĬ[W$(B ux>GYFbK֐"TS]* G#7IV#5b2)c<#~j JDRu -XnwT>"I3rIyR"F O2Ҏ52yBҐ>T uGFD<7 kCԼ5N:3Q&]lvT,Jv`d!d<^@))h:"y{"v{[(bZjpPi$+Lxg$fU~Ҏ>8]t ',7d-)/b":yQM:@S>JC3TdYޣx"C$>$Ұ2 1tq؞ kT|_ XETVsd]Cm?2V>b=Z*bi-&*R!L3e,(YĞAWۊFŠ`*N R-a 4 р QN0 7GcbɵGY歿]H)dENyI3Cm{BJr.8 ñC2ʘi1 "Zmotڨړ,n@CYۥ^^"Pj 8n(eJkU)F&4 ٲ-B$'l7$X$k6ƒ"Ed^ 77o=wyb=v ߺg{MJ1_ 8gȴa9+ÓE&h{dN3WnPjp.񫣓㣓l5AZq킟ϛPѠxMh8vU#Lg :\\ p\ccg?jHo(Ŷv6Xppf =oG5Ȇ2M(Y]jNf|x^|`>Na*JGDISj7 wIY ^a7Zƿ{* ݌@O0K(F { >Q0 EwJ) NHv1_څva V,2ٶKS] 0o)lgo] `*v%r[_ݝf{Q\.-f[;+2~j{}wsqevQp3Gé^l[?:Iᑦ}2mzHzlGnK͇ t;'hgyɀdg\ɤ6xu{q;RҺ6N}PUgX-;M@E7NzrPK!HZ coub_api-0.1.1.dist-info/RECORDַH|f&o$@8IP'<ӯ&{5%M[I|}?Gt[{uP!H.qV]V|V5"/B\PҔqi8qЛ,eϜˋ $ ]WM>n–9VF_3f$<ڰ 1<*BM}58kr?)m#Fc;t'ĩvtLn\NTc5< X2840NS `v%O,p*Lfb5kz%nO @-m@=tކfC3ZY8V:UK~"h|{4IB*A(PWq9P"z`+\Ex 8g6 C%\D2:-i@( ~Ӷ,c–p qhY+JX:քWR<f(dIF6X{ِ"ncmILQ6sl 8aU^&4\Y!GN75B mSo^d) -s;X'ccbB3({_h;&6}㈌fZNIc_ &m1O(m~ܢl% {}ҏMvw*”Ncpz~MLbL""cH;M ?LuH>};ai㸏na:䧐$/9S2zCKyTy>Ŵrv);r9u}n^cr%wuԎˍӈ3ij(l?~o (t~ݓ:??_{8UC7kp)ʱ 0Mbgư_k 'CLqkL|[Y9SHmƁaYoM )DܵvTr٧F 5= |o=fA'|Iۿf췠k_yZ _CKs]҅`'WsS0Ekl92Aӭ[O.}d/+R´9(Ϩ&8 coub_api/modules/metadata.pyPK!,^RR!Ecoub_api/modules/notifications.pyPK!Gcoub_api/modules/recoub.pyPK!<ccuJcoub_api/modules/search.pyPK!6C8 8 Qcoub_api/modules/timelines.pyPK!9.\coub_api/modules/user.pyPK!^coub_api/schemas/__init__.pyPK!{^coub_api/schemas/channel.pyPK!&Xfcoub_api/schemas/constants.pyPK!.E@Ѩncoub_api/schemas/coub.pyPK!ofÿ͈coub_api/schemas/friends.pyPK!a Ŋcoub_api/schemas/metadata.pyPK!{ ٝ܋coub_api/schemas/search.pyPK!Lrcoub_api/schemas/timeline.pyPK!crrcoub_api/schemas/users.pyPK!H¨i;G)3coub_api-0.1.1.dist-info/entry_points.txtPK!11 coub_api-0.1.1.dist-info/LICENSEPK!HnHTU$coub_api-0.1.1.dist-info/WHEELPK!Hτ!coub_api-0.1.1.dist-info/METADATAPK!HZ coub_api-0.1.1.dist-info/RECORDPK y