From 69b73260e79fafff9d84f59ee182330807baac22 Mon Sep 17 00:00:00 2001 From: Wardyn Date: Thu, 26 Jan 2023 19:40:26 -0800 Subject: [PATCH] Remove mastodon.py dependency --- .gitignore | 1 + autodeny.py | 108 +++++++++++++++++++----------------------- config_template.json | 6 +++ fedisearch.py | 57 +++++++--------------- generate_config.py | 34 +++++++++++++ highestpost.py | 98 ++++++++++++++++++++------------------ import_following.py | 45 ++++++++++++++++++ notification.mp3 | Bin 0 -> 12280 bytes notification_sound.py | 30 ++++++++++++ 9 files changed, 234 insertions(+), 145 deletions(-) create mode 100644 .gitignore create mode 100644 config_template.json create mode 100644 generate_config.py create mode 100644 import_following.py create mode 100644 notification.mp3 create mode 100644 notification_sound.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0cffcb3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +config.json \ No newline at end of file diff --git a/autodeny.py b/autodeny.py index 10165a8..fe8077e 100644 --- a/autodeny.py +++ b/autodeny.py @@ -1,13 +1,21 @@ #!/usr/bin/python3.8 -from mastodon import Mastodon import sys import os import re -import sys import argparse import time +import json +import requests -parser = argparse.ArgumentParser(description='Remove follow requests that match the following filters:\n1: Has less than n (defaults to 1) posts\n2: Has no profile picture\n3: Has no bio\n\nAdding extra filters is somewhat easy if you are familiar with python as well as the mastodon.py wrapper.', formatter_class=argparse.RawTextHelpFormatter) +parent = os.path.dirname(os.path.realpath(__file__)) +if not os.path.exists(os.path.join(parent, 'config.json')): + generate_config("Wardyn's feditools", "read write follow push") +with open(os.path.join(parent, 'config.json'), 'r') as config_file: + config = json.load(config_file) +session = requests.Session() +session.headers.update({"Authorization" : "Bearer " + config['user_token']}) + +parser = argparse.ArgumentParser(description='Remove follow requests that match the following filters:\n1: Has less than n (defaults to 1) posts\n2: Has no profile picture\n3: Has no bio\n', formatter_class=argparse.RawTextHelpFormatter) parser.add_argument('-t', '--threshold', action='store', type=int, @@ -22,6 +30,13 @@ parser.add_argument('-i', '--instances', default=None, dest='instances' ) +parser.add_argument('-b', '--blank', + action='store', + type=str, + default=None, + help='If your instance has a custom image for blank pfps set, please specify the url to the image with this flag', + dest='custompfp' +) parser.add_argument('-p', '--posts', action='store', type=int, @@ -65,81 +80,56 @@ simulate = args.simulate accept= args.accept loop = args.loop instances = args.instances +custompfp = args.custompfp if instances is not None: instances = instances.split(", ") if type(loop) == int: loop = loop * 60 * 60 -# Ensure Fediverse credentials -parent = os.path.dirname(os.path.realpath(__file__)) -if os.path.exists(os.path.join(parent, '.creds')) == False: - os.mkdir(os.path.join(parent, '.creds')) +blanks = [config['instance'] + '/avatars/original/missing.png', config['instance'] + '/images/avi.png'] +if custompfp: + blanks.append(custompfp) -if os.path.exists(os.path.join(parent, '.creds', 'client.secret')) == False: - instance = input('Please enter your instance: ') - if not instance[:4] == 'http': - instance = 'https://' + instance - Mastodon.create_app('Wardyns fedi tools', api_base_url = instance, to_file = os.path.join(parent, '.creds', 'client.secret')) -mastodon = Mastodon( - client_id = os.path.join(parent, '.creds', 'client.secret'), -) - -if os.path.exists(os.path.join(parent, '.creds', 'user.secret')) == False: - username = input('Enter your username: ') - password = input('Enter your password: ') - mastodon.log_in(username=username, password=password, scopes=['read', 'write'], to_file=os.path.join(parent, '.creds', 'user.secret')) - -mastodon = Mastodon( - access_token = os.path.join(parent, '.creds', 'user.secret') -) - -def filterposts(min, request): - posts = request['statuses_count'] +def filterposts(min, follow_request): + posts = follow_request['statuses_count'] if posts >= min: return(True) else: return(False) -def filterbio(request): - if request['note'] == '

