import os import twitfix,twExtract,vxApi import cache from flask.testing import FlaskClient client = FlaskClient(twitfix.app) # autogenerated from testgen.py testTextTweet="https://twitter.com/jack/status/20" testVideoTweet="https://twitter.com/pdxdylan/status/1540398733669666818" testMediaTweet="https://twitter.com/pdxdylan/status/1534672932106035200" testMultiMediaTweet="https://twitter.com/pdxdylan/status/1532006436703715331" testQRTTweet="https://twitter.com/pdxdylan/status/1611477137319514129" testQrtCeptionTweet="https://twitter.com/CatherineShu/status/585253766271672320" testQrtVideoTweet="https://twitter.com/pdxdylan/status/1674561759422578690" testNSFWTweet="https://twitter.com/kuyacoy/status/1581185279376838657" testPollTweet="https://twitter.com/norm/status/651169346518056960" testTextTweet_compare={'text': 'just setting up my twttr', 'date': 'Tue Mar 21 20:50:14 +0000 2006', 'tweetURL': 'https://twitter.com/jack/status/20', 'tweetID': '20', 'conversationID': '20', 'mediaURLs': [], 'media_extended': [], 'possibly_sensitive': False, 'hashtags': [], 'qrtURL': None, 'allSameType': False, 'hasMedia': False, 'combinedMediaUrl': None, 'pollData': None, 'date_epoch': 1142974214} testVideoTweet_compare={'text': 'TikTok embeds on Discord/Telegram bait you with a fake play button, but to see the actual video you have to go to their website.\nAs a request from a friend, I made it so that if you add "vx" before "tiktok" on any link, it fixes that. https://t.co/QYpiVXUIrW', 'date': 'Fri Jun 24 18:17:31 +0000 2022', 'tweetURL': 'https://twitter.com/pdxdylan/status/1540398733669666818', 'tweetID': '1540398733669666818', 'conversationID': '1540398733669666818', 'mediaURLs': ['https://video.twimg.com/ext_tw_video/1540396699037929472/pu/vid/762x528/YxbXbT3X7vq4LWfC.mp4?tag=12'], 'media_extended': [{'url': 'https://video.twimg.com/ext_tw_video/1540396699037929472/pu/vid/762x528/YxbXbT3X7vq4LWfC.mp4?tag=12', 'type': 'video', 'size': {'width': 762, 'height': 528}, 'duration_millis': 13650, 'thumbnail_url': 'https://pbs.twimg.com/ext_tw_video_thumb/1540396699037929472/pu/img/l187Z6B9AHHxUKPV.jpg', 'altText': None}], 'possibly_sensitive': False, 'hashtags': [], 'qrtURL': None, 'allSameType': False, 'hasMedia': True, 'combinedMediaUrl': None, 'pollData': None, 'date_epoch': 1656094651} testMediaTweet_compare={'text': 'oh. https://t.co/HgLAbiXw2E', 'date': 'Wed Jun 08 23:05:14 +0000 2022', 'tweetURL': 'https://twitter.com/pdxdylan/status/1534672932106035200', 'tweetID': '1534672932106035200', 'conversationID': '1534672673422381057', 'mediaURLs': ['https://pbs.twimg.com/media/FUxAt5LWUAMol0N.png'], 'media_extended': [{'url': 'https://pbs.twimg.com/media/FUxAt5LWUAMol0N.png', 'altText': None, 'type': 'image', 'size': {'width': 927, 'height': 534}, 'thumbnail_url': 'https://pbs.twimg.com/media/FUxAt5LWUAMol0N.png'}], 'possibly_sensitive': False, 'hashtags': [], 'qrtURL': None, 'allSameType': False, 'hasMedia': True, 'combinedMediaUrl': None, 'pollData': None, 'date_epoch': 1654729514} testMultiMediaTweet_compare={'text': 'Released #Retro64 1.0.9. Besides a lot of internal bug-fixes, this adds quicksand blocks, fixes the rendering for the castle stairs block, and adds a new model, Sonic! \nhttps://github.com/Retro64Mod/Retro64Mod/releases/tag/1.18.2-1.0.9 https://t.co/CWZaw4hzyg', 'date': 'Wed Jun 01 14:29:32 +0000 2022', 'tweetURL': 'https://twitter.com/pdxdylan/status/1532006436703715331', 'tweetID': '1532006436703715331', 'conversationID': '1532006436703715331', 'mediaURLs': ['https://pbs.twimg.com/media/FULF9oxXwAMDI-C.png', 'https://pbs.twimg.com/media/FULGaHkWYAIBV5U.png', 'https://pbs.twimg.com/media/FULGiZnWQAMBRWl.png'], 'media_extended': [{'url': 'https://pbs.twimg.com/media/FULF9oxXwAMDI-C.png', 'altText': None, 'type': 'image', 'size': {'width': 507, 'height': 507}, 'thumbnail_url': 'https://pbs.twimg.com/media/FULF9oxXwAMDI-C.png'}, {'url': 'https://pbs.twimg.com/media/FULGaHkWYAIBV5U.png', 'altText': None, 'type': 'image', 'size': {'width': 396, 'height': 431}, 'thumbnail_url': 'https://pbs.twimg.com/media/FULGaHkWYAIBV5U.png'}, {'url': 'https://pbs.twimg.com/media/FULGiZnWQAMBRWl.png', 'altText': None, 'type': 'image', 'size': {'width': 399, 'height': 341}, 'thumbnail_url': 'https://pbs.twimg.com/media/FULGiZnWQAMBRWl.png'}], 'possibly_sensitive': False, 'hashtags': ['Retro64'], 'qrtURL': None, 'allSameType': True, 'hasMedia': True, 'combinedMediaUrl': 'https://vxtwitter.com/rendercombined.jpg?imgs=https://pbs.twimg.com/media/FULF9oxXwAMDI-C.png,https://pbs.twimg.com/media/FULGaHkWYAIBV5U.png,https://pbs.twimg.com/media/FULGiZnWQAMBRWl.png', 'pollData': None, 'date_epoch': 1654093772} testQRTTweet_compare={'text': "vxTwitter has gotten a *ton* of usage recently, so I'd appreciate a donation to keep things running!\n", 'date': 'Fri Jan 06 21:37:43 +0000 2023', 'tweetURL': 'https://twitter.com/pdxdylan/status/1611477137319514129', 'tweetID': '1611477137319514129', 'conversationID': '1611476665821003776', 'mediaURLs': [], 'media_extended': [], 'possibly_sensitive': False, 'hashtags': [], 'qrtURL': 'https://twitter.com/i/status/1518309187515781125', 'allSameType': False, 'hasMedia': False, 'combinedMediaUrl': None, 'pollData': None, 'date_epoch': 1673041063} testQrtCeptionTweet_compare={'text': 'Testing retweetception ', 'date': 'Tue Apr 07 01:32:26 +0000 2015', 'tweetURL': 'https://twitter.com/CatherineShu/status/585253766271672320', 'tweetID': '585253766271672320', 'conversationID': '585253766271672320', 'mediaURLs': [], 'media_extended': [], 'possibly_sensitive': False, 'hashtags': [], 'qrtURL': 'https://twitter.com/i/status/585253161260216320', 'allSameType': False, 'hasMedia': False, 'combinedMediaUrl': None, 'pollData': None, 'date_epoch': 1428370346} testQrtVideoTweet_compare={'text': 'good', 'date': 'Thu Jun 29 23:33:29 +0000 2023', 'tweetURL': 'https://twitter.com/pdxdylan/status/1674561759422578690', 'tweetID': '1674561759422578690', 'conversationID': '1674561759422578690', 'mediaURLs': [], 'media_extended': [], 'possibly_sensitive': False, 'hashtags': [], 'qrtURL': 'https://twitter.com/i/status/1674197531301904388', 'allSameType': False, 'hasMedia': False, 'combinedMediaUrl': None, 'pollData': None, 'date_epoch': 1688081609} testNSFWTweet_compare={'text': "ngl, I'm scared on finding out the cute Sprigatito's final evolution..\n\nso i had a bot generate it for me.... and I'm forever scarred https://t.co/itMay87vcS", 'date': 'Sat Oct 15 07:28:42 +0000 2022', 'tweetURL': 'https://twitter.com/kuyacoy/status/1581185279376838657', 'tweetID': '1581185279376838657', 'conversationID': '1581185279376838657', 'mediaURLs': ['https://pbs.twimg.com/media/FfF_gKwXgAIpnpD.jpg'], 'media_extended': [{'url': 'https://pbs.twimg.com/media/FfF_gKwXgAIpnpD.jpg', 'altText': None, 'type': 'image', 'size': {'width': 760, 'height': 926}, 'thumbnail_url': 'https://pbs.twimg.com/media/FfF_gKwXgAIpnpD.jpg'}], 'possibly_sensitive': False, 'hashtags': [], 'qrtURL': None, 'allSameType': False, 'hasMedia': True, 'combinedMediaUrl': None, 'pollData': None, 'date_epoch': 1665818922} testPollTweet_compare={'text': 'I know when that hotline bling, that can only:', 'date': 'Mon Oct 05 22:57:25 +0000 2015', 'tweetURL': 'https://twitter.com/norm/status/651169346518056960', 'tweetID': '651169346518056960', 'conversationID': '651169346518056960', 'mediaURLs': [], 'media_extended': [], 'possibly_sensitive': False, 'hashtags': [], 'qrtURL': None, 'allSameType': False, 'hasMedia': False, 'combinedMediaUrl': None, 'pollData': {'options': [{'name': 'Mean one thing', 'votes': 124875, 'percent': 78.82}, {'name': 'Mean multiple things', 'votes': 33554, 'percent': 21.18}]}, 'date_epoch': 1444085845} testUser="https://twitter.com/jack" testUserID = "https://twitter.com/i/user/12" testUserWeirdURLs=["https://twitter.com/jack?lang=en","https://twitter.com/jack/with_replies","https://twitter.com/jack/media","https://twitter.com/jack/likes","https://twitter.com/jack/with_replies?lang=en","https://twitter.com/jack/media?lang=en","https://twitter.com/jack/likes?lang=en","https://twitter.com/jack/"] testTextTweet="https://twitter.com/jack/status/20" tokens=os.getenv("VXTWITTER_WORKAROUND_TOKENS",None).split(',') def compareDict(original,compare): for key in original: assert key in compare if type(compare[key]) is not dict: if (key == 'verified' or key== 'time') and compare[key]!=original[key]: continue # does not match as test data was from before verification changes assert compare[key]==original[key] else: compareDict(original[key],compare[key]) ## Specific API tests ## def test_twextract_syndicationAPI(): tweet = twExtract.extractStatus_syndication(testMediaTweet,workaroundTokens=tokens) assert tweet["full_text"]==testMediaTweet_compare['text'] def test_twextract_extractStatusV2Anon(): tweet = twExtract.extractStatusV2AnonLegacy(testTextTweet,None) assert tweet["full_text"]==testTextTweet_compare['text'] tweet = twExtract.extractStatusV2AnonLegacy(testVideoTweet,None) assert tweet["full_text"]==testVideoTweet_compare['text'] tweet = twExtract.extractStatusV2AnonLegacy(testMediaTweet,None) assert tweet["full_text"]==testMediaTweet_compare['text'] tweet = twExtract.extractStatusV2AnonLegacy(testMultiMediaTweet,None) assert tweet["full_text"][:94]==testMultiMediaTweet_compare['text'][:94] def test_twextract_v2API(): tweet = twExtract.extractStatusV2Legacy(testMediaTweet,workaroundTokens=tokens) assert tweet["full_text"]==testMediaTweet_compare['text'] ## Tweet retrieve tests ## def test_twextract_textTweetExtract(): tweet = twExtract.extractStatus(testTextTweet,workaroundTokens=tokens) assert tweet["full_text"]==testTextTweet_compare['text'] assert tweet["user"]["screen_name"]=="jack" assert 'extended_entities' not in tweet def test_twextract_extractV2(): # remove this when v2 is default tweet = twExtract.extractStatusV2(testTextTweet,workaroundTokens=tokens) def test_twextract_UserExtract(): user = twExtract.extractUser(testUser,workaroundTokens=tokens) assert user["screen_name"]=="jack" assert user["id"]==12 assert user["created_at"] == "Tue Mar 21 20:50:14 +0000 2006" def test_twextract_UserExtractID(): user = twExtract.extractUser(testUserID,workaroundTokens=tokens) assert user["screen_name"]=="jack" assert user["id"]==12 assert user["created_at"] == "Tue Mar 21 20:50:14 +0000 2006" def test_twextract_UserExtractWeirdURLs(): for url in testUserWeirdURLs: user = twExtract.extractUser(url,workaroundTokens=tokens) assert user["screen_name"]=="jack" assert user["id"]==12 assert user["created_at"] == "Tue Mar 21 20:50:14 +0000 2006" def test_twextract_videoTweetExtract(): tweet = twExtract.extractStatus(testVideoTweet,workaroundTokens=tokens) assert tweet["full_text"]==testVideoTweet_compare['text'] assert 'extended_entities' in tweet assert len(tweet['extended_entities']["media"])==1 video = tweet['extended_entities']["media"][0] assert video["media_url_https"]==testVideoTweet_compare['media_extended'][0]['thumbnail_url'] assert video["type"]=="video" def test_twextract_mediaTweetExtract(): tweet = twExtract.extractStatus(testMediaTweet,workaroundTokens=tokens) assert tweet["full_text"]==testMediaTweet_compare['text'] assert 'extended_entities' in tweet assert len(tweet['extended_entities']["media"])==1 video = tweet['extended_entities']["media"][0] assert video["media_url_https"]==testMediaTweet_compare['media_extended'][0]['thumbnail_url'] assert video["type"]=="photo" def test_twextract_multimediaTweetExtract(): tweet = twExtract.extractStatus(testMultiMediaTweet,workaroundTokens=tokens) assert tweet["full_text"][:94]==testMultiMediaTweet_compare['text'][:94] assert 'extended_entities' in tweet assert len(tweet['extended_entities']["media"])==3 video = tweet['extended_entities']["media"][0] assert video["media_url_https"]==testMultiMediaTweet_compare["mediaURLs"][0] assert video["type"]=="photo" video = tweet['extended_entities']["media"][1] assert video["media_url_https"]==testMultiMediaTweet_compare["mediaURLs"][1] assert video["type"]=="photo" def test_twextract_pollTweetExtract(): # basic check if poll data exists tweet = twExtract.extractStatus("https://twitter.com/norm/status/651169346518056960",workaroundTokens=tokens) assert 'card' in tweet assert tweet['card']['name']=="poll2choice_text_only" def test_twextract_NSFW_TweetExtract(): tweet = twExtract.extractStatus(testNSFWTweet,workaroundTokens=tokens) # For now just test that there's no error def getVNFFromLink(link): return twitfix.getTweetData(link) ## VNF conversion test ## def test_textTweetVNF(): vnf = getVNFFromLink(testTextTweet) compareDict(testTextTweet_compare,vnf) def test_videoTweetVNF(): vnf = getVNFFromLink(testVideoTweet) compareDict(testVideoTweet_compare,vnf) def test_mediaTweetVNF(): vnf = getVNFFromLink(testMediaTweet) compareDict(testMediaTweet_compare,vnf) def test_multimediaTweetVNF(): vnf = getVNFFromLink(testMultiMediaTweet) compareDict(testMultiMediaTweet_compare,vnf) def test_pollTweetVNF(): vnf = getVNFFromLink(testPollTweet) compareDict(testPollTweet_compare,vnf) def test_qrtTweet(): cache.clearCache() # this is an incredibly lazy test, todo: improve it in the future resp = client.get(testQRTTweet.replace("https://twitter.com",""),headers={"User-Agent":"test"}) assert resp.status_code==200 assert testQRTTweet_compare['text'][:10] in str(resp.data) # test qrt-ception resp = client.get(testQrtCeptionTweet.replace("https://twitter.com",""),headers={"User-Agent":"test"}) # get top level tweet assert resp.status_code==200 assert "Please retweet this to spread awareness for retweets" in str(resp.data) qtd_tweet=cache.getVnfFromLinkCache("https://twitter.com/EliLanger/status/585253161260216320") # check that the quoted tweet for the top level tweet is cached assert qtd_tweet is not None assert qtd_tweet["qrtURL"] is not None # check that the quoted tweet for the top level tweet has a qrtURL assert cache.getVnfFromLinkCache("https://twitter.com/EliLanger/status/313143446842007553") is None # check that the bottom level tweet has NOT been cached resp = client.get("/EliLanger/status/585253161260216320",headers={"User-Agent":"test"}) # get mid level tweet assert resp.status_code==200 assert cache.getVnfFromLinkCache("https://twitter.com/EliLanger/status/313143446842007553") is not None # check that the bottom level tweet has been cached now def test_qrtVideoTweet(): cache.clearCache() # this is an incredibly lazy test, todo: improve it in the future resp = client.get(testQrtVideoTweet.replace("https://twitter.com",""),headers={"User-Agent":"test"}) assert resp.status_code==200 qtd_tweet=cache.getVnfFromLinkCache("https://twitter.com/i/status/1674197531301904388") vurl = qtd_tweet["mediaURLs"][0] assert f"twitter:player:stream\" content=\"{vurl}" in str(resp.data) ## Test adding to cache ; cache should be empty ## def test_addToCache(): cache.clearCache() twitfix.getTweetData(testTextTweet) twitfix.getTweetData(testVideoTweet) twitfix.getTweetData(testMediaTweet) twitfix.getTweetData(testMultiMediaTweet) #retrieve compareDict(testTextTweet_compare,cache.getVnfFromLinkCache(testTextTweet)) compareDict(testVideoTweet_compare,cache.getVnfFromLinkCache(testVideoTweet)) compareDict(testMediaTweet_compare,cache.getVnfFromLinkCache(testMediaTweet)) compareDict(testMultiMediaTweet_compare,cache.getVnfFromLinkCache(testMultiMediaTweet)) cache.clearCache() def test_embedFromScratch(): cache.clearCache() client.get(testTextTweet.replace("https://twitter.com",""),headers={"User-Agent":"test"}) client.get(testVideoTweet.replace("https://twitter.com",""),headers={"User-Agent":"test"}) client.get(testMediaTweet.replace("https://twitter.com",""),headers={"User-Agent":"test"}) client.get(testMultiMediaTweet.replace("https://twitter.com",""),headers={"User-Agent":"test"}) def test_embedFromCache(): cache.clearCache() twitfix.getTweetData(testTextTweet) twitfix.getTweetData(testVideoTweet) twitfix.getTweetData(testMediaTweet) twitfix.getTweetData(testMultiMediaTweet) #embed time resp = client.get(testTextTweet.replace("https://twitter.com",""),headers={"User-Agent":"test"}) assert resp.status_code==200 resp = client.get(testVideoTweet.replace("https://twitter.com",""),headers={"User-Agent":"test"}) assert resp.status_code==200 resp = client.get(testMediaTweet.replace("https://twitter.com",""),headers={"User-Agent":"test"}) assert resp.status_code==200 resp = client.get(testMultiMediaTweet.replace("https://twitter.com",""),headers={"User-Agent":"test"}) assert resp.status_code==200 def test_embedSuggestive(): resp = client.get(testNSFWTweet.replace("https://twitter.com",""),headers={"User-Agent":"test"}) assert resp.status_code==200 assert "so i had a bot generate it for me" in str(resp.data) assert "FfF_gKwXgAIpnpD" in str(resp.data) def test_veryLongEmbed(): cache.clearCache() cache.setCache({'https://twitter.com/TEST/status/1234': {"description":"A"*1024,"hits":0,"images":["","","","",""],"likes":1234,"nsfw":False,"pfp":"","qrt":{},"rts":1234,"screen_name":"TEST","thumbnail":"","time":"","tweet":"https://twitter.com/TEST/status/1234","type":"Text","uploader":"Test","url":""} }) resp = client.get('https://twitter.com/TEST/status/1234'.replace("https://twitter.com",""),headers={"User-Agent":"test"}) assert resp.status_code==200 def test_directEmbed(): resp = client.get(testVideoTweet.replace("https://twitter.com","")+".mp4",headers={"User-Agent":"test"}) assert resp.status_code==200 assert testVideoTweet_compare["mediaURLs"][0] in str(resp.data) def test_message404(): resp = client.get("https://twitter.com/jack/status/12345",headers={"User-Agent":"test"}) assert resp.status_code==200 assert "Failed to scan your link!" in str(resp.data) def test_combine(): twt = twitfix.getTweetData(testMultiMediaTweet) img1 = twt["mediaURLs"][0] img2 = twt["mediaURLs"][1] resp = client.get(f"/rendercombined.jpg?imgs={img1},{img2}",headers={"User-Agent":"test"}) assert resp.status_code==200 assert resp.headers["Content-Type"]=="image/jpeg" assert len(resp.data)>1000 def test_calcSyndicationToken(): assert twExtract.calcSyndicationToken("1691389765483200513") == "43lnobuxzql"