2021-07-13 02:45:30 +02:00
from flask import Flask , render_template , request , redirect
2021-07-04 01:52:30 +02:00
import youtube_dl
2021-07-12 22:48:21 +02:00
import textwrap
2021-07-12 10:49:07 +02:00
import twitter
import pymongo
2021-07-04 11:40:22 +02:00
import json
2021-07-04 03:41:47 +02:00
import re
2021-07-11 22:48:17 +02:00
import os
2021-07-04 01:52:30 +02:00
app = Flask ( __name__ )
2021-07-04 03:41:47 +02:00
pathregex = re . compile ( " \\ w { 1,15} \\ /status \\ / \\ d {19} " )
2021-07-13 02:45:30 +02:00
discord_user_agents = [ " Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0 " , " Mozilla/5.0 (compatible; Discordbot/2.0; +https://discordapp.com) " ]
2021-07-16 22:25:43 +02:00
telegram_user_agents = [ " TelegramBot (like TwitterBot) " ]
2021-07-04 01:52:30 +02:00
2021-07-13 19:18:50 +02:00
# Read config from config.json. If it does not exist, create new.
2021-07-11 22:48:17 +02:00
if not os . path . exists ( " config.json " ) :
with open ( " config.json " , " w " ) as outfile :
2021-07-14 10:33:46 +02:00
default_config = { " config " : { " link_cache " : " json " , " database " : " [url to mongo database goes here] " , " method " : " youtube-dl " , " color " : " #43B581 " , " appname " : " TwitFix " , " repo " : " https://github.com/robinuniverse/twitfix " , " url " : " https://fxtwitter.com " } , " api " : { " api_key " : " [api_key goes here] " , " api_secret " : " [api_secret goes here] " , " access_token " : " [access_token goes here] " , " access_secret " : " [access_secret goes here] " } }
2021-07-13 19:18:50 +02:00
json . dump ( default_config , outfile , indent = 4 , sort_keys = True )
2021-07-11 22:48:17 +02:00
2021-07-13 19:18:50 +02:00
config = default_config
else :
f = open ( " config.json " )
config = json . load ( f )
f . close ( )
2021-07-11 22:48:17 +02:00
2021-07-13 20:57:09 +02:00
# If method is set to API or Hybrid, attempt to auth with the Twitter API
2021-07-12 21:49:29 +02:00
if config [ ' config ' ] [ ' method ' ] in ( ' api ' , ' hybrid ' ) :
2021-07-12 10:49:07 +02:00
auth = twitter . oauth . OAuth ( config [ ' api ' ] [ ' access_token ' ] , config [ ' api ' ] [ ' access_secret ' ] , config [ ' api ' ] [ ' api_key ' ] , config [ ' api ' ] [ ' api_secret ' ] )
twitter_api = twitter . Twitter ( auth = auth )
link_cache_system = config [ ' config ' ] [ ' link_cache ' ]
2021-07-08 05:17:23 +02:00
if link_cache_system == " json " :
link_cache = { }
2021-07-11 22:48:17 +02:00
if not os . path . exists ( " config.json " ) :
with open ( " config.json " , " w " ) as outfile :
2021-07-13 19:18:50 +02:00
default_link_cache = { " test " : " test " }
json . dump ( default_link_cache , outfile , indent = 4 , sort_keys = True )
2021-07-11 22:48:17 +02:00
2021-07-08 05:17:23 +02:00
f = open ( ' links.json ' , )
link_cache = json . load ( f )
f . close ( )
elif link_cache_system == " db " :
2021-07-11 22:48:17 +02:00
client = pymongo . MongoClient ( config [ ' config ' ] [ ' database ' ] , connect = False )
2021-07-08 05:17:23 +02:00
db = client . TwitFix
2021-07-04 11:40:22 +02:00
2021-07-14 10:33:46 +02:00
@app.route ( ' / ' ) # If the useragent is discord, return the embed, if not, redirect to configured repo directly
2021-07-06 05:30:16 +02:00
def default ( ) :
2021-07-14 10:33:46 +02:00
user_agent = request . headers . get ( ' user-agent ' )
if user_agent in discord_user_agents :
return message ( " TwitFix is an attempt to fix twitter video embeds in discord! created by Robin Universe :) \n \n 💖 \n \n Click me to be redirected to the repo! " )
else :
return redirect ( config [ ' config ' ] [ ' repo ' ] , 301 )
2021-07-06 05:30:16 +02:00
2021-07-09 12:32:04 +02:00
@app.route ( ' /oembed.json ' )
def oembedend ( ) :
desc = request . args . get ( " desc " , None )
user = request . args . get ( " user " , None )
link = request . args . get ( " link " , None )
2021-07-13 19:18:50 +02:00
return o_embed_gen ( desc , user , link )
2021-07-09 12:32:04 +02:00
2021-07-13 20:57:09 +02:00
@app.route ( ' /<path:sub_path> ' )
2021-07-13 19:18:50 +02:00
def twitfix ( sub_path ) :
2021-07-13 02:45:30 +02:00
user_agent = request . headers . get ( ' user-agent ' )
2021-07-13 19:18:50 +02:00
match = pathregex . search ( sub_path )
2021-07-13 08:32:25 +02:00
if match is not None :
2021-07-13 19:18:50 +02:00
twitter_url = sub_path
2021-07-04 03:41:47 +02:00
2021-07-13 08:32:25 +02:00
if match . start ( ) == 0 :
2021-07-13 19:18:50 +02:00
twitter_url = " https://twitter.com/ " + sub_path
2021-07-04 03:41:47 +02:00
2021-07-13 08:32:25 +02:00
if user_agent in discord_user_agents :
2021-07-13 19:18:50 +02:00
res = embed_video ( twitter_url )
2021-07-13 02:45:30 +02:00
return res
2021-07-16 22:25:43 +02:00
if user_agent in telegram_user_agents :
res = embed_video ( twitter_url )
return res
2021-07-13 02:45:30 +02:00
else :
2021-07-13 20:57:09 +02:00
print ( " Redirect to " + twitter_url )
2021-07-13 08:32:25 +02:00
return redirect ( twitter_url , 301 )
2021-07-04 01:52:30 +02:00
else :
2021-07-13 20:57:09 +02:00
return redirect ( sub_path , 301 )
2021-07-04 01:52:30 +02:00
2021-07-14 10:33:46 +02:00
@app.route ( ' /other/<path:sub_path> ' ) # Show all info that Youtube-DL can get about a video as a json
2021-07-13 19:18:50 +02:00
def other ( sub_path ) :
res = embed_video ( sub_path )
2021-07-05 18:02:17 +02:00
return res
2021-07-14 10:33:46 +02:00
@app.route ( ' /info/<path:sub_path> ' ) # Show all info that Youtube-DL can get about a video as a json
2021-07-13 19:18:50 +02:00
def info ( sub_path ) :
2021-07-04 23:54:23 +02:00
with youtube_dl . YoutubeDL ( { ' outtmpl ' : ' %(id)s . %(ext)s ' } ) as ydl :
2021-07-13 19:18:50 +02:00
result = ydl . extract_info ( sub_path , download = False )
2021-07-04 01:52:30 +02:00
return result
2021-07-13 19:19:24 +02:00
def embed_video ( video_link ) :
cached_vnf = get_vnf_from_link_cache ( video_link )
2021-07-08 05:17:23 +02:00
2021-07-12 10:49:07 +02:00
if cached_vnf == None :
2021-07-08 05:17:23 +02:00
try :
2021-07-13 19:19:24 +02:00
vnf = link_to_vnf ( video_link )
add_vnf_to_link_cache ( video_link , vnf )
return embed ( video_link , vnf )
2021-07-12 10:49:07 +02:00
except Exception as e :
print ( e )
2021-07-14 10:33:46 +02:00
return message ( " Failed to scan your link! " )
2021-07-12 10:49:07 +02:00
else :
2021-07-13 19:19:24 +02:00
return embed ( video_link , cached_vnf )
2021-07-05 18:02:17 +02:00
2021-07-13 19:18:50 +02:00
def video_info ( url , tweet = " " , desc = " " , thumb = " " , uploader = " " ) : # Return a dict of video info with default values
2021-07-05 18:02:17 +02:00
vnf = {
2021-07-08 05:17:23 +02:00
" tweet " : tweet ,
2021-07-05 18:02:17 +02:00
" url " : url ,
" description " : desc ,
" thumbnail " : thumb ,
" uploader " : uploader
}
return vnf
2021-07-13 19:19:24 +02:00
def link_to_vnf_from_api ( video_link ) :
2021-07-12 20:49:19 +02:00
print ( " Attempting to download tweet info from Twitter API " )
2021-07-13 19:19:24 +02:00
twid = int ( re . sub ( r ' \ ?.*$ ' , ' ' , video_link . rsplit ( " / " , 1 ) [ - 1 ] ) ) # gets the tweet ID as a int from the passed url
2021-07-12 20:49:19 +02:00
tweet = twitter_api . statuses . show ( _id = twid , tweet_mode = " extended " )
2021-07-15 02:15:06 +02:00
# 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 ' extended_entities ' in tweet :
if ' video_info ' in tweet [ ' extended_entities ' ] [ ' media ' ] [ 0 ] :
if tweet [ ' extended_entities ' ] [ ' media ' ] [ 0 ] [ ' video_info ' ] [ ' variants ' ] [ - 1 ] [ ' content_type ' ] == " video/mp4 " :
url = tweet [ ' extended_entities ' ] [ ' media ' ] [ 0 ] [ ' video_info ' ] [ ' variants ' ] [ - 1 ] [ ' url ' ]
thumb = tweet [ ' extended_entities ' ] [ ' media ' ] [ 0 ] [ ' media_url ' ]
else :
url = tweet [ ' extended_entities ' ] [ ' media ' ] [ 0 ] [ ' video_info ' ] [ ' variants ' ] [ - 2 ] [ ' url ' ]
thumb = tweet [ ' extended_entities ' ] [ ' media ' ] [ 0 ] [ ' media_url ' ]
else :
url = re . findall ( r ' (https?://[^ \ s]+) ' , tweet [ ' full_text ' ] ) [ 0 ]
thumb = " Non video link with url "
print ( " Non video tweet, but has a link: " + url )
2021-07-12 20:49:19 +02:00
else :
2021-07-15 02:15:06 +02:00
url = re . findall ( r ' (https?://[^ \ s]+) ' , tweet [ ' full_text ' ] ) [ 0 ]
thumb = " Non video link with url "
print ( " Non video tweet, but has a link: " + url )
2021-07-12 23:41:07 +02:00
if len ( tweet [ ' full_text ' ] ) > 200 :
text = textwrap . shorten ( tweet [ ' full_text ' ] , width = 200 , placeholder = " ... " )
else :
text = tweet [ ' full_text ' ]
2021-07-15 02:15:06 +02:00
vnf = video_info ( url , video_link , text , thumb , tweet [ ' user ' ] [ ' name ' ] )
2021-07-12 20:49:19 +02:00
return vnf
2021-07-13 19:19:24 +02:00
def link_to_vnf_from_youtubedl ( video_link ) :
2021-07-12 20:49:19 +02:00
print ( " Attempting to download tweet info via YoutubeDL " )
with youtube_dl . YoutubeDL ( { ' outtmpl ' : ' %(id)s . %(ext)s ' } ) as ydl :
2021-07-13 19:19:24 +02:00
result = ydl . extract_info ( video_link , download = False )
vnf = video_info ( result [ ' url ' ] , video_link , result [ ' description ' ] . rsplit ( ' ' , 1 ) [ 0 ] , result [ ' thumbnail ' ] , result [ ' uploader ' ] )
2021-07-12 10:49:07 +02:00
return vnf
2021-07-12 20:49:19 +02:00
2021-07-13 19:19:24 +02:00
def link_to_vnf ( video_link ) : # Return a VideoInfo object or die trying
2021-07-12 20:49:19 +02:00
if config [ ' config ' ] [ ' method ' ] == ' hybrid ' :
try :
2021-07-13 19:19:24 +02:00
return link_to_vnf_from_api ( video_link )
2021-07-12 21:49:29 +02:00
except Exception as e :
2021-07-12 20:49:19 +02:00
print ( " API Failed " )
2021-07-12 21:49:29 +02:00
print ( e )
2021-07-13 19:19:24 +02:00
return link_to_vnf_from_youtubedl ( video_link )
2021-07-12 20:49:19 +02:00
elif config [ ' config ' ] [ ' method ' ] == ' api ' :
try :
2021-07-13 19:19:24 +02:00
return link_to_vnf_from_api ( video_link )
2021-07-12 20:49:19 +02:00
except Exception as e :
print ( " API Failed " )
print ( e )
return None
elif config [ ' config ' ] [ ' method ' ] == ' youtube-dl ' :
try :
2021-07-13 19:19:24 +02:00
return link_to_vnf_from_youtubedl ( video_link )
2021-07-12 20:49:19 +02:00
except Exception as e :
print ( " Youtube-DL Failed " )
print ( e )
return None
else :
print ( " Please set the method key in your config file to ' api ' ' youtube-dl ' or ' hybrid ' " )
return None
2021-07-13 19:18:50 +02:00
def get_vnf_from_link_cache ( video_link ) :
2021-07-12 10:49:07 +02:00
if link_cache_system == " db " :
collection = db . linkCache
2021-07-13 19:18:50 +02:00
vnf = collection . find_one ( { ' tweet ' : video_link } )
2021-07-12 10:49:07 +02:00
if vnf != None :
print ( " Link located in DB cache " )
return vnf
else :
print ( " Link not in DB cache " )
return None
elif link_cache_system == " json " :
2021-07-13 19:18:50 +02:00
if video_link in link_cache :
2021-07-12 10:49:07 +02:00
print ( " Link located in json cache " )
2021-07-13 19:18:50 +02:00
vnf = link_cache [ video_link ]
2021-07-12 10:49:07 +02:00
return vnf
else :
print ( " Link not in json cache " )
return None
2021-07-13 19:18:50 +02:00
def add_vnf_to_link_cache ( video_link , vnf ) :
2021-07-12 10:49:07 +02:00
if link_cache_system == " db " :
try :
out = db . linkCache . insert_one ( vnf )
print ( " Link added to DB cache " )
return True
except Exception :
print ( " Failed to add link to DB cache " )
return None
elif link_cache_system == " json " :
2021-07-13 19:18:50 +02:00
link_cache [ video_link ] = vnf
2021-07-12 10:49:07 +02:00
with open ( " links.json " , " w " ) as outfile :
json . dump ( link_cache , outfile , indent = 4 , sort_keys = True )
return None
2021-07-14 10:33:46 +02:00
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 ' ] )
2021-07-13 19:19:24 +02:00
def embed ( video_link , vnf ) :
2021-07-15 02:15:06 +02:00
print ( vnf [ ' url ' ] )
if vnf [ ' url ' ] . startswith ( ' https://t.co ' ) is not True :
desc = re . sub ( r ' http.*t \ .co \ S+ ' , ' ' , vnf [ ' description ' ] . replace ( " # " , " # " ) ) # some funky string manipulation to get rid of the t.co vid link and replace # with a similar looking character, the normal # breaks when getting fed into the oembed endpoint
return render_template ( ' index.html ' , vidurl = vnf [ ' url ' ] , desc = desc , pic = vnf [ ' thumbnail ' ] , user = vnf [ ' uploader ' ] , video_link = video_link , color = config [ ' config ' ] [ ' color ' ] , appname = config [ ' config ' ] [ ' appname ' ] , repo = config [ ' config ' ] [ ' repo ' ] , url = config [ ' config ' ] [ ' url ' ] )
else :
return redirect ( vnf [ ' url ' ] , 301 )
2021-07-12 10:49:07 +02:00
2021-07-13 19:19:24 +02:00
def o_embed_gen ( description , user , video_link ) :
2021-07-09 12:32:04 +02:00
out = {
" type " : " video " ,
" version " : " 1.0 " ,
" provider_name " : " TwitFix " ,
" provider_url " : " https://github.com/robinuniverse/twitfix " ,
" title " : description ,
" author_name " : user ,
2021-07-13 19:19:24 +02:00
" author_url " : video_link
2021-07-09 12:32:04 +02:00
}
return out
2021-07-04 01:52:30 +02:00
if __name__ == " __main__ " :
2021-07-08 05:17:23 +02:00
app . run ( host = ' 0.0.0.0 ' )