"""JARVIS Twitter sync.""" import asyncio from datetime import datetime, timedelta from logging import Logger import tweepy from dis_snek import Snake from jarvis_core.db import q from jarvis_core.db.models import TwitterAccount, TwitterFollow from jarvis_tasks.config import TaskConfig config = TaskConfig.from_yaml() async def twitter(bot: Snake, logger: Logger) -> None: """ Sync tweets in the background. Args: bot: Snake instance logger: Global logger """ auth = tweepy.AppAuthHandler(config.twitter["consumer_key"], config.twitter["consumer_secret"]) api = tweepy.API(auth) while True: # Only check once a minute await asyncio.sleep(60) accounts = TwitterAccount.find() accounts_to_delete = [] # Go through all actively followed accounts async for account in accounts: # Check if account needs updated (handle changes) if account.last_sync + timedelta(hours=1) <= datetime.utcnow(): logger.debug(f"Account {account.handle} out of sync, updating") user = api.get_user(id=account.twitter_id) account.update(q(handle=user.screen_name, last_sync=datetime.utcnow())) # Get new tweets if tweets := api.user_timeline(id=account.twitter_id, since_id=account.last_tweet): tweets = sorted(tweets, key=lambda x: x.id) follows = TwitterFollow.find(q(twitter_id=account.twitter_id)) follows_to_delete = [] num_follows = 0 # Go through follows and send tweet if necessary async for follow in follows: num_follows += 1 guild = await bot.fetch_guild(follow.guild_id) if not guild: logger.warning(f"Follow {follow.id}'s guild no longer exists, deleting") follows_to_delete.append(follow) continue channel = await guild.fetch_channel(follow.channel_id) if not channel: logger.warning(f"Follow {follow.id}'s channel no longer exists, deleting") follows_to_delete.append(follow) continue for tweet in tweets: retweet = "retweeted_status" in tweet.__dict__ if retweet and not follow.retweets: continue timestamp = int(tweet.created_at.timestamp()) url = f"https://twitter.com/{account.handle}/status/{tweet.id}" mod = "re" if retweet else "" await channel.send( f"`@{account.handle}` {mod}tweeted this at : {url}" ) # Delete invalid follows for follow in follows_to_delete: await follow.delete() if num_follows == 0: accounts_to_delete.append(account) else: newest = tweets[0] account.update(q(last_tweet=newest.id)) await account.commit() # Delete invalid accounts (no follows) for account in accounts_to_delete: logger.info(f"Account {account.handle} has no followers, removing") await account.delete()