Created
May 2, 2022 16:14
-
-
Save Angelin01/694353ac5e446fa002e516ca7370c938 to your computer and use it in GitHub Desktop.
Twitter 2 Discord
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Old script I made and had laying around, it's not really adequate corpared to something I'd create | |
# these days, but it works | |
# Simple script to poll Twitter for tweets and route them to a Discord webhook | |
# Run it periodically, using something like Cron | |
# Requires the following environment variables, can also be read from a file `.env` in the same | |
# directory as this one: | |
# USERNAME: Case insensitive twitter handle without the @ | |
# LAST_TWEET: File name to store the last tweet ID, must have read/write permissions | |
# BEARER: Bearer token for twitter API | |
# WEBHOOK: Discord's webhook URL | |
from os.path import join, dirname | |
from time import sleep | |
from os import getenv | |
from dotenv import load_dotenv | |
from requests import post, get | |
from json import loads | |
from sys import exit | |
def get_tweets(user, bearer, since_id=None, count=20, include_rts=False): | |
""" | |
Gets tweets from user API | |
:param user: The handle of the tweeter user, without the @ | |
:param bearer: The twitter bearer token, load from environment | |
:param since_id: Returns results with an ID greater than this paramter | |
:param count: Maximum number of tweets to get | |
:param include_rts: Include retweets if true | |
:return: A list of tweets in JSON format | |
""" | |
payload = { | |
'screen_name': user, | |
'count': count, | |
'include_rts': include_rts, | |
'trim_user': False, | |
'tweet_mode': 'extended' | |
} | |
if since_id: | |
payload['since_id'] = since_id | |
response = get('https://api.twitter.com/1.1/statuses/user_timeline.json', | |
headers={'Authorization': 'Bearer ' + bearer}, | |
params=payload) | |
if response.status_code != 200: | |
raise ValueError('Non 200 response from API, something is wrong with the request') | |
return loads(response.text) | |
def build_payload(tweet): | |
""" | |
Builds a payload (to use as json) to send to Discord | |
:param tweet: The tweet object properly loaded with Python types and formats | |
:return: A dictionary in JSON format to use with the POST request as a payload | |
""" | |
from time import strftime, strptime | |
payload = { | |
'username': 'JefTweets', | |
'embeds': [ | |
{ | |
'title': '{user} tweeted'.format(user=tweet['user']['name']), | |
'url': 'https://twitter.com/{user}/status/{id}'.format(user=tweet['user']['screen_name'], id=tweet['id']), | |
'thumbnail': { | |
'url': tweet['user']['profile_image_url_https'] | |
}, | |
'timestamp': strftime('%Y-%m-%dT%H:%M:%SZ', strptime(tweet['created_at'], '%a %b %d %H:%M:%S %z %Y')), | |
'description': tweet['full_text'] | |
} | |
] | |
} | |
if 'extended_entities' in tweet and tweet['extended_entities']['media']: | |
payload['embeds'][0]['image'] = { | |
'url': tweet['extended_entities']['media'][0]['media_url_https'] | |
} | |
if len(tweet['extended_entities']['media']) > 1: | |
payload['embeds'][0]['footer'] = { | |
'text': 'There are more images in the tweet' | |
} | |
return payload | |
def send_to_discord(user, last_tweet, bearer, webhook): | |
""" | |
Sends to a Discord webhook tweets from someone | |
:param user: The handle of the tweeter user, without the @ | |
:param last_tweet: The file in which to store the last tweet's ID. If it doesn't exist, it will be created | |
:param webhook: The Discord webhook URL | |
:param bearer: The twitter bearer token, load from environment | |
:return: Exit code, 0 is fine, 1 indicates some form of error in the request | |
""" | |
# Get the last known tweet's ID from the file | |
try: | |
with open(last_tweet) as last_tweet_file: | |
last_tweet_id = last_tweet_file.readline().strip() or None | |
except FileNotFoundError: | |
last_tweet_id = None | |
# If there's no ID, simply get the tweets and set the last one as the last known ID | |
if not last_tweet_id: | |
try: | |
for tweet in get_tweets(user, bearer): | |
if not tweet['retweeted']: | |
with open(last_tweet, 'w') as last_tweet_file: | |
last_tweet_file.write(str(tweet['id'])) | |
return 0 | |
except ValueError: | |
return 1 | |
try: | |
yet_to_post = get_tweets(user, bearer, since_id=last_tweet_id) | |
except ValueError: | |
return 1 | |
# If the list is empty, there are no new IDs, so just return | |
if not yet_to_post: | |
return 0 | |
# Send the Tweets to Discord | |
# Last one in list is the oldest, so send it reversed | |
for tweet in reversed(yet_to_post): | |
discord_response = post(webhook, json=build_payload(tweet)) | |
sleep(0.5) | |
# Write the new last known ID | |
with open(last_tweet, 'w') as last_tweet_file: | |
last_tweet_file.write(str(yet_to_post[0]['id'])) | |
return 0 | |
if __name__ == '__main__': | |
load_dotenv(join(dirname(__file__), '.env')) | |
USERNAME = getenv('USERNAME') | |
LAST_TWEET = getenv('LAST_TWEET_FILE') | |
BEARER = getenv('TWITTER_BEARER') | |
WEBHOOK = getenv('WEBHOOK') | |
exit(send_to_discord(USERNAME, LAST_TWEET, BEARER, WEBHOOK)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment