Added thread support

merge-requests/1/merge
spla 1 year ago
parent b1f13bb2cb
commit 9e8fa9ccb1
  1. 9
      README.md
  2. 4
      db-setup.py
  3. 190
      mastodon-setup.py
  4. 125
      mastotuit.py

@ -16,8 +16,11 @@ Within Python Virtual Environment:
2. Run `python db-setup.py` to setup and create new Postgresql database and needed table in it and setup your Mastodon's account RSS feed in the format 'https://your.mastodon.server/@your_user.rss'
3. Run `python setup.py` to input and save your Twitter's key and access tokens. You can get your keys and tokens from [Twitter Developer Platform](https://developer.twitter.com/en/apply/user.html)
3. Run `python setup.py` to input and save your Twitter's key and access tokens. You can get your keys and tokens from [Twitter Developer Platform](https://developer.twitter.com/en/apply/user.html)
4. Use your favourite scheduling method to set `python mastotuit.py` to run every minute.
4. Run `python mastodon-setup.py` to setup your Mastodon account access tokens.
29.9.2021 **New Feature** Added support to media files! mastotuit now gets all media files from Mastodon's post (if any) and publish them to Twitter together with your status update.
5. Use your favourite scheduling method to set `python mastotuit.py` to run every minute.
29.9.2021 **New Feature** Added support to media files! mastotuit now gets all media files from Mastodon's post (if any) and publish them to Twitter together with your status update.
7.10.2021 **New Feature** Added thread support! If you create a thread in Mastodon mastotuit will create the same thread on Twitter.

@ -139,6 +139,10 @@ table = "feeds"
sql = "create table "+table+" (link varchar(300) PRIMARY KEY)"
create_table(db, db_user, table, sql)
table = "id"
sql = "create table "+table+" (toot_id bigint PRIMARY KEY, tweet_id bigint)"
create_table(db, db_user, table, sql)
#####################################
print("Done!")

@ -0,0 +1,190 @@
import getpass
from mastodon import Mastodon
from mastodon.Mastodon import MastodonMalformedEventError, MastodonNetworkError, MastodonReadTimeout, MastodonAPIError, MastodonIllegalArgumentError
import fileinput,re
import os
import sys
def create_dir():
if not os.path.exists('secrets'):
os.makedirs('secrets')
def create_file():
if not os.path.exists('secrets/secrets.txt'):
with open('secrets/secrets.txt', 'w'): pass
print(secrets_filepath + " created!")
def create_config():
if not os.path.exists('config'):
os.makedirs('config')
if not os.path.exists(config_filepath):
print(config_filepath + " created!")
with open('config/config.txt', 'w'): pass
def write_params():
with open(secrets_filepath, 'a') as the_file:
print("Writing secrets parameter names to " + secrets_filepath)
the_file.write('uc_client_id: \n'+'uc_client_secret: \n'+'uc_access_token: \n')
def write_config():
with open(config_filepath, 'a') as the_file:
the_file.write('mastodon_hostname: \n')
print("adding parameter name 'mastodon_hostname' to "+ config_filepath)
def read_client_lines(self):
client_path = 'app_clientcred.txt'
with open(client_path) as fp:
line = fp.readline()
cnt = 1
while line:
if cnt == 1:
print("Writing client id to " + secrets_filepath)
modify_file(secrets_filepath, "uc_client_id: ", value=line.rstrip())
elif cnt == 2:
print("Writing client secret to " + secrets_filepath)
modify_file(secrets_filepath, "uc_client_secret: ", value=line.rstrip())
line = fp.readline()
cnt += 1
def read_token_line(self):
token_path = 'app_usercred.txt'
with open(token_path) as fp:
line = fp.readline()
print("Writing access token to " + secrets_filepath)
modify_file(secrets_filepath, "uc_access_token: ", value=line.rstrip())
def read_config_line():
with open(config_filepath) as fp:
line = fp.readline()
modify_file(config_filepath, "mastodon_hostname: ", value=hostname)
def log_in():
error = 0
try:
global hostname
hostname = input("Enter Mastodon hostname: ")
user_name = input("User name, ex. user@" + hostname +"? ")
user_password = getpass.getpass("User password? ")
app_name = input("This app name? ")
Mastodon.create_app(app_name, scopes=["read","write"],
to_file="app_clientcred.txt", api_base_url=hostname)
mastodon = Mastodon(client_id = "app_clientcred.txt", api_base_url = hostname)
mastodon.log_in(
user_name,
user_password,
scopes = ["read", "write"],
to_file = "app_usercred.txt"
)
except MastodonIllegalArgumentError as i_error:
error = 1
if os.path.exists("secrets/secrets.txt"):
print("Removing secrets/secrets.txt file..")
os.remove("secrets/secrets.txt")
if os.path.exists("app_clientcred.txt"):
print("Removing app_clientcred.txt file..")
os.remove("app_clientcred.txt")
sys.exit(i_error)
except MastodonNetworkError as n_error:
error = 1
if os.path.exists("secrets/secrets.txt"):
print("Removing secrets/secrets.txt file..")
os.remove("secrets/secrets.txt")
if os.path.exists("app_clientcred.txt"):
print("Removing app_clientcred.txt file..")
os.remove("app_clientcred.txt")
sys.exit(n_error)
except MastodonReadTimeout as r_error:
error = 1
if os.path.exists("secrets/secrets.txt"):
print("Removing secrets/secrets.txt file..")
os.remove("secrets/secrets.txt")
if os.path.exists("app_clientcred.txt"):
print("Removing app_clientcred.txt file..")
os.remove("app_clientcred.txt")
sys.exit(r_error)
except MastodonAPIError as a_error:
error = 1
if os.path.exists("secrets/secrets.txt"):
print("Removing secrets/secrets.txt file..")
os.remove("secrets/secrets.txt")
if os.path.exists("app_clientcred.txt"):
print("Removing app_clientcred.txt file..")
os.remove("app_clientcred.txt")
sys.exit(a_error)
finally:
if error == 0:
create_dir()
create_file()
write_params()
client_path = 'app_clientcred.txt'
read_client_lines(client_path)
token_path = 'app_usercred.txt'
read_token_line(token_path)
if os.path.exists("app_clientcred.txt"):
print("Removing app_clientcred.txt temp file..")
os.remove("app_clientcred.txt")
if os.path.exists("app_usercred.txt"):
print("Removing app_usercred.txt temp file..")
os.remove("app_usercred.txt")
print("Secrets setup done!\n")
def modify_file(file_name,pattern,value=""):
fh=fileinput.input(file_name,inplace=True)
for line in fh:
replacement=pattern + value
line=re.sub(pattern,replacement,line)
sys.stdout.write(line)
fh.close()
# 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, creating it."%file_path)
log_in()
# 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)
# Returns the parameter from the specified file
def get_hostname( parameter, config_filepath ):
# Check if secrets file exists
if not os.path.isfile(config_filepath):
print("File %s not found, creating it."%config_filepath)
create_config()
# Find parameter in file
with open( config_filepath ) as f:
for line in f:
if line.startswith( parameter ):
return line.replace(parameter + ":", "").strip()
# Cannot find parameter, exit
print(config_filepath + " Missing parameter %s "%parameter)
write_config()
read_config_line()
print("hostname setup done!")
sys.exit(0)
###############################################################################
# main
if __name__ == '__main__':
# 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_hostname("mastodon_hostname", config_filepath) # E.g., mastodon.social

