class Bot(object): def __init__(self, access_token, since_at, instance_url, msg_template='', debug_mode=True, account_id=1): self.instance_url = instance_url self.msg_template = Template(msg_template) self.client = Mastodon( access_token=access_token, api_base_url=self.instance_url ) self.since_at = since_at self.debug_mode = debug_mode self.log = logging.getLogger() if self.debug_mode: self.log.setLevel(logging.DEBUG) else: self.log.setLevel(logging.INFO) ch = logging.StreamHandler(sys.stdout) self.log.addHandler(ch) self.account_id = account_id self.until = pytz.utc.localize(datetime.now() - timedelta(days=1)) self.until = self.until.replace(hour=23, minute=59, second=59) def _should_get_msg(self, account): # return account.url.startswith(self.instance_url) and account.created_at >= self.since_at and account.created_at <= self.until and not account.locked self.log.debug("self.until") return account.url.startswith(self.instance_url) and account.created_at >= self.since_at and not account.locked def get_users(self): users_to_msg = [] def get_prev(users): followers = self.client.fetch_previous(users) filtered = [follower for follower in followers if self._should_get_msg(follower)] users_to_msg.extend(filtered) # this assumes that followers are returned in descending order by follow date! if len(filtered) == 0: return users_to_msg return get_prev(followers) followers = self.client.account_followers(self.account_id, limit=80) filtered = [follower for follower in followers if self._should_get_msg(follower)] users_to_msg.extend(filtered) if len(filtered) == 0: return users_to_msg return get_prev(followers) def send_msg(self, account): msg = self.msg_template.render(account) msg = msg.replace('\\n', '\n') self.log.debug(msg) if self.debug_mode: return self.client.status_post(msg, visibility='direct') def go(self): users_to_msg = self.get_users() if self.debug_mode: self.log.debug('in debug mode so not actually sending toots, but if I was, I\'d be sending to ' \ '{} accounts'.format(len(users_to_msg))) self.log.debug('here\'s the toots that I would be sending:') for u in users_to_msg: self.send_msg(u) self.log.info('{} toots sent!'. format(len(users_to_msg)))
if j % 100 == 0: consolidate('accounts') consolidate('followers') consolidate('following') try: account = mastodon.account(j) accounts.append(account) with open(pathTo + 'accounts/accounts' + str(j) + '.json', 'w') as accountsFile: json.dump(account, accountsFile, default=datetimeconvert) if account['url'][:len(url)] == url: if len(account) > 0: if account["followers_count"] > 0: followersTemp = getLists(mastodon.account_followers(j)) if account["followers_count"] != len(followersTemp[1]): error.append(['followers', j]) followers.append(followersTemp) if account["following_count"] > 0: followingTemp = getLists(mastodon.account_following(j)) if account["following_count"] != len(followingTemp[1]): error.append(['followiong', j]) following.append(followingTemp) with open(pathTo + 'followers/followers' + str(j) + '.json', 'w') as followersFile: json.dump(followers, followersFile, default=datetimeconvert) with open(pathTo + 'following/following' + str(j) + '.json',
class FediGroupBot: def __init__(self, base_url, access_token, accept_dms, accept_retoots, save_file): self.base_url = base_url self.accept_dms = accept_dms self.accept_retoots = accept_retoots self.save_file = save_file self.masto = Mastodon(debug_requests=False, access_token=access_token, api_base_url=base_url) self.id = self.masto.account_verify_credentials().id self.username = self.masto.account_verify_credentials().username self.group_members = [] self.group_admins = [] def run(self): last_seen_id = 0 if os.path.exists(self.save_file): with open(self.save_file, 'r') as fd: try: last_seen_id = int(fd.readline()) except ValueError: pass while True: notifications = self.masto.notifications(since_id=last_seen_id) if notifications: self.__update_group_members() for notification in sorted(notifications, key=lambda x: x.id): if notification.id > last_seen_id: last_seen_id = notification.id self.__do_action(notification) with open(self.save_file, 'w') as fd: fd.write(str(last_seen_id)) time.sleep(2) def __update_group_members(self): self.group_members, max_id = [], None followers = self.masto.account_followers(self.id, limit=sys.maxsize, max_id=max_id) # NOQA: E501 while len(followers) > 1: self.group_members += [member.acct for member in followers] max_id = followers[-1].id followers = self.masto.account_followers( self.id, limit=sys.maxsize, max_id=max_id) # NOQA: E501 self.group_admins, max_id = [], None following = self.masto.account_following(self.id, limit=sys.maxsize, max_id=max_id) # NOQA: E501 while len(following) > 1: self.group_admins += [member.acct for member in following] max_id = followers[-1].id following = self.masto.account_following( self.id, limit=sys.maxsize, max_id=max_id) # NOQA: E501 def __do_action(self, notification): if notification.type != "mention": return if notification.status.in_reply_to_id is not None: return if self.accept_retoots and notification.status.visibility == "public" \ and notification.status.account.acct in self.group_members: self.masto.status_reblog(notification.status.id) if self.accept_dms and notification.status.visibility == "direct" \ and notification.status.account.acct in self.group_admins: new_status = re.sub("<br />", "\n", notification.status.content) new_status = re.sub("</p><p>", "\n\n", new_status) new_status = re.sub("<.*?>", "", new_status) if new_status.startswith("@" + self.username): new_status = re.sub("@" + self.username, "", new_status) new_status = html.unescape(new_status) media_ids = [] for media in notification.status.media_attachments: response = requests.get(media.url) mime_type = response.headers['Content-Type'] media_data = response.content media_ids.append( self.masto.media_post(media_data, mime_type, description=media.description)) self.masto.status_post( new_status, media_ids=media_ids, sensitive=notification.status.sensitive, visibility="public", spoiler_text=notification.status.spoiler_text)
import os from mastodon import Mastodon fedibird = Mastodon( access_token=os.getenv('MASTODON_TOKEN'), api_base_url='https://fedibird.com', ) mentions = ' '.join( [f'@{follower.acct}' for follower in fedibird.account_followers(77150)]) poll = fedibird.make_poll(options=('21時から参加可能', '22時から参加可能', '23時から参加可能', '行けたら行く'), expires_in=10000) fedibird.status_post( status=f'{mentions}\n #今日のプラベ 如何ですか?', poll=poll, visibility='private', )
if last_id_file.is_file(): with open(last_id_file) as f: last_id = int(f.read()) print('Botvenon is starting...') print('Welcoming any users after id %d' % last_id) mastodon = Mastodon( client_id=CLIENT_ID, client_secret=CLIENT_SECRET, access_token=ACCESS_TOKEN, api_base_url=INSTANCE_BASE) user = mastodon.account_verify_credentials() # Get the current user followers = mastodon.account_followers(user) # Get the latest followers # print( # f'''Followed by: {account.username} # (Acct value: {account.acct}, id: {account.id})''') while True: for account in mastodon.account_followers(user): if is_local_account(account) and account.id > last_id and not account.bot: last_id = account.id with open(last_id_file, 'w+') as f: f.write(str(last_id)) print('Welcoming %s...' % account.username) send_welcome_message(mastodon, account) time.sleep(WAIT_TIME)
def main(): colorama_init() parser = ArgumentParser(description=__doc__, formatter_class=RawDescriptionHelpFormatter) parser.add_argument('--min-activity', dest='min_activity', type=parse_time_ago, default="1y", help=("Remove followings inactive for a given period" " (m for months, y for years, d for days) " "(default: %(default)s)")) parser.add_argument('--target-count', dest='target_count', type=int, help=("Target some following count (will try to stop" " when you have that many followings left)")) parser.add_argument('--unfollow', action='store_true', help="Actually unfollow") parser.add_argument('--followers', action='store_true', help="Instead of removing people you follow, remove " "people who follow YOU") parser.add_argument('--unmutuals', action='store_true', help="Remove people who follow you but that you " "don't follow") parser.add_argument('-v', '--verbose', action='store_true', help="Display more things") args = parser.parse_args() session = requests.Session() mastodon = Mastodon(access_token=settings.ACCESS_TOKEN, api_base_url=settings.API_BASE, ratelimit_method='pace') current_user = mastodon.account_verify_credentials() uid = current_user['id'] if args.unmutuals: args.followers = True if args.followers: followings_count = current_user['followers_count'] else: followings_count = current_user['following_count'] local_count = followings_count goal_msg = "" if args.target_count: goal_msg = "(goal: n>={})".format(args.target_count) now = datetime.now(tz=timezone.utc) def clog(c, s): tqdm.write(c + s + Fore.WHITE + Style.NORMAL) cprint(Fore.GREEN, "Current user: @{} (#{})".format(current_user['username'], uid)) cprint( Fore.GREEN, "{}: {} {}".format("Followers" if args.followers else "Followings", followings_count, goal_msg)) if args.unfollow: cprint(Fore.RED, "Action: unfollow") else: cprint(Fore.YELLOW, "Action: none") followings = None if args.followers: followings = mastodon.account_followers(uid) else: followings = mastodon.account_following(uid) followings = mastodon.fetch_remaining(followings) bar = tqdm(list(followings)) for f in bar: fid = f.get('id') acct = f.get('acct') fullhandle = "@{}".format(acct) if '@' in acct: inst = acct.split('@', 1)[1].lower() else: inst = None if args.target_count is not None and local_count <= args.target_count: clog(Fore.RED + Style.BRIGHT, "{} followings left; stopping".format(local_count)) break title_printed = False def title(): nonlocal title_printed if title_printed: return title_printed = True clog(Fore.WHITE + Style.BRIGHT, "Account: {} (#{})".format(f.get('acct'), fid)) if args.verbose: title() try: bar.set_description(fullhandle.ljust(30, ' ')) act = False if args.unmutuals and inst not in settings.SKIP_INSTANCES: try: relations = mastodon.account_relationships(fid) is_mutual = relations[0]["following"] or relations[0][ "requested"] if not is_mutual: clog(Fore.YELLOW, "- Unmutual ({})".format(relations)) act = True except (UserGone, Error, requests.RequestException) as e: act = False clog(Fore.YELLOW, "- Exception ({})".format(e)) elif args.min_activity and inst not in settings.SKIP_INSTANCES: try: last_toot = get_last_toot(mastodon, fid) if last_toot < now - args.min_activity: # force a cache miss to be Sure last_toot = get_last_toot(mastodon, fid, force=True) if last_toot < now - args.min_activity: act = True msg = "(!)" title() clog(Fore.WHITE, "- Last toot: {} {}".format(last_toot, msg)) else: msg = "(pass)" if args.verbose: clog(Fore.WHITE, "- Last toot: {} {}".format(last_toot, msg)) except UserGone as e: moved = f.get('moved') if moved: # TODO: follow new account and unfollow old act = False title() clog( Fore.YELLOW, "- User moved ({}) [NOT IMPLEMENTED]".format( moved)) else: act = True title() clog(Fore.YELLOW, "- User gone ({})".format(e)) except (Error, requests.RequestException) as e: if inst and inst in settings.ASSUME_DEAD_INSTANCES: act = True title() clog(Fore.YELLOW, "- Instance gone ({})".format(e)) else: raise if act: local_count -= 1 if args.unfollow: if args.followers: clog(Fore.GREEN + Style.BRIGHT, "- Removing follower {}".format(fullhandle)) mastodon.account_block(fid) mastodon.account_unblock(fid) else: clog(Fore.GREEN + Style.BRIGHT, "- Unfollowing {}".format(fullhandle)) mastodon.account_unfollow(fid) else: clog(Fore.GREEN + Style.BRIGHT, "- (not) unfollowing {}".format(fullhandle)) clog(Fore.WHITE, ("- {}/{} followings left".format( local_count, followings_count))) except Exception as e: title() clog(Fore.RED, "- Error: {}".format(str(e)))