': +def filterbio(follow_request): + if follow_request['note'] == '

': return(False) else: return(True) -def filterpfp(request): - if request['avatar'] == mastodon.api_base_url + '/avatars/original/missing.png' or request['avatar'] == mastodon.api_base_url + '/images/avi.png': +def filterpfp(follow_request): + if follow_request['avatar'] in blanks: return(False) else: return(True) while True: denied=[] accepted=[] - while True: - try: - requests = mastodon.follow_requests() - break - except: - continue - if len(requests) > 0: - for request in requests: - #print(request['fqn']) - #print(request['id']) - postcheck = filterposts(minposts, request) - pfpcheck = filterpfp(request) - biocheck = filterbio(request) - #print('Has more than one post?: ' + str(postcheck)) - #print('Has a pfp?: ' + str(pfpcheck)) - #print('Has a bio?: ' + str(biocheck)) - following = mastodon.account_relationships(request)[0]['following'] + + follow_requests = session.get(config['instance'] + '/api/v1/follow_requests', params={'limit':80}).json() + + if len(follow_requests) > 0: + for follow_request in follow_requests: + postcheck = filterposts(minposts, follow_request) + pfpcheck = filterpfp(follow_request) + biocheck = filterbio(follow_request) + + following = session.get(config['instance'] + '/api/v1/accounts/relationships', params={'id':follow_request['id']}).json()[0]['following'] blockedinstance = False if instances is not None: - blockedinstance = request['fqn'].split('@')[-1] in instances + blockedinstance = follow_request['fqn'].split('@')[-1] in instances if postcheck + biocheck + pfpcheck < threshold and following == False or blockedinstance == True: - denied.append(request) + denied.append(follow_request) elif accept == True: - accepted.append(request) + accepted.append(follow_request) print('DENIED: ') - for request in denied: - print(request['fqn']) + for follow_request in denied: + print(follow_request['fqn']) confirm = None if auto == False: while True: @@ -151,16 +141,16 @@ while True: continue break if auto == True or confirm == 'y': - for request in denied: + for follow_request in denied: if simulate: - print('This is where ' + request['fqn'] + ' would be rejected') + print('This is where ' + follow_request['fqn'] + ' would be rejected') else: - mastodon.follow_request_reject(request['id']) - for request in accepted: + session.post(config['instance'] + '/api/v1/follow_requests/' + follow_request['id'] + '/reject') + for follow_request in accepted: if simulate: - print('This is where ' + request['fqn'] + ' would be accepted') + print('This is where ' + follow_request['fqn'] + ' would be accepted') else: - mastodon.follow_request_authorize(request['id']) + session.post(config['instance'] + '/api/v1/follow_requests/' + follow_request['id'] + '/authorize') if loop == None: break else: diff --git a/config_template.json b/config_template.json new file mode 100644 index 0000000..71552b7 --- /dev/null +++ b/config_template.json @@ -0,0 +1,6 @@ +{ + "instance":"", + "client_id":"", + "client_secret":"", + "user_token":"" +} diff --git a/fedisearch.py b/fedisearch.py index 486801a..e4a7ab4 100644 --- a/fedisearch.py +++ b/fedisearch.py @@ -1,9 +1,19 @@ # Import modules -from mastodon import Mastodon import os import html2text from argparse import ArgumentParser -import os +import json +import requests + +parent = os.path.dirname(os.path.realpath(__file__)) + +if not os.path.exists(os.path.join(parent, 'config.json')): + generate_config("Wardyn's feditools", "read write follow push") +with open(os.path.join(parent, 'config.json'), 'r') as config_file: + config = json.load(config_file) + +session = requests.Session() +session.headers.update({"Authorization" : "Bearer " + config['user_token']}) # Initialize arguments parser = ArgumentParser(description='Search a fedi users posts for a specific word or phrase') @@ -36,30 +46,6 @@ dms = args.dms account = args.account pattern = args.pattern -# Ensure Fediverse credentials -parent = os.path.dirname(os.path.realpath(__file__)) -if os.path.exists(os.path.join(parent, '.creds')) == False: - os.mkdir(os.path.join(parent, '.creds')) - -if os.path.exists(os.path.join(parent, '.creds', 'client.secret')) == False: - instance = input('Please enter your instance: ') - if not instance[:4] == 'http': - instance = 'https://' + instance - Mastodon.create_app('Wardyns fedi tools', api_base_url = instance, to_file = os.path.join(parent, '.creds', 'client.secret')) - -mastodon = Mastodon( - client_id = os.path.join(parent, '.creds', 'client.secret'), -) - -if os.path.exists(os.path.join(parent, '.creds', 'user.secret')) == False: - username = input('Enter your username: ') - password = input('Enter your password: ') - mastodon.log_in(username=username, password=password, scopes=['read', 'write'], to_file=os.path.join(parent, '.creds', 'user.secret')) - -mastodon = Mastodon( - access_token = os.path.join(parent, '.creds', 'user.secret') -) - # Main block if case == False: pattern = pattern.lower() @@ -70,8 +56,8 @@ htmlconvert.body_width = 0 if account[0] == '@': account = account[1:] if len(account.split('@')) == 1: - account = account + '@' + mastodon.api_base_url.split('/')[2] -accountlist = mastodon.account_search(account) + account = account + '@' + config['instance'].split('/')[2] +accountlist = session.get(config['instance'] + '/api/v2/search', params={'q':account}).json()['accounts'] for curaccount in accountlist: print(curaccount['fqn']) @@ -85,20 +71,11 @@ accid = account['id'] print('Searching for posts including "' + pattern + '" from user ' + account['fqn']) print('\n---\n') while True: - while True: - try: - statuses = mastodon.account_statuses(accid, max_id=oldest_status_id, limit=1000) - break - except IndexError: - break - except: - pass - if len(statuses) == 0: - break + statuses = session.get(config['instance'] + '/api/v1/accounts/' + accid + '/statuses', params={'max_id':oldest_status_id, 'limit':40}).json() oldest_status_id = statuses[-1]['id'] for status in statuses: if status['reblog'] == None: - if status['visibility'] == 'direct' and dms == False: + if status['visibility'] == 'direct' and not dms: continue content = str(htmlconvert.handle(status['content'])) if case == False: @@ -107,4 +84,6 @@ while True: print(content) print('\nlink: ' + status['url']) print('\n---\n') + if len(statuses) < 40: + break print('Finished searching') \ No newline at end of file diff --git a/generate_config.py b/generate_config.py new file mode 100644 index 0000000..208ad3b --- /dev/null +++ b/generate_config.py @@ -0,0 +1,34 @@ +import os +import requests +import json +from urllib.parse import urlencode + +def generate_config(app_name, scopes): + #Ensure Credentials + parent = os.path.dirname(os.path.realpath(__file__)) + with open(os.path.join(parent, 'config_template.json'), 'r') as template: + config = json.load(template) + + #Create app + instance = input('Please enter your instance: ') + if not instance[:4] == 'http': + instance = 'https://' + instance + + response = requests.post(instance + '/api/v1/apps', data={'client_name':app_name,'scopes':scopes, 'redirect_uris':'urn:ietf:wg:oauth:2.0:oob'}).json() + + client_id = config['client_id'] = response['client_id'] + client_secret = config['client_secret'] = response['client_secret'] + config['instance'] = instance + + #Log in to user account + + print(instance + '/oauth/authorize?', urlencode({'response_type':'code', 'client_id':client_id, 'redirect_uri':'urn:ietf:wg:oauth:2.0:oob', 'scope':scopes})) + code = input("To generate a token to access your account, " + app_name + " needs an authorization code. Please authorize using the link above and enter the code it provides you \nCode: ") + response = requests.post(instance + '/oauth/token', data={'grant_type':'authorization_code', 'code':code, 'client_id':client_id, 'client_secret':client_secret, 'redirect_uri':'urn:ietf:wg:oauth:2.0:oob', 'scope':scopes}) + config['user_token'] = response.json()['access_token'] + + with open(os.path.join(parent, 'config.json'), 'w') as config_file: + config_file.write(json.dumps(config)) + +if __name__ == "__main__": + generate_config("test_app", "read") \ No newline at end of file diff --git a/highestpost.py b/highestpost.py index e7df895..a00ceb4 100644 --- a/highestpost.py +++ b/highestpost.py @@ -1,55 +1,59 @@ #!/usr/bin/python3.8 -from mastodon import Mastodon import os import sys import html2text +import json +import requests +parent = os.path.dirname(os.path.realpath(__file__)) -if os.path.exists('./.creds/') == False: - os.mkdir('./.creds') -if os.path.exists('./.creds/client.secret') == False: - instance = input('Please enter your instance: ') - if not instance[:4] == 'http': - instance = 'https://' + instance - Mastodon.create_app('Wardyns fedi tools', api_base_url = instance, to_file = './.creds/client.secret') +if not os.path.exists(os.path.join(parent, 'config.json')): + generate_config("Wardyn's feditools", "read write follow push") +with open(os.path.join(parent, 'config.json'), 'r') as config_file: + config = json.load(config_file) +session = requests.Session() +session.headers.update({"Authorization" : "Bearer " + config['user_token']}) -mastodon = Mastodon( - client_id = './.creds/client.secret', -) - -if os.path.exists('./.creds/user.secret') == False: - username = input('Enter your username: ') - password = input('Enter your password: ') - mastodon.log_in(username=username, password=password, scopes=['read', 'write'], to_file='./.creds/user.secret') - - -mastodon = Mastodon( - access_token = './.creds/user.secret' -) -if len(sys.argv) < 2: +if len(sys.argv) != 2: print('Expected 1 argument (account)') quit() -else: - highestpost = None - oldest_status_id = None - htmlconvert = html2text.HTML2Text() - htmlconvert.ignore_links = True - htmlconvert.body_width = 0 - account = sys.argv[1] - accid = mastodon.account_search(account)[0]['id'] - while True: - statuses = mastodon.account_statuses(accid, max_id=oldest_status_id, limit=1000) - try: - oldest_status_id = statuses[-1]['id'] - except IndexError: - print('Reached end of posts') - quit() - for status in statuses: - statusscore = status['reblogs_count'] + status['favourites_count'] - if status['reblogged'] == False: - if highestpost == None or statusscore > highestpost['score']: - highestpost = {'score' : status['reblogs_count'] + status['favourites_count'], 'post' : status} - content = str(htmlconvert.handle(status['content'])) - print(content) - print('Favorites: ' + str(status['favourites_count']) + ' Boosts: ' + str(status['reblogs_count'])) - print('link: ' + status['url']) - print('-------') \ No newline at end of file + +highestpost = None +oldest_status_id = None +htmlconvert = html2text.HTML2Text() +htmlconvert.ignore_links = True +htmlconvert.body_width = 0 +account = sys.argv[1] + +if account[0] == '@': + account = account[1:] +if len(account.split('@')) == 1: + account = account + '@' + config['instance'].split('/')[-1] + +searchlist = session.get(config['instance'] + '/api/v2/search?', params={'q':account, 'type':'accounts'}).json()['accounts'] +acc_id = None +for user in searchlist: + if user['fqn'].lower() == account.lower(): + acc_id = user['id'] + acc_name = user['fqn'] + break +if not acc_id: + print("Could not find user: " + account) + quit() + +#accid = session.get(config['instance'] + '/api/v2/search?', params={'q':account, 'type':'accounts'}).json()['accounts'][0]['id'] +while True: + statuses = session.get(config['instance'] + '/api/v1/accounts/' + acc_id + '/statuses', params={"max_id":oldest_status_id, "limit":40, "exclude_reblogs":True}).json() + try: + oldest_status_id = statuses[-1]['id'] + except IndexError: + print('Reached end of posts') + quit() + for status in statuses: + status_score = status['reblogs_count'] + status['favourites_count'] + if highestpost == None or status_score > highestpost['score']: + highestpost = {'score' : status['reblogs_count'] + status['favourites_count'], 'post' : status} + content = str(htmlconvert.handle(status['content'])) + print(content) + print('Favorites: ' + str(status['favourites_count']) + ' Boosts: ' + str(status['reblogs_count'])) + print('link: ' + status['url']) + print('-------') \ No newline at end of file diff --git a/import_following.py b/import_following.py new file mode 100644 index 0000000..98dbfa8 --- /dev/null +++ b/import_following.py @@ -0,0 +1,45 @@ +import requests +import json +import os +from argparse import ArgumentParser, FileType +from generate_config import generate_config + +parent = os.path.dirname(os.path.realpath(__file__)) + +# Initialize arguments +parser = ArgumentParser(description='Import follows from a csv file') +parser.add_argument('file', + type=open, + help='csv file containing users to follow', +) +args = parser.parse_args() +csv = args.file.read() + +if not os.path.exists(os.path.join(parent, 'config.json')): + generate_config("Wardyn's feditools", "read write follow push") +with open(os.path.join(parent, 'config.json'), 'r') as config_file: + config = json.load(config_file) + +session = requests.Session() +session.headers.update({"Authorization" : "Bearer " + config['user_token']}) + + +acclist = csv.split('\n') +for account in acclist: + searchlist = session.get(config['instance'] + '/api/v2/search?', params={'q':account, 'type':'accounts'}).json() + searchlist = searchlist['accounts'] + found = False + for user in searchlist: + if user['fqn'] == account: + account = user + found = True + break + if found == False: + print("Could not find user: " + account) + continue + relationship = account['pleroma']['relationship'] + if relationship['following'] == True: + print('Already following: ' + account['fqn']) + continue + print("Following: " + account['fqn']) + session.post(config['instance'] + '/api/v1/accounts/' + account['id'] + '/follow') diff --git a/notification.mp3 b/notification.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..bf9c3c1aaff40dea76ad82e3e853d2d32382bcea GIT binary patch literal 12280 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 literal 0 HcmV?d00001 diff --git a/notification_sound.py b/notification_sound.py new file mode 100644 index 0000000..34152a7 --- /dev/null +++ b/notification_sound.py @@ -0,0 +1,30 @@ +import websocket +import os +from playsound import playsound +from generate_config import generate_config +import json + +parent = os.path.dirname(os.path.realpath(__file__)) + +if not os.path.exists(os.path.join(parent, 'config.json')): + generate_config("Wardyn's feditools", "read write follow push") +with open(os.path.join(parent, 'config.json'), 'r') as config_file: + config = json.load(config_file) + +sound = os.path.join(parent, 'notification.mp3') + +def on_message(ws, message): + print('Received notification') + playsound(sound) +def on_open(ws): + print('Websocket opened') +def on_close(ws): + print('Websocket closed, attempting to reconnect') + connect_websocket() + +def connect_websocket(): + ws = websocket.WebSocketApp("wss://" + config['instance'].split('/')[-1] + "/api/v1/streaming?access_token=" + config['user_token'] + "&stream=user:notification", + on_message = on_message, on_open = on_open, on_close = on_close, on_error=on_close) + ws.run_forever() + +connect_websocket() \ No newline at end of file