mastotuit/mastotuit.py

366 líneas
9 KiB
Python

import os
import feedparser
import html2text
from mastodon import Mastodon
import psycopg2
import sys
import time
import requests
import shutil
import tweepy
from tweepy import TweepyException
import logging
import filetype
import ffmpeg
import pdb
logger = logging.getLogger()
def get_toot_text(title):
html2text.hn = lambda _:0
h = html2text.HTML2Text()
h.images_to_alt = True
h.single_line_break = True
h.ignore_emphasis = True
h.ignore_links = True
h.ignore_tables = True
tuit_text = h.handle(title)
return tuit_text
def compose_tweet(tuit_text, with_images, is_reply):
images_id_lst = []
if with_images:
for image in images_list:
kind = filetype.guess('images/' + image)
is_video = True if kind.mime == 'video/mp4' else False
if is_video:
try:
probe = ffmpeg.probe(f'images/{image}')
video_duration = float(
probe['streams'][0]['duration']
)
except Exception as e:
print(f'Error while trying to probe {image}\n{e}')
sys.exit(e)
if video_duration > 139:
print(f'video duration is too large: {video_duration}')
continue
try:
media_upload = api.media_upload(
f'images/{image}',
media_category='tweet_video' if is_video else 'tweet_image'
)
if is_video:
if media_upload.processing_info['state'] == 'succeeded':
images_id_lst.append(media_upload.media_id)
else:
images_id_lst.append(media_upload.media_id)
except TweepyException as err:
print('Error while uploading media!\n')
sys.exit(err)
# Compose tuit
tuit_text2 = ''
if len(tuit_text) > 280:
tuit_max_length = 250 if with_images else 275
tuit_text1 = '{0} (1/2)'.format(
tuit_text[:tuit_max_length].rsplit(' ', 1)[0]
)
tuit_text2 = '{0} (2/2)'.format(
tuit_text[len(tuit_text1) - 6:]
)
try:
first_tweet = api.update_status(
status=tuit_text1,
in_reply_to_status_id=tweet_id if is_reply else ''
)
tweet = api.update_status(
status=tuit_text2,
in_reply_to_status_id=first_tweet.id,
media_ids=images_id_lst
)
except TweepyException as err:
print('Error while trying to publish split tweet.\n')
sys.exit(err)
else:
try:
tweet = api.update_status(
status=tuit_text,
in_reply_to_status_id=tweet_id if is_reply else '',
media_ids=images_id_lst
)
except TweepyException as err:
print('Error while trying to publish tweet.\n')
sys.exit(err)
return tweet
def get_tweet_id(toot_id):
tweet_id = 0
try:
conn = None
conn = psycopg2.connect(database = feeds_db, user = feeds_db_user, password = "", host = "/var/run/postgresql", port = "5432")
cur = conn.cursor()
cur.execute('select tweet_id from id where toot_id=(%s)', (toot_id,))
row = cur.fetchone()
if row != None:
tweet_id = row[0]
cur.close()
return(tweet_id)
except (Exception, psycopg2.DatabaseError) as error:
print(error)
finally:
if conn is not None:
conn.close()
def write_image(image_url):
if not os.path.exists('images'):
os.makedirs('images')
filename = image_url.split("/") [-1]
r = requests.get(image_url, stream = True)
r.raw.decode_content = True
with open('images/' + filename, 'wb') as f:
shutil.copyfileobj(r.raw, f)
return filename
def create_api():
auth = tweepy.OAuthHandler(api_key, api_key_secret)
auth.set_access_token(access_token, access_token_secret)
api = tweepy.API(auth)
try:
api.verify_credentials()
logged_in = True
except Exception as e:
logger.error("Error creating API", exc_info=True)
raise e
logger.info("API created")
return (api, logged_in)
def mastodon():
# Load secrets from secrets file
secrets_filepath = "secrets/secrets.txt"
uc_client_id = get_parameter("uc_client_id", secrets_filepath)
uc_client_secret = get_parameter("uc_client_secret", secrets_filepath)
uc_access_token = get_parameter("uc_access_token", secrets_filepath)
# Load configuration from config file
config_filepath = "config/config.txt"
mastodon_hostname = get_parameter("mastodon_hostname", config_filepath)
# Initialise Mastodon API
mastodon = Mastodon(
client_id=uc_client_id,
client_secret=uc_client_secret,
access_token=uc_access_token,
api_base_url='https://' + mastodon_hostname,
)
# Initialise access headers
headers = {'Authorization': 'Bearer %s'%uc_access_token}
return (mastodon, mastodon_hostname)
def db_config():
# Load db configuration from config file
db_config_filepath = "config/db_config.txt"
feeds_db = get_parameter("feeds_db", db_config_filepath)
feeds_db_user = get_parameter("feeds_db_user", db_config_filepath)
feeds_url = get_parameter("feeds_url", db_config_filepath)
return (feeds_db, feeds_db_user, feeds_url)
def twitter_config():
twitter_config_filepath = "config/keys_config.txt"
api_key = get_parameter("api_key", twitter_config_filepath)
api_key_secret = get_parameter("api_key_secret", twitter_config_filepath)
access_token = get_parameter("access_token", twitter_config_filepath)
access_token_secret = get_parameter("access_token_secret", twitter_config_filepath)
return(api_key, api_key_secret, access_token, access_token_secret)
# Returns the parameter from the specified file
def get_parameter( parameter, file_path ):
# Check if secrets file exists
if not os.path.isfile(file_path):
print("File %s not found, exiting."%file_path)
sys.exit(0)
# Find parameter in file
with open( file_path ) as f:
for line in f:
if line.startswith( parameter ):
return line.replace(parameter + ":", "").strip()
# Cannot find parameter, exit
print(file_path + " Missing parameter %s "%parameter)
sys.exit(0)
###############################################################################
# main
if __name__ == '__main__':
mastodon, mastodon_hostname = mastodon()
feeds_db, feeds_db_user, feeds_url = db_config()
api_key, api_key_secret, access_token, access_token_secret = twitter_config()
logged_in = False
try:
newsfeeds = feedparser.parse(feeds_url)
except:
print(newsfeeds.status)
sys.exit(0)
for entry in newsfeeds.entries:
publish = False
with_images = False
is_reply = False
title = entry['summary']
id = entry['id']
link = entry['link']
toot_id = link.rsplit('/')[4]
tweet_id = get_tweet_id(toot_id)
if tweet_id == 0:
publish = True
reply_id = mastodon.status(toot_id).in_reply_to_id
if reply_id != None:
is_reply = True
tweet_id = get_tweet_id(reply_id)
if len(entry.links) >= 2:
with_images = True
images_list = []
images = len(entry.links) - 1
i = 0
while i < images:
image_url = entry.links[i+1].href
image_filename = write_image(image_url)
images_list.append(image_filename)
i += 1
###########################################################
if publish:
tuit_text = get_toot_text(title)
print("Tooting...")
print(tuit_text)
if not logged_in:
api, logged_in = create_api()
tweet = compose_tweet(tuit_text, with_images, is_reply)
#########################################################
sql_insert_ids = 'INSERT INTO id(toot_id, tweet_id) VALUES (%s,%s)'
conn = None
try:
conn = psycopg2.connect(database = feeds_db, user = feeds_db_user, password = "", host = "/var/run/postgresql", port = "5432")
cur = conn.cursor()
cur.execute(sql_insert_ids, (toot_id, tweet.id))
conn.commit()
cur.close()
except (Exception, psycopg2.DatabaseError) as error:
print(error)
finally:
if conn is not None:
conn.close()
#########################################################
time.sleep(2)
else:
print("Any new feeds")
sys.exit(0)