@ -14,6 +14,40 @@ import pdb
logger = logging.getLogger()
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'):
@ -109,12 +143,12 @@ def get_parameter( parameter, file_path ):
if __name__ == '__main__':
#######################################################################
#mastodon, mastodon_hostname = mastodon()
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:
@ -130,11 +164,19 @@ if __name__ == '__main__':
publish = False
with_images = False
is_reply = False
title = entry['summary']
id = entry['id']
link = entry['link']
toot_id = link.rsplit('/')[4]
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
@ -183,7 +225,7 @@ if __name__ == '__main__':
if publish:
soup = BeautifulSoup(title, 'html.parser')
toot_text = soup.get_text()
sub_str = 'http'
@ -218,7 +260,7 @@ if __name__ == '__main__':
if not logged_in:
api, logged_in = create_api()
if len(tuit_text) < 280:
try:
@ -234,17 +276,29 @@ if __name__ == '__main__':
images_id_lst.append(media.media_id)
i += 1
api.update_status(status=tuit_text, media_ids=images_id_lst)
if is_reply:
tweet = api.update_status(status=tuit_text, in_reply_to_status_id=tweet_id, media_ids=images_id_lst)
else:
tweet = api.update_status(status=tuit_text, media_ids=images_id_lst)
else:
api.update_status(tuit_text)
if is_reply:
tweet = api.update_status(status=tuit_text, in_reply_to_status_id=tweet_id)
else:
tweet = api.update_status(tuit_text)
except TweepError as err:
print('\n')
sys.exit(err)
else:
if with_images:
@ -270,16 +324,30 @@ if __name__ == '__main__':
images_id_lst.append(media.media_id)
i += 1
first_tweet = api.update_status(status=tuit_text1)
api.update_status(status=tuit_text2, in_reply_to_status_id=first_tweet.id, media_ids=images_id_lst)
if is_reply:
first_tweet = api.update_status(status=tuit_text1, in_reply_to_status_id=tweet_id)
else:
first_tweet = api.update_status(status=tuit_text1)
tweet = api.update_status(status=tuit_text2, in_reply_to_status_id=first_tweet.id, media_ids=images_id_lst)
else:
first_tweet = api.update_status(tuit_text1)
api.update_status(tuit_text2, in_reply_to_status_id=first_tweet.id)
if is_reply:
first_tweet = api.update_status(tuit_text1, in_reply_to_status_id=tweet_id)
else:
first_tweet = api.update_status(tuit_text1)
tweet = api.update_status(tuit_text2, in_reply_to_status_id=first_tweet.id)
except TweepError as err:
print('\n')
sys.exit(err)
@ -312,6 +380,33 @@ if __name__ == '__main__':
if conn is not None:
conn.close()
insert_line = '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(insert_line, (toot_id, tweet.id))
conn.commit()
cur.close()
except (Exception, psycopg2.DatabaseError) as error:
print(error)
finally:
if conn is not None:
conn.close()
else:
print("Any new feeds")

Loading…
Cancel
Save