From 95c8726cbb5869bc714471cf570ef1f3f7c5b2e2 Mon Sep 17 00:00:00 2001 From: Andrew Rogers Date: Thu, 4 Nov 2021 04:28:10 -0500 Subject: [PATCH] added new multimedia superpowers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On startup, the code now initializes MEDIA with suitable media files found in the `media` directory that satisfy Twitter's media file requirements. The `filetype` Python module has been added as a dependency to ensure that files in `media` are what they claim to be. In the bot class I added a new _parse_mention method, broke up the pasta chain builder method, and added a new recursive _tweet_media method. The _parse_mention method had to have some `stupid_emoji` hackery thanks to the way the 🖼 emoji gets encoded in some tweets. A new fetch-media script has been added along with a new util function named `download_tweet_media` See media/README.md for more information on how to use the newly-added multimedia superpowers. --- .gitignore | 3 + Makefile | 1 + media/README.md | 20 ++++++ media/smart/E2ejzt4XwAEV6OA.jpg | Bin 0 -> 12866 bytes pseudbot/bot.py | 113 ++++++++++++++++++++++++++------ pseudbot/media.py | 65 ++++++++++++++++++ pseudbot/util.py | 20 ++++++ requirements.txt | 1 + scripts/fetch-media | 60 +++++++++++++++++ setup.py | 2 +- 10 files changed, 264 insertions(+), 21 deletions(-) create mode 100644 media/README.md create mode 100644 media/smart/E2ejzt4XwAEV6OA.jpg create mode 100644 pseudbot/media.py create mode 100755 scripts/fetch-media diff --git a/.gitignore b/.gitignore index 5e4510e..810b9bc 100644 --- a/.gitignore +++ b/.gitignore @@ -122,6 +122,9 @@ pseud.json # Pseudbot JSON dumps *.dump.json +# Pseudbot test media +media/* + # Pseudbot last id file last_id diff --git a/Makefile b/Makefile index 5f34e87..8876bdf 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ reinstall: readme-preview: pandoc README.md -s -c img/pub.css -o README.html + pandoc media/README.md -s -c $(PWD)/img/pub.css -o media/README.html format: black -v -l 80 pseudbot/* diff --git a/media/README.md b/media/README.md new file mode 100644 index 0000000..e9099fd --- /dev/null +++ b/media/README.md @@ -0,0 +1,20 @@ +# Media +This is your home for Pseudbot's media files. + +## Adding media +Place thematically-related media files together in subdirectories of this one. + +## Using media +Mention Pseudbot in a tweet followed by the 🖼 emoji and a category. If you +wanted to tweet a randomly-selected image from the `smart` directory at the +head of a copypasta response chain (note that your instance of Pseudbot will +have a different handle) you could tweet at the bot: +``` +@pseudbot 🖼 smart +``` + +If you want to only tweet just an image with no attached copypasta chain, add a +second 🖼 emoji at the end of your tweet like so: +``` +@pseudbot 🖼 smart 🖼 +``` diff --git a/media/smart/E2ejzt4XwAEV6OA.jpg b/media/smart/E2ejzt4XwAEV6OA.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7662c7a4ce933d696e84ffed3b0322ae80198538 GIT binary patch literal 12866 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 literal 0 HcmV?d00001 diff --git a/pseudbot/bot.py b/pseudbot/bot.py index ffc4692..f1f2a0a 100644 --- a/pseudbot/bot.py +++ b/pseudbot/bot.py @@ -1,4 +1,5 @@ import random +import re from sys import stderr from textwrap import indent from time import sleep, time @@ -7,6 +8,7 @@ from tweepy.errors import Forbidden, TooManyRequests import typing from .exceptions import * +from .media import MEDIA from .pastas import PASTAS from .util import get_timestamp_s, jdump, log_t_by_sname, surl_prefix @@ -127,7 +129,28 @@ class PseudBot: jdump(jsons, extra_tag=self.screen_name) - def _tweet_pasta(self, id_reply_to: int, pasta: [str]): + def _tweet_media( + self, id_reply_to: int, parent_screen_name: str, media: [str] = [] + ): + _stat = self.last_stat + try: + self.last_stat = self.tapi.update_status_with_media( + "@" + parent_screen_name, + in_reply_to_status_id=id_reply_to, + filename=media.pop(0), + ) + except Forbidden: + return _stat + + if len(media) > 0: + sleep(2) + return self._tweet_media( + self.last_stat.id, self.last_stat.user.screen_name, media + ) + else: + return self.last_stat + + def _tweet_pasta(self, id_reply_to: int, pasta: [str], media: [str] = []): """ Recursively tweet an entire pasta, noodle by noodle:: In this house we stan recursion. @@ -135,16 +158,23 @@ class PseudBot: _stat = self.last_stat try: noodle = pasta.pop(0) - self.last_stat = self.tapi.update_status( - noodle, in_reply_to_status_id=id_reply_to - ) + if len(media) > 0: + self.last_stat = self.tapi.update_status_with_media( + noodle, + in_reply_to_status_id=id_reply_to, + filename=media.pop(0), + ) + else: + self.last_stat = self.tapi.update_status( + noodle, in_reply_to_status_id=id_reply_to + ) self._log_tweet(noodle, self.last_stat) except Forbidden: return _stat if len(pasta) > 0: pasta[0] = "@" + self.last_stat.user.screen_name + " " + pasta[0] sleep(2) - return self._tweet_pasta(self.last_stat.id, pasta) + return self._tweet_pasta(self.last_stat.id, pasta, media) else: return self.last_stat @@ -225,14 +255,7 @@ class PseudBot: for tweet in tweets: self._send_pasta_chain(tweet) - def _send_pasta_chain(self, tweet): - """ - Send a copypasta chain. - """ - pasta = [] - while len(pasta) < 1: - pasta = random.choice(PASTAS) - + def _get_reply_parent(self, tweet) -> (int, str): if tweet.in_reply_to_screen_name is not None: if tweet.in_reply_to_screen_name != self.screen_name: parent_name = tweet.in_reply_to_screen_name @@ -248,13 +271,63 @@ class PseudBot: parent_name = None if tweet.in_reply_to_status_id is not None and parent_name is not None: - pasta[0] = "@" + tweet.in_reply_to_screen_name + " " + pasta[0] - self.last_stat = self._tweet_pasta( - tweet.in_reply_to_status_id, pasta - ) + reply_to_screen_name = tweet.in_reply_to_screen_name + parent_id = tweet.in_reply_to_status_id else: - pasta[0] = "@" + tweet.user.screen_name + " " + pasta[0] - self.last_stat = self._tweet_pasta(tweet.id, pasta) + reply_to_screen_name = tweet.user.screen_name + parent_id = tweet.id + + return (parent_id, reply_to_screen_name) + + def _parse_mention(self, tweet): + """ + Parse commands in tweet and do something + """ + words = re.split(r'[\s.;\-():"]+', tweet.text) + media = [] + do_pasta = True + + stupid_emoji = "🖼" + b"\xef\xb8\x8f".decode() + if stupid_emoji in words or "🖼" in words: + for i in range(len(words)): + if words[i] in ("🖼", stupid_emoji): + try: + media_category = words[i + 1] + i += 1 + except IndexError: + do_pasta = False + break + + if media_category in MEDIA: + media.append(random.choice(MEDIA[media_category])) + + if len(media) == 0: + media = None + + (parent_id, parent_screen_name) = self._get_reply_parent(tweet) + + if do_pasta is True: + pasta = self._make_pasta_chain(parent_screen_name) + self._tweet_pasta(parent_id, pasta, media) + elif len(media) > 0: + self._tweet_media(parent_id, parent_screen_name, media) + else: + print( + '[WARN]: Unable to parse tweet: "{}"'.format(tweet.text), + file=stderr, + ) + + def _make_pasta_chain(self, parent_screen_name: str) -> [str]: + """ + Send a copypasta chain. + """ + pasta = [] + while len(pasta) < 1: + pasta = random.choice(PASTAS) + + pasta[0] = "@" + parent_screen_name + " " + pasta[0] + + return pasta def _reply_mentions(self): """ @@ -269,7 +342,7 @@ class PseudBot: self.last_id = max(tweet.id, self.last_id) - self._send_pasta_chain(tweet) + self._parse_mention(tweet) if self.last_stat is not None: print("Finished chain with {}".format(self.last_stat.id)) diff --git a/pseudbot/media.py b/pseudbot/media.py new file mode 100644 index 0000000..0a48c23 --- /dev/null +++ b/pseudbot/media.py @@ -0,0 +1,65 @@ +from filetype import guess +from os import listdir +import os.path as op + + +def validate_img_size(media_path: str) -> bool: + img_sz = op.getsize(media_path) + + if img_sz > 0 and img_sz <= 5242880: + return True + else: + return False + + +def validate_vid_size(media_path: str) -> bool: + vid_sz = op.getsize(media_path) + + if vid_sz > 0 and vid_sz <= 1073741824: + return True + else: + return False + + +def validate_media(media_path: str) -> bool: + kind = guess(media_path) + if kind is None: + return False + + if kind.extension in ("jpg", "jpeg") and kind.mime == "image/jpeg": + return validate_img_size(media_path) + elif kind.extension == "png" and kind.mime == "image/png": + return validate_img_size(media_path) + if kind.extension == "gif" and kind.mime == "image/gif": + return validate_img_size(media_path) + elif kind.extension == "mp4" and kind.mime == "video/mp4": + return validate_vid_size(media_path) + elif kind.extension == "mov" and kind.mime == "video/quicktime": + return validate_vid_size(media_path) + else: + return False + + +def get_media() -> dict: + media = {} + + media_prefix = op.abspath("media") + for cat in listdir("media"): + fullcat = op.join(media_prefix, cat) + + if op.isdir(fullcat): + items = [] + for itm in listdir(fullcat): + fullitm = op.join(fullcat, itm) + + if op.isfile(fullitm): + if validate_media(fullitm) is True: + items.append(fullitm) + + if len(items) > 0: + media[cat] = items + + return media + + +MEDIA = get_media() diff --git a/pseudbot/util.py b/pseudbot/util.py index 8d579e6..f4174d6 100644 --- a/pseudbot/util.py +++ b/pseudbot/util.py @@ -1,5 +1,7 @@ import inspect import json as j +from os.path import basename +import requests from time import time import typing @@ -37,3 +39,21 @@ def log_t_by_sname(tweet): surl_prefix(tweet.user.screen_name) + str(tweet.id), ) ) + + +def download_tweet_media(tweet: dict): + if "extended_entities" in tweet: + try: + media = tweet["extended_entities"]["media"] + except KeyError: + return + + for item in media: + dl_url = item["media_url_https"] + r = requests.get(dl_url, stream=True) + if r.status_code == 200: + filename = basename(dl_url) + print('[MEDIA]: Saving media to "{}"'.format(filename)) + with open(filename, mode="wb") as f: + for chunk in r.iter_content(1024): + f.write(chunk) diff --git a/requirements.txt b/requirements.txt index 1f2e2f2..b625bbf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ +filetype tweepy requests[socks] diff --git a/scripts/fetch-media b/scripts/fetch-media new file mode 100755 index 0000000..b245c76 --- /dev/null +++ b/scripts/fetch-media @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +import argparse +import json as j +from pseudbot.util import download_tweet_media +from sys import argv as ARGV +import typing + + +def parse_args(args: [str], name: str): + parser = argparse.ArgumentParser(prog=name) + + parser.add_argument( + "json_dump", + type=argparse.FileType("r"), + help="JSON File containing a Twitter info dictionary dump", + ) + + return parser.parse_args(args=args) + + +if __name__ == "__main__": + prog_name = ARGV.pop(0) + print( + "" + + " ,▄▄▄▄▄,\n" + + " ▄▄ ▄███████████▄▄▄▄█▀\n" + + " ▐███▌ ,█████████████████▄▄▄\n" + + " ▐██████▄ ███████████████████▀\n" + + " ██████████▌▄, █████████████████\n" + + " ▓█████████████████████████████████\n" + + " ▐██████████████████████████████████▓\n" + + " ██████████████████████████████████\n" + + " ▀███████████████████████████████▌\n" + + " ,▐▓███████████████████████████▌\n" + + " ████████████████████████████▀\n" + + " ╙█████████████████████████`\n" + + " `▀▓██████████████████▀\n" + + " ,▄▓██████████████████▓└\n" + + "`▀██████████████████████▀└\n" + + " ╙▀▌▓████████▓▌▀└\n" + + " _ _\n" + + " _ __ ___ ___ __| (_) __ _\n" + + " | '_ ` _ \ / _ \/ _` | |/ _` |\n" + + " | | | | | | __/ (_| | | (_| |\n" + + " |_| |_| |_|\___|\__,_|_|\__,_|\n" + + " _\n" + + " __| |_ _ _ __ ___ _ __ ___ _ __\n" + + " / _` | | | | '_ ` _ \| '_ \ / _ \ '__|\n" + + "| (_| | |_| | | | | | | |_) | __/ |\n" + + " \__,_|\__,_|_| |_| |_| .__/ \___|_|\n" + + " |_|\n" + ) + + opts = parse_args(args=ARGV, name=prog_name) + + tweets = j.loads(opts.json_dump.read()) + for tweet in tweets: + download_tweet_media(tweet) + + opts.json_dump.close() diff --git a/setup.py b/setup.py index b675f5c..def5fad 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ except ImportError: # don't care, so I do this: pipmain(["install", "-r", "requirements.txt"]) -scripts = ["scripts/pseudbot"] +scripts = ["scripts/pseudbot", "scripts/fetch-media"] setup( name="pseudbot",