commit e256ad251cd8d339b9946ca856f726c84a72e7f8 Author: Wardyn Date: Wed Nov 23 01:07:54 2022 -0800 set up a proper git repository diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7a3094a --- /dev/null +++ b/LICENSE @@ -0,0 +1,11 @@ +DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +Version 2, December 2004 + +Copyright (C) 2004 Sam Hocevar + +Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed. + +DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c00683c --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# wardyns-feditools + + + +Collection of simple tools to add functionality to the fediverse + +## Installation +All these tools are designed to run in the same directory in order to prevent generating multiple access tokens. Installation is simple, download master branch as zip, extract into a folder of your choice, then, in that directory, run: + + pip install -r requirements.txt +**Note:** These programs were written and tested in python 3.8 +## Usage +All the programs are created with a built in help command, simply run [script] -h +## fedisearch.py +fedisearch is a tool that allows you to search through a users profile for a specific keyword or phrase. + +Limitations: + + - Very slow. Takes longer the the older a post is + - Does not currently support regular expressions + + + + ## autodeny.py +A simple script to automatically deny follow requests that do not pass certain tests. Those tests are: + 1. The user has more than 1 post (configurable) + 2. The user has a pfp other than the default one their instance assigns. + 3. The user has text within their bio + +It will not deny a follow request if you are following the user + +You can also specify instances to block all follow requests from + diff --git a/autodeny.py b/autodeny.py new file mode 100644 index 0000000..10165a8 --- /dev/null +++ b/autodeny.py @@ -0,0 +1,167 @@ +#!/usr/bin/python3.8 +from mastodon import Mastodon +import sys +import os +import re +import sys +import argparse +import time + +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) +parser.add_argument('-t', '--threshold', + action='store', + type=int, + help='The amount of filters the request has to pass to not be automatically declined, defaults to 1 (setting this to a number higher than the amount of availible filters will deny every request in the list)', + default=1, + dest='threshold' + ) +parser.add_argument('-i', '--instances', + action='store', + type=str, + help='block follow requests from these instances (comma seperated list)', + default=None, + dest='instances' +) +parser.add_argument('-p', '--posts', + action='store', + type=int, + help='The minimum number of posts a user must have to pass the posts filter (does not include boosts), defaults to 1', + default=1, + dest='minposts' + ) +parser.add_argument('-y', '--yes', + action='store_const', + const=True, + help='Skips asking for confirmation before denying requests, use this if you want to automate this script.', + default=False, + dest='auto' + ) +parser.add_argument('-s', '--simulate', + action='store_const', + const=True, + help='Simulates the process without modifying anything', + default=False, + dest='simulate' + ) +parser.add_argument('-a', '--accept', + action='store_const', + const=True, + help='Automatically accept requests that arent rejected', + default=False, + dest='accept' +) +parser.add_argument('-l', '--loop', + action='store', + type=int, + help='Loop command every x hours', + default=None, + dest='loop' +) +args = parser.parse_args() +threshold = args.threshold +minposts = args.minposts +auto = args.auto +simulate = args.simulate +accept= args.accept +loop = args.loop +instances = args.instances +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')) + +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'] + if posts >= min: + return(True) + else: + return(False) +def filterbio(request): + if 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': + 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'] + blockedinstance = False + if instances is not None: + blockedinstance = request['fqn'].split('@')[-1] in instances + if postcheck + biocheck + pfpcheck < threshold and following == False or blockedinstance == True: + denied.append(request) + elif accept == True: + accepted.append(request) + print('DENIED: ') + for request in denied: + print(request['fqn']) + confirm = None + if auto == False: + while True: + confirm = input('Deny these requests? (y/n): ') + if confirm == 'n': + pass + elif not confirm == 'y': + print('Not recognized, try again') + continue + break + if auto == True or confirm == 'y': + for request in denied: + if simulate: + print('This is where ' + request['fqn'] + ' would be rejected') + else: + mastodon.follow_request_reject(request['id']) + for request in accepted: + if simulate: + print('This is where ' + request['fqn'] + ' would be accepted') + else: + mastodon.follow_request_authorize(request['id']) + if loop == None: + break + else: + time.sleep(loop) \ No newline at end of file diff --git a/fedisearch.py b/fedisearch.py new file mode 100644 index 0000000..486801a --- /dev/null +++ b/fedisearch.py @@ -0,0 +1,110 @@ +# Import modules +from mastodon import Mastodon +import os +import html2text +from argparse import ArgumentParser +import os + +# Initialize arguments +parser = ArgumentParser(description='Search a fedi users posts for a specific word or phrase') +parser.add_argument('-c', '--case', + action='store_const', + const=True, + help='Match string as case sensitive', + default=False, + dest='case' +) +parser.add_argument('-d', '--dms', + action='store_const', + const=True, + help='Include DMs', + default=False, + dest='dms' +) +parser.add_argument('account', + type=str, + help='Account to search through', +) +parser.add_argument('pattern', + type=str, + help='Pattern to search for', + +) +args = parser.parse_args() +case = args.case +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() +oldest_status_id = None +htmlconvert = html2text.HTML2Text() +htmlconvert.ignore_links = True +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) + +for curaccount in accountlist: + print(curaccount['fqn']) + if curaccount['fqn'].lower() == account.lower(): + account = curaccount + break +if type(account) is str: + print('Could not find an account with the search term: ' + account) + exit() +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 + oldest_status_id = statuses[-1]['id'] + for status in statuses: + if status['reblog'] == None: + if status['visibility'] == 'direct' and dms == False: + continue + content = str(htmlconvert.handle(status['content'])) + if case == False: + content = content.lower() + if pattern in content: + print(content) + print('\nlink: ' + status['url']) + print('\n---\n') +print('Finished searching') \ No newline at end of file diff --git a/highestpost.py b/highestpost.py new file mode 100644 index 0000000..e7df895 --- /dev/null +++ b/highestpost.py @@ -0,0 +1,55 @@ +#!/usr/bin/python3.8 +from mastodon import Mastodon +import os +import sys +import html2text + +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') + +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: + 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 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ce663de Binary files /dev/null and b/requirements.txt differ