| 9 | |
| 10 | |
| 11 | class MediumApi: |
| 12 | __slots__ = ("auth_cookies", "proxy_list", "timeout", "impersonate") |
| 13 | |
| 14 | def __init__( |
| 15 | self, |
| 16 | auth_cookies: Optional[str] = None, |
| 17 | proxy_list: Optional[List[str]] = None, |
| 18 | timeout: int = 3, |
| 19 | impersonate: str = "chrome146", |
| 20 | ): |
| 21 | self.auth_cookies = auth_cookies |
| 22 | self.proxy_list = proxy_list |
| 23 | self.timeout = timeout |
| 24 | self.impersonate = impersonate |
| 25 | |
| 26 | async def query_post_by_id(self, post_id: str): |
| 27 | logger.debug("Using graphql implementation") |
| 28 | return await self.query_post_graphql(post_id) |
| 29 | |
| 30 | async def query_post_graphql(self, post_id: str): |
| 31 | logger.debug(f"Starting request construction for post {post_id}") |
| 32 | |
| 33 | proxy = None |
| 34 | if self.proxy_list: |
| 35 | proxy = random.choice(self.proxy_list) |
| 36 | logger.debug(f"Using proxy: {proxy}") |
| 37 | |
| 38 | headers = { |
| 39 | "X-APOLLO-OPERATION-ID": generate_random_sha256_hash(), |
| 40 | "X-APOLLO-OPERATION-NAME": "FullPostQuery", |
| 41 | "Accept": "multipart/mixed; deferSpec=20220824, application/json, application/json", |
| 42 | "Accept-Language": "en-US", |
| 43 | "X-Obvious-CID": "android", |
| 44 | "X-Xsrf-Token": "1", |
| 45 | "X-Client-Date": str(get_unix_ms()), |
| 46 | "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1 (compatible; YandexMobileBot/3.0;", # "donkey/4.5.1187420", # <---- There is Medium version |
| 47 | "Cache-Control": "public, max-age=-1", |
| 48 | "Content-Type": "application/json", |
| 49 | "Connection": "Keep-Alive", |
| 50 | } |
| 51 | |
| 52 | if self.auth_cookies is not None: |
| 53 | headers["Cookie"] = self.auth_cookies |
| 54 | |
| 55 | graphql_data = { |
| 56 | "operationName": "FullPostQuery", |
| 57 | "variables": { |
| 58 | "postId": post_id, |
| 59 | "postMeteringOptions": {}, |
| 60 | }, |
| 61 | "query": "query FullPostQuery($postId: ID!, $postMeteringOptions: PostMeteringOptions) { post(id: $postId) { __typename id ...FullPostData } meterPost(postId: $postId, postMeteringOptions: $postMeteringOptions) { __typename ...MeteringInfoData } } fragment UserFollowData on User { id socialStats { followingCount followerCount } viewerEdge { isFollowing } } fragment NewsletterData on NewsletterV3 { id viewerEdge { id isSubscribed } } fragment UserNewsletterData on User { id newsletterV3 { __typename ...NewsletterData } } fragment ImageMetadataData on ImageMetadata { id originalWidth originalHeight focusPercentX focusPercentY alt } fragment CollectionFollowData on Collection { id subscriberCount viewerEdge { isFollowing } } fragment CollectionNewsletterData on Collection { id newsletterV3 { __typename ...NewsletterData } } fragment BylineData on Post { id readingTime creator { __typename id imageId username name bio tippingLink viewerEdge { isUser } ...UserFollowData ...UserNewsletterData } collection { __typename id name avatar { __typename id ...ImageMetadataData } ...CollectionFollowData ...CollectionNewsletterData } isLocked firstPublishedAt latestPublishedVersion } fragment ResponseCountData on Post { postResponses { count } } fragment InResponseToPost on Post { id title creator { name } clapCount responsesCount isLocked } fragment PostVisibilityData on Post { id collection { viewerEdge { isEditor canEditPosts canEditOwnPosts } } creator { id } isLocked visibility } fragment PostMenuData on Post { id title creator { __typename ...UserFollowData } collection { __typename ...CollectionFollowData } } fragment PostMetaData on Post { __typename id title visibility ...ResponseCountData clapCount viewerEdge { clapCount } detectedLanguage mediumUrl readingTime updatedAt isLocked allowResponses isProxyPost latestPublishedVersion isSeries firstPublishedAt previewImage { id } inResponseToPostResult { __typename ...InResponseToPost } inResponseToMediaResource { mediumQuote { startOffset endOffset paragraphs { text type markups { type start end anchorType } } } } inResponseToEntityType canonicalUrl collection { id slug name shortDescription avatar { __typename id ...ImageMetadataData } viewerEdge { isFollowing isEditor canEditPosts canEditOwnPosts isMuting } } creator { id isFollowing name bio imageId mediumMemberAt twitterScreenName viewerEdge { isBlocking isMuting isUser } } previewContent { subtitle } pinnedByCreatorAt ...PostVisibilityData ...PostMenuData } fragment LinkMetadataList on Post { linkMetadataList { url alts { type url } } } fragment MediaResourceData on MediaResource { id iframeSrc thumbnailUrl iframeHeight iframeWidth title } fragment IframeData on Iframe { iframeHeight iframeWidth mediaResource { __typename ...MediaResourceData } } fragment MarkupData on Markup { name type start end href title rel type anchorType userId creatorIds } fragment CatalogSummaryData on Catalog { id name description type visibility predefined responsesLocked creator { id name username imageId bio viewerEdge { isUser } } createdAt version itemsLastInsertedAt postItemsCount } fragment CatalogPreviewData on Catalog { __typename ...CatalogSummaryData id itemsConnection(pagingOptions: { limit: 10 } ) { items { entity { __typename ... on Post { id previewImage { id } } } } paging { count } } } fragment MixtapeMetadataData on MixtapeMetadata { mediaResourceId href thumbnailImageId mediaResource { mediumCatalog { __typename ...CatalogPreviewData } } } fragment ParagraphData on Paragraph { id name href text iframe { __typename ...IframeData } layout markups { __typename ...MarkupData } metadata { __typename ...ImageMetadataData } mixtapeMetadata { __typename ...MixtapeMetadataData } type hasDropCap dropCapImage { __typename ...ImageMetadataData } codeBlockMetadata { lang mode } } fragment QuoteData on Quote { id postId userId startOffset endOffset paragraphs { __typename id ...ParagraphData } quoteType } fragment HighlightsData on Post { id highlights { __typename ...QuoteData } } fragment PostFooterCountData on Post { __typename id clapCount viewerEdge { clapCount } ...ResponseCountData responsesLocked mediumUrl title collection { id viewerEdge { isMuting isFollowing } } creator { id viewerEdge { isMuting isFollowing } } } fragment TagNoViewerEdgeData on Tag { id normalizedTagSlug displayTitle followerCount postCount } fragment VideoMetadataData on VideoMetadata { videoId previewImageId originalWidth originalHeight } fragment SectionData on Section { name startIndex textLayout imageLayout videoLayout backgroundImage { __typename ...ImageMetadataData } backgroundVideo { __typename ...VideoMetadataData } } fragment PostBodyData on RichText { sections { __typename ...SectionData } paragraphs { __typename id ...ParagraphData } } fragment FullPostData on Post { __typename ...BylineData ...PostMetaData ...LinkMetadataList ...HighlightsData ...PostFooterCountData tags { __typename id ...TagNoViewerEdgeData } content(postMeteringOptions: $postMeteringOptions) { bodyModel { __typename ...PostBodyData } validatedShareKey } } fragment MeteringInfoData on MeteringInfo { maxUnlockCount unlocksRemaining postIds }", |
| 62 | } |
| 63 | |
| 64 | response_data = None |
| 65 | exception = None |
| 66 | |
| 67 | logger.debug("Request started...") |
| 68 |
no outgoing calls