if (self.components is None) == (self.swipeableLayoutSlides is None):
307
+
raise ValueError('did not get exactly one of components or swipeableLayoutSlides')
308
+
if self.components and not all(k in self.componentObjects for k in self.components):
309
+
raise ValueError('missing components')
310
+
if self.swipeableLayoutSlides and not all(s.mediumComponentKey in self.componentObjects and s.componentKey in self.componentObjects for s in self.swipeableLayoutSlides):
311
+
raise ValueError('missing components')
312
+
if any(c.destinationKey not in self.destinations for c in self.componentObjects.values() if hasattr(c, 'destinationKey')):
313
+
raise ValueError('missing destinations')
314
+
if any(b.destinationKey not in self.destinations for c in self.componentObjects.values() if isinstance(c, UnifiedCardButtonGroupComponentObject) for b in c.buttons):
315
+
raise ValueError('missing destinations')
316
+
mediaKeys = []
317
+
for c in self.componentObjects.values():
318
+
if isinstance(c, UnifiedCardMediumComponentObject):
if (ext := medium.get('ext')) and (mediaStats := ext['mediaStats']) and isinstance(r := mediaStats['r'], dict) and 'ok' in r and isinstance(r['ok'], dict):
556
-
mKwargs['views'] = int(r['ok']['viewCount'])
557
-
elif (mediaStats := medium.get('mediaStats')):
558
-
mKwargs['views'] = mediaStats['viewCount']
559
-
cls = Video
560
-
elif medium['type'] == 'animated_gif':
561
-
cls = Gif
562
-
media.append(cls(**mKwargs))
835
+
if (mediumO := self._make_medium(medium, tweetId)):
836
+
media.append(mediumO)
563
837
if media:
564
838
kwargs['media'] = media
565
839
if retweetedTweet:
skipped 34 lines
600
874
kwargs['cashtags'] = [o['text'] for o in tweet['entities']['symbols']]
601
875
if card:
602
876
kwargs['card'] = card
603
-
# Try to convert the URL to the non-shortened/t.co one
604
-
try:
605
-
i = kwargs['tcooutlinks'].index(card.url)
606
-
except ValueError:
607
-
_logger.warning('Could not find card URL in tcooutlinks')
608
-
else:
609
-
card.url = kwargs['outlinks'][i]
877
+
if hasattr(card, 'url') and '//t.co/' in card.url:
878
+
# Try to convert the URL to the non-shortened/t.co one
879
+
# Retweets inherit the card but not the outlinks; try to get them from the retweeted tweet instead in that case.
880
+
if 'tcooutlinks' in kwargs and card.url in kwargs['tcooutlinks']:
if (ext := medium.get('ext')) and (mediaStats := ext.get('mediaStats')) and isinstance(r := mediaStats['r'], dict) and 'ok' in r and isinstance(r['ok'], dict):
914
+
mKwargs['views'] = int(r['ok']['viewCount'])
915
+
elif (mediaStats := medium.get('mediaStats')):
916
+
mKwargs['views'] = mediaStats['viewCount']
917
+
cls = Video
918
+
elif medium['type'] == 'animated_gif':
919
+
cls = Gif
920
+
return cls(**mKwargs)
921
+
else:
922
+
_logger.warning(f'Unsupported medium type on tweet {tweetId}: {medium["type"]!r}')
923
+
924
+
def _make_card(self, card, apiType, tweetId):
925
+
bindingValues = {}
926
+
927
+
def _kwargs_from_map(keyKwargMap):
928
+
nonlocal bindingValues
929
+
return {kwarg: bindingValues[key] for key, kwarg in keyKwargMap.items() if key in bindingValues}
930
+
931
+
userRefs = {}
932
+
if apiType is _TwitterAPIType.V2:
933
+
for o in card.get('users', {}).values():
934
+
userId = o['id']
935
+
assert userId not in userRefs
936
+
userRefs[userId] = self._user_to_user(o)
937
+
elif apiType is _TwitterAPIType.GRAPHQL:
938
+
for o in card['legacy'].get('user_refs', {}):
939
+
userId = int(o['rest_id'])
940
+
if userId in userRefs:
941
+
_logger.warning(f'Duplicate user {userId} in card on tweet {tweetId}')
elif any(cardName.startswith(x) for x in ('poll2choice_', 'poll3choice_', 'poll4choice_')) and cardName.split('_', 1)[1] in ('text_only', 'image', 'video'):
_logger.warning(f'Unsupported unified_card type on tweet {tweetId}: {unifiedCardType!r}')
1127
+
return
1128
+
kwargs['type'] = unifiedCardType
1129
+
elif set(c['type'] for c in o['component_objects'].values()) not in ({'media', 'twitter_list_details'}, {'media', 'community_details'}):
1130
+
_logger.warning(f'Unsupported unified_card type on tweet {tweetId}')
1131
+
return
1132
+
1133
+
kwargs['componentObjects'] = {}
1134
+
for k, v in o['component_objects'].items():
1135
+
if v['type'] == 'details':
1136
+
co = UnifiedCardDetailComponentObject(content = v['data']['title']['content'], destinationKey = v['data']['destination'])
1137
+
elif v['type'] == 'media':
1138
+
co = UnifiedCardMediumComponentObject(mediumKey = v['data']['id'], destinationKey = v['data']['destination'])
1139
+
elif v['type'] == 'button_group':
1140
+
if not all(b['type'] == 'cta' for b in v['data']['buttons']):
1141
+
_logger.warning(f'Unsupported unified_card button_group button type on tweet {tweetId}')
1142
+
return
1143
+
buttons = [UnifiedCardButton(text = b['action'][0].upper() + re.sub('[A-Z]', lambda x: f' {x[0]}', b['action'][1:]), destinationKey = b['destination']) for b in v['data']['buttons']]
1144
+
co = UnifiedCardButtonGroupComponentObject(buttons = buttons)
1145
+
elif v['type'] == 'swipeable_media':
1146
+
media = [UnifiedCardSwipeableMediaMedium(mediumKey = m['id'], destinationKey = m['destination']) for m in v['data']['media_list']]
1147
+
co = UnifiedCardSwipeableMediaComponentObject(media = media)
1148
+
elif v['type'] == 'app_store_details':
1149
+
co = UnifiedCardAppStoreComponentObject(appKey = v['data']['app_id'], destinationKey = v['data']['destination'])
1150
+
elif v['type'] == 'twitter_list_details':
1151
+
co = UnifiedCardTwitterListDetailsComponentObject(
1152
+
name = v['data']['name']['content'],
1153
+
memberCount = v['data']['member_count'],
1154
+
subscriberCount = v['data']['subscriber_count'],
1155
+
user = self._user_to_user(o['users'][v['data']['user_id']]),
1156
+
destinationKey = v['data']['destination'],
1157
+
)
1158
+
elif v['type'] == 'community_details':
1159
+
co = UnifiedCardTwitterCommunityDetailsComponentObject(
1160
+
name = v['data']['name']['content'],
1161
+
theme = v['data']['theme'],
1162
+
membersCount = v['data']['member_count'],
1163
+
destinationKey = v['data']['destination'],
1164
+
membersFacepile = [self._user_to_user(u) for u in map(o['users'].get, v['data']['members_facepile']) if u],
1165
+
)
1166
+
else:
1167
+
_logger.warning(f'Unsupported unified_card component type on tweet {tweetId}: {v["type"]!r}')
vKwargs['url'] = f'https://play.google.com/store/apps/details?id={var["id"]}' if var['type'] == 'android_app' else f'https://itunes.apple.com/app/id{var["id"]}'
1213
+
variants.append(UnifiedCardApp(**vKwargs))
1214
+
kwargs['apps'][k] = variants
1215
+
1216
+
if o['components']:
1217
+
kwargs['components'] = o['components']
1218
+
1219
+
if 'layout' in o:
1220
+
if o['layout']['type'] != 'swipeable':
1221
+
_logger.warning(f'Unsupported unified_card layout type on tweet {tweetId}: {o["layout"]["type"]!r}')
1222
+
return
1223
+
kwargs['swipeableLayoutSlides'] = [UnifiedCardSwipeableLayoutSlide(mediumComponentKey = v[0], componentKey = v[1]) for v in o['layout']['data']['slides']]
1224
+
1225
+
return UnifiedCard(**kwargs)
1226
+
1227
+
_logger.warning(f'Unsupported card type on tweet {tweetId}: {cardName!r}')
628
1228
629
1229
def _tweet_to_tweet(self, tweet, obj):
630
1230
user = self._user_to_user(obj['globalObjects']['users'][tweet['user_id_str']])
skipped 3 lines
634
1234
if 'quoted_status_id_str' in tweet and tweet['quoted_status_id_str'] in obj['globalObjects']['tweets']: