diff --git a/pyproject.toml b/pyproject.toml index 159c85e23..8b5aed39e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,6 @@ module = [ "slack_sdk.*", "sleekxmpp.*", "trac.*", - "twitter.*", "wit.*", ] ignore_missing_imports = true diff --git a/zulip/integrations/twitter/requirements.txt b/zulip/integrations/twitter/requirements.txt deleted file mode 100644 index b67d14b5a..000000000 --- a/zulip/integrations/twitter/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -python-twitter >= 1.0 diff --git a/zulip/integrations/twitter/twitter-bot b/zulip/integrations/twitter/twitter-bot deleted file mode 100755 index ef42ac557..000000000 --- a/zulip/integrations/twitter/twitter-bot +++ /dev/null @@ -1,251 +0,0 @@ -#!/usr/bin/env python3 - -# Twitter integration for Zulip - -import argparse -import os -import sys -from configparser import ConfigParser, NoOptionError, NoSectionError - -import zulip - -VERSION = "0.9" -CONFIGFILE = os.path.expanduser("~/.zulip_twitterrc") -INSTRUCTIONS = r""" -twitter-bot --config-file=~/.zuliprc --search="@nprnews,quantum physics" - -Send Twitter tweets to a Zulip stream. - -Depends on: https://github.com/bear/python-twitter version 3.1 - -To use this script: - -0. Use `pip install python-twitter` to install `python-twitter` -1. Set up Twitter authentication, as described below -2. Set up a Zulip bot user and download its `.zuliprc` config file to e.g. `~/.zuliprc` -3. Subscribe the bot to the stream that will receive Twitter updates (default stream: twitter) -4. Test the script by running it manually, like this: - -twitter-bot --config-file= --search="" -or -twitter-bot --config-file= --twitter-name="" - -- optional - Exclude any terms or users by using the flags `--exluded-terms` or `--excluded-users`: - -twitter-bot --config-file= --search="" --excluded-users="test-username,other-username" -or -twitter-bot --config-file= --twitter-name="" --excluded-terms="test-term,other-term" - -5. Configure a crontab entry for this script. A sample crontab entry -that will process tweets every 5 minutes is: - -*/5 * * * * /usr/local/share/zulip/integrations/twitter/twitter-bot [options] - -== Setting up Twitter authentications == - -Run this on a personal or trusted machine, because your API key is -visible to local users through the command line or config file. - -This bot uses OAuth to authenticate with Twitter. Please create a -~/.zulip_twitterrc with the following contents: - -[twitter] -consumer_key = -consumer_secret = -access_token_key = -access_token_secret = - -In order to obtain a consumer key & secret, you must register a -new application under your Twitter account: - -1. Go to http://dev.twitter.com -2. Log in -3. In the menu under your username, click My Applications -4. Create a new application - -Make sure to go the application you created and click "create my -access token" as well. Fill in the values displayed. -""" - - -def write_config(config: ConfigParser, configfile_path: str) -> None: - with open(configfile_path, "w") as configfile: - config.write(configfile) - - -parser = zulip.add_default_arguments(argparse.ArgumentParser("Fetch tweets from Twitter.")) -parser.add_argument( - "--instructions", - action="store_true", - help="Show instructions for the twitter bot setup and exit", -) -parser.add_argument( - "--limit-tweets", default=15, type=int, help="Maximum number of tweets to send at once" -) -parser.add_argument("--search", dest="search_terms", help="Terms to search on", action="store") -parser.add_argument( - "--stream", - dest="stream", - help="The stream to which to send tweets", - default="twitter", - action="store", -) -parser.add_argument( - "--twitter-name", dest="twitter_name", help='Twitter username to poll new tweets from"' -) -parser.add_argument("--excluded-terms", dest="excluded_terms", help="Terms to exclude tweets on") -parser.add_argument("--excluded-users", dest="excluded_users", help="Users to exclude tweets on") - -opts = parser.parse_args() - -if opts.instructions: - print(INSTRUCTIONS) - sys.exit() - -if all([opts.search_terms, opts.twitter_name]): - parser.error("You must only specify either a search term or a username.") -if opts.search_terms: - client_type = "ZulipTwitterSearch/" - CONFIGFILE_INTERNAL = os.path.expanduser("~/.zulip_twitterrc_fetchsearch") -elif opts.twitter_name: - client_type = "ZulipTwitter/" - CONFIGFILE_INTERNAL = os.path.expanduser("~/.zulip_twitteruserrc_fetchuser") -else: - parser.error("You must either specify a search term or a username.") - -try: - config = ConfigParser() - config.read(CONFIGFILE) - config_internal = ConfigParser() - config_internal.read(CONFIGFILE_INTERNAL) - - consumer_key = config.get("twitter", "consumer_key") - consumer_secret = config.get("twitter", "consumer_secret") - access_token_key = config.get("twitter", "access_token_key") - access_token_secret = config.get("twitter", "access_token_secret") -except (NoSectionError, NoOptionError): - parser.error("Please provide a ~/.zulip_twitterrc") - -if not all([consumer_key, consumer_secret, access_token_key, access_token_secret]): - parser.error("Please provide a ~/.zulip_twitterrc") - -try: - since_id = config_internal.getint("twitter", "since_id") -except (NoOptionError, NoSectionError): - since_id = 0 -try: - previous_twitter_name = config_internal.get("twitter", "twitter_name") -except (NoOptionError, NoSectionError): - previous_twitter_name = "" -try: - previous_search_terms = config_internal.get("twitter", "search_terms") -except (NoOptionError, NoSectionError): - previous_search_terms = "" - -try: - import twitter -except ImportError: - parser.error("Please install python-twitter") - -api = twitter.Api( - consumer_key=consumer_key, - consumer_secret=consumer_secret, - access_token_key=access_token_key, - access_token_secret=access_token_secret, -) - -user = api.VerifyCredentials() - -if not user.id: - print( - "Unable to log in to twitter with supplied credentials. Please double-check and try again" - ) - sys.exit(1) - -client = zulip.init_from_options(opts, client=client_type + VERSION) - -if opts.search_terms: - search_query = " OR ".join(opts.search_terms.split(",")) - if since_id == 0 or opts.search_terms != previous_search_terms: - # No since id yet, fetch the latest and then start monitoring from next time - # Or, a different user id is being asked for, so start from scratch - # Either way, fetch last 5 tweets to start off - statuses = api.GetSearch(search_query, count=5) - else: - # We have a saved last id, so insert all newer tweets into the zulip stream - statuses = api.GetSearch(search_query, since_id=since_id) -elif opts.twitter_name: - if since_id == 0 or opts.twitter_name != previous_twitter_name: - # Same strategy as for search_terms - statuses = api.GetUserTimeline(screen_name=opts.twitter_name, count=5) - else: - statuses = api.GetUserTimeline(screen_name=opts.twitter_name, since_id=since_id) - -excluded_terms = opts.excluded_terms.split(",") if opts.excluded_terms else [] - -excluded_users = opts.excluded_users.split(",") if opts.excluded_users else [] - -for status in statuses[::-1][: opts.limit_tweets]: - # Check if the tweet is from an excluded user - exclude = False - for user in excluded_users: - if user == status.user.screen_name: - exclude = True - break - if exclude: - continue # Continue with the loop for the next tweet - - # https://twitter.com/eatevilpenguins/status/309995853408530432 - composed = f"{status.user.name} ({status.user.screen_name})" - url = f"https://twitter.com/{status.user.screen_name}/status/{status.id}" - # This contains all strings that could have caused the tweet to match our query. - text_to_check = [status.text, status.user.screen_name] - text_to_check.extend(url.expanded_url for url in status.urls) - - text_to_check = [text.lower() for text in text_to_check] - - # Check that the tweet doesn't contain any terms that - # are supposed to be excluded - for term in excluded_terms: - if any(term.lower() in text for text in text_to_check): - exclude = True # Tweet should be excluded - break - if exclude: - continue # Continue with the loop for the next tweet - - if opts.search_terms: - search_term_used = None - for raw_term in opts.search_terms.split(","): - # Remove quotes from phrase: - # "Zulip API" -> Zulip API - term = raw_term[1:-1] if term.startswith('"') and term.endswith('"') else raw_term - if any(term.lower() in text for text in text_to_check): - search_term_used = term - break - # For some reason (perhaps encodings or message tranformations we - # didn't anticipate), we don't know what term was used, so use a - # default. - if not search_term_used: - search_term_used = "mentions" - subject = search_term_used - elif opts.twitter_name: - subject = composed - - message = {"type": "stream", "to": [opts.stream], "subject": subject, "content": url} - - ret = client.send_message(message) - - if ret["result"] == "error": - # If sending failed (e.g. no such stream), abort and retry next time - print("Error sending message to zulip: %s" % ret["msg"]) - break - else: - since_id = status.id - -if "twitter" not in config_internal.sections(): - config_internal.add_section("twitter") -config_internal.set("twitter", "since_id", str(since_id)) -config_internal.set("twitter", "search_terms", str(opts.search_terms)) -config_internal.set("twitter", "twitter_name", str(opts.twitter_name)) - -write_config(config_internal, CONFIGFILE_INTERNAL)