From ef42c26a81775f5e04332e3c6573df6db32b3304 Mon Sep 17 00:00:00 2001 From: Chloe Carver-Brown Date: Wed, 18 May 2022 15:24:27 +0100 Subject: [PATCH] Updates and changes Signed-off-by: Chloe Carver-Brown --- .github/ISSUE_TEMPLATE/bug_report.md | 38 + .github/ISSUE_TEMPLATE/feature_request.md | 20 + .vscode/settings.json | 7 + readme.md | 67 +- static/coconut.jpg | Bin 0 -> 13214 bytes static/favicon.ico | Bin 15406 -> 4286 bytes twitfix.ini | 2 +- twitfix.py | 805 ++++++++++++++-------- wsgi.py | 2 +- 9 files changed, 605 insertions(+), 336 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .vscode/settings.json create mode 100644 static/coconut.jpg diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..5b0a804 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG]" +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..8137475 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[FEATURE REQUEST]" +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6604e6f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "python.linting.flake8Enabled": true, + "python.linting.enabled": true, + "python.linting.flake8Args": [ + "--ignore=E501, E261, E302, E231, E226, F401, E221, W503, E722, E711, E712, F841", + ], +} \ No newline at end of file diff --git a/readme.md b/readme.md index 8bcf375..3b40bdf 100644 --- a/readme.md +++ b/readme.md @@ -1,54 +1,18 @@ -# TwitFix - +# Twxtter/sixFix +(A fork of TwitFix) Basic flask server that serves fixed twitter video embeds to desktop discord by using either the Twitter API or Youtube-DL to grab tweet video information. This also automatically embeds the first link in the text of non video tweets (API Only) +Regarding what happened to TwitFix: http://blog.hirob.in/2022-05-16-Goodbye-TwitFix/ + ## How to use (discord side) -just put the url to the server, and directly after, the full URL to the tweet you want to embed - -**I now have a copy of this running on a Linode server, you can use it via the following url** - ``` -https://fxtwitter.com/[twitter video url] or [last half of twitter url] (everything past twitter.com/) +https://twxtter.com/[twitter video url] or [last half of twitter url]) ``` -You can also simply type out 'fx' directly before 'twitter.com' in any valid twitter video url, and that will convert it into a working TwitFix url, for example: +You can also simply type out `s/i/x` on PC and iOS after sending a twitter link to discord, and it will edit the last message (The link) to replace the first i with x. -![example](example.gif) - -**Note**: If you enjoy this service, please considering donating via [Ko-Fi](https://ko-fi.com/robin_universe) to help cover server costs - -## Child Projects: - -[TwitFix-Bot](https://github.com/robinuniverse/TwitFix-Bot) - A discord bot for automatically converting normal twitter links posted by users into twitfix links - -[TwitFix-Extension](https://github.com/robinuniverse/TwitFix-Extension) - A browser extention that lets you right click twitter videos to copy a twitfix link to your clipboard - -# Monthly Contributors - -TwitFix is run for free, period, I have no plans to monetize it directly in any way ( no ads, no premium accounts with more features ) so I rely on donations to keep TwitFix running, and I have created the option to [donate on a monthly basis using my KoFi](https://ko-fi.com/robin_universe#tier16328580186740) - - - -Here's a a list of the people who help to keep this project alive! ( current total monthly - $49!!! ) - -1. [$3] First Contributor and Twitter Funnyman **Chris Burwell** ( [@countchrisdo](https://twitter.com/countchrisdo) on Twitter ) - -2. [$9] Previously highest Contributor, Suspciously wealthy furry, and a very loving friend **Vectrobe** ( [@Vectrobe](https://twitter.com/Vectrobe) on Twitter ) - -3. [$10] New highest monthly contributor, **helloitscrash**! - -4. [$6] A Mysterious and **Anonymous** contributor... - -5. [$10] One of the highest contributors, **Ryan Vilbrandt**! - -6. [$3] **Starcat13**, the one with the coolest sounding name - -7. [$5] THE LIGHT THROUGH WHICH GOD SPEAKS TO THIS EARTH: **Statek** - -8. [$3] **Impulse**, probably the source cheat - -9. [$3] a STRONG contendor for coolest name, "**Lost in Art & Magic**" +**Note**: If you enjoy this service, please considering donating via [Ko-Fi](https://ko-fi.com/twxtter) to help cover server costs ## How to run (server side) @@ -58,7 +22,7 @@ I have included some files to give you a head start on setting this server up wi ### Config -TwitFix generates a config.json in its root directory the first time you run it, the options are: +Twxtter generates a config.json in its root directory the first time you run it, the options are: **API** - This will be where you put the credentials for your twitter API if you use this method @@ -90,13 +54,19 @@ This project is licensed under the **Do What The Fuck You Want Public License** ## Other stuff -Going to `https://fxtwitter.com/latest/` will present a page that shows the all the latest tweets that were added to the database, use with caution as results may be nsfw! Current page created by @DorukSaga +We check for t.co links in non video tweets, and if one is found, we direct the discord useragent to embed that link directly, this means that twitter links containing youtube / vimeo links will automatically embed those as if you had just directly linked to that content + + + +## Other stuff + +Going to `https://twxtter.com/latest/` will present a page that shows the all the latest tweets that were added to the database, use with caution as results may be nsfw! Current page created by @DorukSaga Using the `/dir/` endpoint will return a redirect to the direct MP4 link, this can be useful for downloading a video Using the `/dl/` or appending a `.mp4` will make the server download the video and return a static, locally hosted copy -Using the subdomain `d.fxtwitter.com/` will redirect to a direct MP4 url hosted on Twitter +Using the subdomain `d.twxtter.com/` will redirect to a direct MP4 url hosted on Twitter Using the `/info/` endpoint will return a json that contains all video info that youtube-dl can grab about any given video @@ -108,6 +78,7 @@ Using `/api/top/` will return a json with the most hit tweet in the database. Ta Using `/api/stats/` will return a json with some stats about TwitFix's activity (embeds, new cached links, API hits, downloads). Takes param `?=date"YYYY-MM-DD"` to return a specific day, otherwise will return today's stats to far -Advanced embeds are provided via a `/oembed.json?` endpoint - This is manually pointing at my server in `/templates/index.html` and should be changed from `https://fxtwitter.com/` to whatever your domain is +Advanced embeds are provided via a `/oembed.json?` endpoint - This is manually pointing at my server in `/templates/index.html` and should be changed from `https://twxtter.com/` to whatever your domain is -We check for t.co links in non video tweets, and if one is found, we direct the discord useragent to embed that link directly, this means that twitter links containing youtube / vimeo links will automatically embed those as if you had just directly linked to that content +# NOTICE +## This is _**NOT**_ actively monitored by anyone working on Twxtter. All tweets are public ally accessible. \ No newline at end of file diff --git a/static/coconut.jpg b/static/coconut.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7a2b105e656975fd8cf51e4e8f3f55ff8f2f3a74 GIT binary patch literal 13214 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 literal 0 HcmV?d00001 diff --git a/static/favicon.ico b/static/favicon.ico index 6928b70c2f2a4e3a72038b0631bc3bcfa98d4fd4..0e1b19f7a8ba9f9ece38e769c87a71a198940a3a 100644 GIT binary patch literal 4286 zcmeHLTTc@~6kh*0~>N{g2OauXWF zmqKsb*4D(NfffpsmVysx2+;&F5hB}uo|#%CzCdUfedsi2W_NaWzjOJ{nM?)Y2>miL z1l9|KM+M=SAP6T&Xi7Lm!sdCSXU9**B|aet(-S3-S4!#o2~pCu5;JBMiJC+sGq&8e zrN1vPchdgd?O*>m`NPa|s1*M3;zP|HhBs$b!EY+UtmPJhq6Sl@!_mP{HEPzX#Cu}_ zTI!0?Qd0nX-$g8Y%6L7I+ZR5vbJ3~B^CkrXb75?J|AE%VN*ErUL13Z?A%|90U-&5Q zAx9}*v|h!^r!}w^ivbSjJJeSe;%!$JmfTwSO!DLl<6Y2p8~UaqtZ!^_d`Xh{xVaUD z{;3Yn8qQ+bt$|+_ZW8BBH(=1+g+z9f^*cI5qj5cqMkCrDoJLqAp4!ypi_~#5U{S$4 zmXDX6RU9`Ki}I0mF$#nvZS#B4;X=**Y`BN7P&~>~ro*gN4bN~M`n%gWPQqq>^SevL zV|8sEjkV<%YCn&VNcqZYMiP5Gdw<@pfu%PGqa!^WErGQgpIyiGis9#W41KS<(cf_i z3rXt0@Iwv_#taJBZRS1jqdT#Pd|X9Kbq>OmH!Ob+>H{Cuy|7b*!H#T9PEK+BX!P&+ zOFOo+1rDbNwH24~;Y}gcN@Y?pV7iB$D)i`b5L}3Ge8xGZLxKnFp0&u*3e}|w=pUXV z+?(-wo#Z~jYmZohuI6j_y!w^n?2iBXw@rwm2>s(av{q%o{W_O(KUH;r;V%%rq4_%2 zzHD%O*3AbaFg$61t~MWix=eUR^2krhl;?gt{%Z8VM}zz;+J-YdK~tcSyASTs%$ZV|Gsdb8eO|j*0&P{?+&k0q_b$ literal 15406 zcmeHO&ui0A9Dg!}=|Lwr)Qj|@>^M+&QdF3D5ky(nBn788{{=niQPHEGwt>?@w;e@n zHSI)jt2PO9Y?n##;tumr)*o}p%a*tAx7}k>7_5ogOV|s1cum@GKA(K@lK0K`rGy+H zZKR`vKpP_u4ieHs2#LiS>s`kQ8HR1$-J9Q^BV?+RkS_QQgg``NJ2atO=!|;FF}075 zrA@U2h@nQ}GBvf|)Jifx0KUZqeZ>2zk1)UZkgBbx$5xnMe12D512DfRl}bz`{Oaa$ zetqL{3;Ju{=MQ58^&dl9qz3k1H3NP4Yb&4-gMB}g7i9~g@IDg*Tm|6$NiNcX?Hpjg z2HN3Txexct1th!*k`R|GHT1b)gti)i!NvYXFUm?((M(ImIn z92x!3XQA&2gotZSa&A2r>Jv*a_J{+F!5$wA_quIxtAg_n?af{vH|Mhp>S$G1{;ADj z{)ss-o4nHQSn6HZQlC0z^7(F}&QVYX*kmLBMt-?S2llH2SSNuJEx8AfbrLW^F46(% zKxiFc#`#0ch@YW_c+aZU-|Je+^>9J!t!tZ3#8B(MMzU}5Q`Ax)QA7Iyxu-2M_vJZY zbKB6Q*;tcw3d)mO;r3}f24>tm0@{bDnRJ?C!nzK};)2)1&|i+ZM*j_1kBoNiDb$Lo zV 15: tweets = 1 - vnf = db.linkCache.find(sort = [('_id', pymongo.DESCENDING)]).skip(tweets * page).limit(tweets) + vnf = ( + db.linkCache.find(sort=[("_id", pymongo.DESCENDING)]) + .skip(tweets * page) + .limit(tweets) + ) for r in vnf: bigvnf.append(r) print(" ➤ [ ✔ ] Latest video API called") - addToStat('api') - return Response(response=json.dumps(bigvnf, default=str), status=200, mimetype="application/json") + addToStat("api") + return Response( + response=json.dumps(bigvnf, default=str), + status=200, + mimetype="application/json", + ) -@app.route('/api/top/') # Return some raw VNF data sorted by top tweets + +@app.route("/api/top/") # Return some raw VNF data sorted by top tweets def apiTop(): - bigvnf = [] + bigvnf = [] - tweets = request.args.get("tweets", default=10, type=int) - page = request.args.get("page", default=0, type=int) + tweets = request.args.get("tweets", default=10, type=int) + page = request.args.get("page", default=0, type=int) if tweets > 15: tweets = 1 - vnf = db.linkCache.find(sort = [('hits', pymongo.DESCENDING )]).skip(tweets * page).limit(tweets) + vnf = ( + db.linkCache.find(sort=[("hits", pymongo.DESCENDING)]) + .skip(tweets * page) + .limit(tweets) + ) for r in vnf: bigvnf.append(r) print(" ➤ [ ✔ ] Top video API called") - addToStat('api') - return Response(response=json.dumps(bigvnf, default=str), status=200, mimetype="application/json") + addToStat("api") + return Response( + response=json.dumps(bigvnf, default=str), + status=200, + mimetype="application/json", + ) -@app.route('/api/stats/') # Return a json of a usage stats for a given date (defaults to today) + +@app.route( + "/api/stats/" +) # Return a json of a usage stats for a given date (defaults to today) def apiStats(): try: - addToStat('api') + addToStat("api") today = str(date.today()) desiredDate = request.args.get("date", default=today, type=str) stat = getStats(desiredDate) - print (" ➤ [ ✔ ] Stats API called") - return Response(response=json.dumps(stat, default=str), status=200, mimetype="application/json") + print(" ➤ [ ✔ ] Stats API called") + return Response( + response=json.dumps(stat, default=str), + status=200, + mimetype="application/json", + ) except: - print (" ➤ [ ✔ ] Stats API failed") + print(" ➤ [ ✔ ] Stats API failed") -@app.route('/') # If the useragent is discord, return the embed, if not, redirect to configured repo directly + +@app.route( + "/" +) # If the useragent is discord, return the embed, if not, redirect to configured repo directly def default(): - user_agent = request.headers.get('user-agent') + user_agent = request.headers.get("user-agent") if user_agent in generate_embed_user_agents: - return message("TwitFix is an attempt to fix twitter video embeds in discord! created by Robin Universe :)\n\n💖\n\nClick me to be redirected to the repo!") + return message( + "Twxtter is an attempt to fix twitter video embeds in discord! :)\n\n💖\n\nClick me to be redirected to the repo!" + ) else: - return redirect(config['config']['repo'], 301) + return redirect(config["config"]["repo"], 301) -@app.route('/oembed.json') #oEmbed endpoint + +@app.route("/oembed.json") # oEmbed endpoint def oembedend(): - desc = request.args.get("desc", None) - user = request.args.get("user", None) - link = request.args.get("link", None) + desc = request.args.get("desc", None) + user = request.args.get("user", None) + link = request.args.get("link", None) ttype = request.args.get("ttype", None) - return oEmbedGen(desc, user, link, ttype) + return oEmbedGen(desc, user, link, ttype) -@app.route('/') # Default endpoint used by everything + +@app.route("/") # Default endpoint used by everything def twitfix(sub_path): - user_agent = request.headers.get('user-agent') + user_agent = request.headers.get("user-agent") match = pathregex.search(sub_path) print(request.url) - if request.url.startswith("https://d.fx"): # Matches d.fx? Try to give the user a direct link + if request.url.startswith( + "https://d.fx" + ): # Matches d.fx? Try to give the user a direct link if user_agent in generate_embed_user_agents: - print( " ➤ [ D ] d.fx link shown to discord user-agent!") + print(" ➤ [ D ] d.fx link shown to discord user-agent!") if request.url.endswith(".mp4") and "?" not in request.url: return dl(sub_path) else: - return message("To use a direct MP4 link in discord, remove anything past '?' and put '.mp4' at the end") + return message( + "To use a direct MP4 link in discord, remove anything past '?' and put '.mp4' at the end" + ) else: print(" ➤ [ R ] Redirect to MP4 using d.fxtwitter.com") return dir(sub_path) elif request.url.endswith(".mp4") or request.url.endswith("%2Emp4"): twitter_url = "https://twitter.com/" + sub_path - + if "?" not in request.url: clean = twitter_url[:-4] else: @@ -215,32 +312,68 @@ def twitfix(sub_path): elif request.url.endswith(".json") or request.url.endswith("%2Ejson"): twitter_url = "https://twitter.com/" + sub_path - + if "?" not in request.url: clean = twitter_url[:-5] else: clean = twitter_url - print( " ➤ [ API ] VNF Json api hit!") + print(" ➤ [ API ] VNF Json api hit!") - vnf = link_to_vnf_from_api(clean.replace(".json","")) + vnf = link_to_vnf_from_api(clean.replace(".json", "")) if user_agent in generate_embed_user_agents: - return message("VNF Data: ( discord useragent preview )\n\n"+ json.dumps(vnf, default=str)) + return message( + "VNF Data: ( discord useragent preview )\n\n" + + json.dumps(vnf, default=str) + ) else: - return Response(response=json.dumps(vnf, default=str), status=200, mimetype="application/json") + return Response( + response=json.dumps(vnf, default=str), + status=200, + mimetype="application/json", + ) - elif request.url.endswith("/1") or request.url.endswith("/2") or request.url.endswith("/3") or request.url.endswith("/4") or request.url.endswith("%2F1") or request.url.endswith("%2F2") or request.url.endswith("%2F3") or request.url.endswith("%2F4"): + elif ( + request.url.endswith("/1") + or request.url.endswith("/2") + or request.url.endswith("/3") + or request.url.endswith("/4") + or request.url.endswith("%2F1") + or request.url.endswith("%2F2") + or request.url.endswith("%2F3") + or request.url.endswith("%2F4") + ): twitter_url = "https://twitter.com/" + sub_path - + if "?" not in request.url: clean = twitter_url[:-2] else: clean = twitter_url - image = ( int(request.url[-1]) - 1 ) + image = int(request.url[-1]) - 1 return embed_video(clean, image) + elif ( + request.url.endswith("/1p") + or request.url.endswith("/2p") + or request.url.endswith("/3p") + or request.url.endswith("/4p") + or request.url.endswith("%2F1p") + or request.url.endswith("%2F2p") + or request.url.endswith("%2F3p") + or request.url.endswith("%2F4p") + ): + twitter_url = "https://twitter.com/" + sub_path + + if "?" not in request.url: + clean = twitter_url[:-3] + else: + clean = twitter_url + + image = int(request.url[-2]) - 1 + return embed_video(clean, image, raw=True) + if match is not None: twitter_url = sub_path @@ -257,56 +390,74 @@ def twitfix(sub_path): else: return message("This doesn't appear to be a twitter URL") -@app.route('/other/') # Show all info that Youtube-DL can get about a video as a json + +@app.route( + "/other/" +) # Show all info that Youtube-DL can get about a video as a json def other(sub_path): - otherurl = request.url.split("/other/", 1)[1].replace(":/","://") + otherurl = request.url.split("/other/", 1)[1].replace(":/", "://") print(" ➤ [ OTHER ] Other URL embed attempted: " + otherurl) res = embed_video(otherurl) return res -@app.route('/info/') # Show all info that Youtube-DL can get about a video as a json + +@app.route( + "/info/" +) # Show all info that Youtube-DL can get about a video as a json def info(sub_path): - infourl = request.url.split("/info/", 1)[1].replace(":/","://") + infourl = request.url.split("/info/", 1)[1].replace(":/", "://") print(" ➤ [ INFO ] Info data requested: " + infourl) - with youtube_dl.YoutubeDL({'outtmpl': '%(id)s.%(ext)s'}) as ydl: + with youtube_dl.YoutubeDL({"outtmpl": "%(id)s.%(ext)s"}) as ydl: result = ydl.extract_info(infourl, download=False) return result -@app.route('/dl/') # Download the tweets video, and rehost it + +@app.route("/dl/") # Download the tweets video, and rehost it def dl(sub_path): - print(' ➤ [[ !!! TRYING TO DOWNLOAD FILE !!! ]] Downloading file from ' + sub_path) - url = sub_path + print(" ➤ [[ !!! TRYING TO DOWNLOAD FILE !!! ]] Downloading file from " + sub_path) + url = sub_path match = pathregex.search(url) if match is not None: twitter_url = url if match.start() == 0: twitter_url = "https://twitter.com/" + url - - mp4link = direct_video_link(twitter_url) - filename = (sub_path.split('/')[-1].split('.mp4')[0] + '.mp4') - PATH = ( './static/' + filename ) + mp4link = direct_video_link(twitter_url) + filename = sub_path.split("/")[-1].split(".mp4")[0] + ".mp4" + + PATH = "./static/" + filename if os.path.isfile(PATH) and os.access(PATH, os.R_OK): print(" ➤ [[ FILE EXISTS ]]") else: print(" ➤ [[ FILE DOES NOT EXIST, DOWNLOADING... ]]") - addToStat('downloads') + addToStat("downloads") mp4file = urllib.request.urlopen(mp4link) - with open(('/home/robin/twitfix/static/' + filename), 'wb') as output: + with open(("/home/robin/twitfix/static/" + filename), "wb") as output: output.write(mp4file.read()) - print(' ➤ [[ PRESENTING FILE: '+ filename +', URL: https://fxtwitter.com/static/'+ filename +' ]]') - r = make_response(send_file(('static/' + filename), mimetype='video/mp4', max_age=100)) - r.headers['Content-Type'] = 'video/mp4' - r.headers['Sec-Fetch-Site'] = 'none' - r.headers['Sec-Fetch-User'] = '?1' + print( + " ➤ [[ PRESENTING FILE: " + + filename + + ", URL: https://fxtwitter.com/static/" + + filename + + " ]]" + ) + r = make_response( + send_file(("static/" + filename), mimetype="video/mp4", max_age=100) + ) + r.headers["Content-Type"] = "video/mp4" + r.headers["Sec-Fetch-Site"] = "none" + r.headers["Sec-Fetch-User"] = "?1" return r - -@app.route('/dir/') # Try to return a direct link to the MP4 on twitters servers + + +@app.route( + "/dir/" +) # Try to return a direct link to the MP4 on twitters servers def dir(sub_path): - user_agent = request.headers.get('user-agent') - url = sub_path + user_agent = request.headers.get("user-agent") + url = sub_path match = pathregex.search(url) if match is not None: twitter_url = url @@ -324,180 +475,226 @@ def dir(sub_path): else: return redirect(url, 301) -@app.route('/favicon.ico') # This shit don't work + +@app.route("/favicon.ico") # This shit don't work def favicon(): - return send_from_directory(os.path.join(app.root_path, 'static'), - 'favicon.ico',mimetype='image/vnd.microsoft.icon') + return send_from_directory( + os.path.join(app.root_path, "static"), + "favicon.ico", + mimetype="image/vnd.microsoft.icon", + ) -def direct_video(video_link): # Just get a redirect to a MP4 link from any tweet link + +def direct_video(video_link): # Just get a redirect to a MP4 link from any tweet link cached_vnf = getVnfFromLinkCache(video_link) if cached_vnf == None: try: vnf = link_to_vnf(video_link) addVnfToLinkCache(video_link, vnf) - return redirect(vnf['url'], 301) - print(" ➤ [ D ] Redirecting to direct URL: " + vnf['url']) + return redirect(vnf["url"], 301) + print(" ➤ [ D ] Redirecting to direct URL: " + vnf["url"]) except Exception as e: print(e) return message("Failed to scan your link!") else: - return redirect(cached_vnf['url'], 301) - print(" ➤ [ D ] Redirecting to direct URL: " + vnf['url']) + return redirect(cached_vnf["url"], 301) + print(" ➤ [ D ] Redirecting to direct URL: " + vnf["url"]) -def direct_video_link(video_link): # Just get a redirect to a MP4 link from any tweet link + +def direct_video_link( + video_link, +): # Just get a redirect to a MP4 link from any tweet link cached_vnf = getVnfFromLinkCache(video_link) if cached_vnf == None: try: vnf = link_to_vnf(video_link) addVnfToLinkCache(video_link, vnf) - return vnf['url'] - print(" ➤ [ D ] Redirecting to direct URL: " + vnf['url']) + return vnf["url"] + print(" ➤ [ D ] Redirecting to direct URL: " + vnf["url"]) except Exception as e: print(e) return message("Failed to scan your link!") else: - return cached_vnf['url'] - print(" ➤ [ D ] Redirecting to direct URL: " + vnf['url']) + return cached_vnf["url"] + print(" ➤ [ D ] Redirecting to direct URL: " + vnf["url"]) + def addToStat(stat): - #print(stat) + # print(stat) today = str(date.today()) try: - collection = db.stats.find_one({'date': today}) - delta = ( collection[stat] + 1 ) - query = { "date" : today } - change = { "$set" : { stat : delta } } - out = db.stats.update_one(query, change) + collection = db.stats.find_one({"date": today}) + delta = collection[stat] + 1 + query = {"date": today} + change = {"$set": {stat: delta}} + out = db.stats.update_one(query, change) except: - collection = db.stats.insert_one({'date': today, "embeds" : 1, "linksCached" : 1, "api" : 1, "downloads" : 1 }) + collection = db.stats.insert_one( + {"date": today, "embeds": 1, "linksCached": 1, "api": 1, "downloads": 1} + ) def getStats(day): - collection = db.stats.find_one({'date': day}) + collection = db.stats.find_one({"date": day}) return collection -def embed_video(video_link, image=0): # Return Embed from any tweet link + +def embed_video(video_link, image=0, raw=False): # Return Embed from any tweet link cached_vnf = getVnfFromLinkCache(video_link) if cached_vnf == None: try: vnf = link_to_vnf(video_link) addVnfToLinkCache(video_link, vnf) - return embed(video_link, vnf, image) + return embed(video_link, vnf, image, raw) except Exception as e: print(e) return message("Failed to scan your link!") else: - return embed(video_link, cached_vnf, image) + return embed(video_link, cached_vnf, image, raw) -def tweetInfo(url, tweet="", desc="", thumb="", uploader="", screen_name="", pfp="", tweetType="", images="", hits=0, likes=0, rts=0, time="", qrt={}, nsfw=False): # Return a dict of video info with default values + +def tweetInfo( + url, + tweet="", + desc="", + thumb="", + uploader="", + screen_name="", + pfp="", + tweetType="", + images="", + hits=0, + likes=0, + rts=0, + time="", + qrt={}, + nsfw=False, +): # Return a dict of video info with default values vnf = { - "tweet" : tweet, - "url" : url, - "description" : desc, - "thumbnail" : thumb, - "uploader" : uploader, - "screen_name" : screen_name, - "pfp" : pfp, - "type" : tweetType, - "images" : images, - "hits" : hits, - "likes" : likes, - "rts" : rts, - "time" : time, - "qrt" : qrt, - "nsfw" : nsfw + "tweet": tweet, + "url": url, + "description": desc, + "thumbnail": thumb, + "uploader": uploader, + "screen_name": screen_name, + "pfp": pfp, + "type": tweetType, + "images": images, + "hits": hits, + "likes": likes, + "rts": rts, + "time": time, + "qrt": qrt, + "nsfw": nsfw, } return vnf + def link_to_vnf_from_api(video_link): print(" ➤ [ + ] Attempting to download tweet info from Twitter API") - twid = int(re.sub(r'\?.*$','',video_link.rsplit("/", 1)[-1])) # gets the tweet ID as a int from the passed url + twid = int( + re.sub(r"\?.*$", "", video_link.rsplit("/", 1)[-1]) + ) # gets the tweet ID as a int from the passed url tweet = twitter_api.statuses.show(_id=twid, tweet_mode="extended") # For when I need to poke around and see what a tweet looks like - #print(tweet) - imgs = ["","","","", ""] + # print(tweet) + imgs = ["", "", "", "", ""] print(" ➤ [ + ] Tweet Type: " + tweetType(tweet)) # Check to see if tweet has a video, if not, make the url passed to the VNF the first t.co link in the tweet if tweetType(tweet) == "Video": - if tweet['extended_entities']['media'][0]['video_info']['variants']: + if tweet["extended_entities"]["media"][0]["video_info"]["variants"]: best_bitrate = 0 - thumb = tweet['extended_entities']['media'][0]['media_url'] - for video in tweet['extended_entities']['media'][0]['video_info']['variants']: - if video['content_type'] == "video/mp4" and video['bitrate'] > best_bitrate: - url = video['url'] + thumb = tweet["extended_entities"]["media"][0]["media_url"] + for video in tweet["extended_entities"]["media"][0]["video_info"][ + "variants" + ]: + if ( + video["content_type"] == "video/mp4" + and video["bitrate"] > best_bitrate + ): + url = video["url"] elif tweetType(tweet) == "Text": - url = "" + url = "" thumb = "" else: - imgs = ["","","","", ""] + imgs = ["", "", "", "", ""] i = 0 - for media in tweet['extended_entities']['media']: - imgs[i] = media['media_url_https'] + for media in tweet["extended_entities"]["media"]: + imgs[i] = media["media_url_https"] i = i + 1 - #print(imgs) + # print(imgs) imgs[4] = str(i) - url = "" - images= imgs - thumb = tweet['extended_entities']['media'][0]['media_url_https'] + url = "" + images = imgs + thumb = tweet["extended_entities"]["media"][0]["media_url_https"] qrt = {} - if 'quoted_status' in tweet: - qrt['desc'] = tweet['quoted_status']['full_text'] - qrt['handle'] = tweet['quoted_status']['user']['name'] - qrt['screen_name'] = tweet['quoted_status']['user']['screen_name'] + if "quoted_status" in tweet: + qrt["desc"] = tweet["quoted_status"]["full_text"] + qrt["handle"] = tweet["quoted_status"]["user"]["name"] + qrt["screen_name"] = tweet["quoted_status"]["user"]["screen_name"] - text = tweet['full_text'] + text = tweet["full_text"] - if 'possibly_sensitive' in tweet: - nsfw = tweet['possibly_sensitive'] + if "possibly_sensitive" in tweet: + nsfw = tweet["possibly_sensitive"] else: nsfw = False vnf = tweetInfo( - url, - video_link, - text, thumb, - tweet['user']['name'], - tweet['user']['screen_name'], - tweet['user']['profile_image_url'], - tweetType(tweet), - likes=tweet['favorite_count'], - rts=tweet['retweet_count'], - time=tweet['created_at'], - qrt=qrt, + url, + video_link, + text, + thumb, + tweet["user"]["name"], + tweet["user"]["screen_name"], + tweet["user"]["profile_image_url"], + tweetType(tweet), + likes=tweet["favorite_count"], + rts=tweet["retweet_count"], + time=tweet["created_at"], + qrt=qrt, images=imgs, - nsfw=nsfw - ) - + nsfw=nsfw, + ) + return vnf + def link_to_vnf_from_youtubedl(video_link): print(" ➤ [ X ] Attempting to download tweet info via YoutubeDL: " + video_link) - with youtube_dl.YoutubeDL({'outtmpl': '%(id)s.%(ext)s'}) as ydl: + with youtube_dl.YoutubeDL({"outtmpl": "%(id)s.%(ext)s"}) as ydl: result = ydl.extract_info(video_link, download=False) - vnf = tweetInfo(result['url'], video_link, result['description'].rsplit(' ',1)[0], result['thumbnail'], result['uploader']) + vnf = tweetInfo( + result["url"], + video_link, + result["description"].rsplit(" ", 1)[0], + result["thumbnail"], + result["uploader"], + ) return vnf -def link_to_vnf(video_link): # Return a VideoInfo object or die trying - if config['config']['method'] == 'hybrid': + +def link_to_vnf(video_link): # Return a VideoInfo object or die trying + if config["config"]["method"] == "hybrid": try: return link_to_vnf_from_api(video_link) except Exception as e: print(" ➤ [ !!! ] API Failed") print(e) return link_to_vnf_from_youtubedl(video_link) - elif config['config']['method'] == 'api': + elif config["config"]["method"] == "api": try: return link_to_vnf_from_api(video_link) except Exception as e: print(" ➤ [ X ] API Failed") print(e) return None - elif config['config']['method'] == 'youtube-dl': + elif config["config"]["method"] == "youtube-dl": try: return link_to_vnf_from_youtubedl(video_link) except Exception as e: @@ -505,21 +702,29 @@ def link_to_vnf(video_link): # Return a VideoInfo object or die trying print(e) return None else: - print("Please set the method key in your config file to 'api' 'youtube-dl' or 'hybrid'") + print( + "Please set the method key in your config file to 'api' 'youtube-dl' or 'hybrid'" + ) return None + def getVnfFromLinkCache(video_link): if link_cache_system == "db": collection = db.linkCache - vnf = collection.find_one({'tweet': video_link}) + vnf = collection.find_one({"tweet": video_link}) # print(vnf) - if vnf != None: - hits = ( vnf['hits'] + 1 ) - print(" ➤ [ ✔ ] Link located in DB cache. " + "hits on this link so far: [" + str(hits) + "]") - query = { 'tweet': video_link } - change = { "$set" : { "hits" : hits } } - out = db.linkCache.update_one(query, change) - addToStat('embeds') + if vnf != None: + hits = vnf["hits"] + 1 + print( + " ➤ [ ✔ ] Link located in DB cache. " + + "hits on this link so far: [" + + str(hits) + + "]" + ) + query = {"tweet": video_link} + change = {"$set": {"hits": hits}} + out = db.linkCache.update_one(query, change) + addToStat("embeds") return vnf else: print(" ➤ [ X ] Link not in DB cache") @@ -533,95 +738,122 @@ def getVnfFromLinkCache(video_link): print(" ➤ [ X ] Link not in json cache") return None + def addVnfToLinkCache(video_link, vnf): if link_cache_system == "db": try: out = db.linkCache.insert_one(vnf) print(" ➤ [ + ] Link added to DB cache ") - addToStat('linksCached') + addToStat("linksCached") return True except Exception: print(" ➤ [ X ] Failed to add link to DB cache") return None elif link_cache_system == "json": link_cache[video_link] = vnf - with open("links.json", "w") as outfile: + with open("links.json", "w") as outfile: json.dump(link_cache, outfile, indent=4, sort_keys=True) return None + def message(text): return render_template( - 'default.html', - message = text, - color = config['config']['color'], - appname = config['config']['appname'], - repo = config['config']['repo'], - url = config['config']['url'] ) + "default.html", + message=text, + color=config["config"]["color"], + appname=config["config"]["appname"], + repo=config["config"]["repo"], + url=config["config"]["url"], + ) -def embed(video_link, vnf, image): - print(" ➤ [ E ] Embedding " + vnf['type'] + ": " + vnf['url']) - - desc = re.sub(r' http.*t\.co\S+', '', vnf['description']) - urlUser = urllib.parse.quote(vnf['uploader']) + +def embed(video_link, vnf, image, raw=False): + print(" ➤ [ E ] Embedding " + vnf["type"] + ": " + vnf["url"]) + + desc = re.sub(r" http.*t\.co\S+", "", vnf["description"]) + urlUser = urllib.parse.quote(vnf["uploader"]) urlDesc = urllib.parse.quote(desc) urlLink = urllib.parse.quote(video_link) - likeDisplay = ("\n\n💖 " + str(vnf['likes']) + " 🔁 " + str(vnf['rts']) + "\n") - - try: - if vnf['type'] == "": - desc = desc - elif vnf['type'] == "Video": - desc = desc - elif vnf['qrt'] == {}: # Check if this is a QRT and modify the description - desc = (desc + likeDisplay) - else: - qrtDisplay = ("\n─────────────\n ➤ QRT of " + vnf['qrt']['handle'] + " (@" + vnf['qrt']['screen_name'] + "):\n─────────────\n'" + vnf['qrt']['desc'] + "'") - desc = (desc + qrtDisplay + likeDisplay) - except: - vnf['likes'] = 0; vnf['rts'] = 0; vnf['time'] = 0 - print(' ➤ [ X ] Failed QRT check - old VNF object') - - if vnf['type'] == "Text": # Change the template based on tweet type - template = 'text.html' - if vnf['type'] == "Image": - image = vnf['images'][image] - template = 'image.html' - if vnf['type'] == "Video": - urlDesc = urllib.parse.quote(textwrap.shorten(desc, width=220, placeholder="...")) - template = 'video.html' - if vnf['type'] == "": - urlDesc = urllib.parse.quote(textwrap.shorten(desc, width=220, placeholder="...")) - template = 'video.html' - - color = "#7FFFD4" # Green + likeDisplay = "\n\n💖 " + str(vnf["likes"]) + " 🔁 " + str(vnf["rts"]) + "\n" + imagecount = "Twitter" - if vnf['nsfw'] == True: - color = "#800020" # Red + try: + if vnf["type"] == "": + desc = desc + elif vnf["type"] == "Video": + desc = desc + elif vnf["qrt"] == {}: # Check if this is a QRT and modify the description + desc = desc + likeDisplay + else: + qrtDisplay = ( + "\n─────────────\n ➤ QRT of " + + vnf["qrt"]["handle"] + + " (@" + + vnf["qrt"]["screen_name"] + + "):\n─────────────\n'" + + vnf["qrt"]["desc"] + + "'" + ) + desc = desc + qrtDisplay + likeDisplay + except: + vnf["likes"] = 0 + vnf["rts"] = 0 + vnf["time"] = 0 + print(" ➤ [ X ] Failed QRT check - old VNF object") + + if vnf["type"] == "Text": # Change the template based on tweet type + template = "text.html" + if vnf["type"] == "Image": + image = vnf["images"][image] + if vnf["images"][4] != "1": + imagecount = "Twitter (" + vnf["images"][4] + " images in post)" + if raw == True: + template = "img.html" + else: + template = "image.html" + if vnf["type"] == "Video": + urlDesc = urllib.parse.quote( + textwrap.shorten(desc, width=220, placeholder="...") + ) + template = "video.html" + if vnf["type"] == "": + urlDesc = urllib.parse.quote( + textwrap.shorten(desc, width=220, placeholder="...") + ) + template = "video.html" + + color = "#7FFFD4" # Green + + if vnf["nsfw"] == True: + color = "#800020" # Red return render_template( - template, - likes = vnf['likes'], - rts = vnf['rts'], - time = vnf['time'], - screenName = vnf['screen_name'], - vidlink = vnf['url'], - pfp = vnf['pfp'], - vidurl = vnf['url'], - desc = desc, - pic = image, - user = vnf['uploader'], - video_link = video_link, - color = color, - appname = config['config']['appname'], - repo = config['config']['repo'], - url = config['config']['url'], - urlDesc = urlDesc, - urlUser = urlUser, - urlLink = urlLink ) + template, + likes=vnf["likes"], + rts=vnf["rts"], + time=vnf["time"], + screenName=vnf["screen_name"], + vidlink=vnf["url"], + pfp=vnf["pfp"], + vidurl=vnf["url"], + desc=desc, + pic=image, + imagecount=imagecount, + user=vnf["uploader"], + video_link=video_link, + color=color, + appname=config["config"]["appname"], + repo=config["config"]["repo"], + url=config["config"]["url"], + urlDesc=urlDesc, + urlUser=urlUser, + urlLink=urlLink, + ) -def tweetType(tweet): # Are we dealing with a Video, Image, or Text tweet? - if 'extended_entities' in tweet: - if 'video_info' in tweet['extended_entities']['media'][0]: + +def tweetType(tweet): # Are we dealing with a Video, Image, or Text tweet? + if "extended_entities" in tweet: + if "video_info" in tweet["extended_entities"]["media"][0]: out = "Video" else: out = "Image" @@ -633,17 +865,18 @@ def tweetType(tweet): # Are we dealing with a Video, Image, or Text tweet? def oEmbedGen(description, user, video_link, ttype): out = { - "type" : ttype, - "version" : "1.0", - "provider_name" : config['config']['appname'], - "provider_url" : config['config']['repo'], - "title" : description, - "author_name" : user, - "author_url" : video_link - } + "type": ttype, + "version": "1.0", + "provider_name": config["config"]["appname"], + "provider_url": config["config"]["repo"], + "title": description, + "author_name": user, + "author_url": video_link, + } return out + if __name__ == "__main__": - app.config['SERVER_NAME']='localhost:80' - app.run(host='0.0.0.0') + app.config["SERVER_NAME"] = "localhost:80" + app.run(host="0.0.0.0") diff --git a/wsgi.py b/wsgi.py index 456d4ba..7637b73 100644 --- a/wsgi.py +++ b/wsgi.py @@ -2,4 +2,4 @@ from twitfix import app if __name__ == "__main__": # listen on 0.0.0.0 to facilitate testing with real services - app.run(host='0.0.0.0') \ No newline at end of file + app.run(host="0.0.0.0")