#!/usr/bin/env python # -*- coding: utf-8 -*- # # Twitter search integration for Zulip # # Copyright © 2014 Zulip, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function import os import sys import optparse import six.moves.configparser import zulip VERSION = "0.9" CONFIGFILE = os.path.expanduser("~/.zulip_twitterrc") def write_config(config, since_id): if 'search' not in config.sections(): config.add_section('search') config.set('search', 'since_id', since_id) with open(CONFIGFILE, 'wb') as configfile: config.write(configfile) parser = optparse.OptionParser(r""" %prog --user username@example.com --api-key a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5 \ --search="@nprnews,quantum physics" Send Twitter search results 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. Subscribe to the stream that will receive Twitter updates (default stream: twitter) 3. Test the script by running it manually, like this: /usr/local/share/zulip/integrations/twitter/twitter-search-bot \ --search="@nprnews,quantum physics" --site=https://zulip.example.com 4. 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-search-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. """) parser.add_option('--search', dest='search_terms', help='Terms to search on', action='store') parser.add_option('--stream', dest='stream', help='The stream to which to send tweets', default="twitter", action='store') parser.add_option('--limit-tweets', default=15, type='int', help='Maximum number of tweets to send at once') parser.add_option_group(zulip.generate_option_group(parser)) (opts, args) = parser.parse_args() if not opts.search_terms: parser.error('You must specify a search term.') try: config = six.moves.configparser.ConfigParser() config.read(CONFIGFILE) 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 (six.moves.configparser.NoSectionError, six.moves.configparser.NoOptionError): parser.error("Please provide a ~/.zulip_twitterrc") if not (consumer_key and consumer_secret and access_token_key and access_token_secret): parser.error("Please provide a ~/.zulip_twitterrc") try: since_id = config.getint('search', 'since_id') except (six.moves.configparser.NoOptionError, six.moves.configparser.NoSectionError): since_id = 0 try: import twitter except ImportError: parser.error("Please install twitter-python") 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() client = zulip.Client( email=opts.zulip_email, api_key=opts.zulip_api_key, site=opts.zulip_site, client="ZulipTwitterSearch/" + VERSION, verbose=True) search_query = " OR ".join(opts.search_terms.split(",")) statuses = api.GetSearch(search_query, since_id=since_id) for status in statuses[::-1][:opts.limit_tweets]: # https://twitter.com/eatevilpenguins/status/309995853408530432 composed = "%s (%s)" % (status.user.name, status.user.screen_name) url = "https://twitter.com/%s/status/%s" % (status.user.screen_name, status.id) content = status.text search_term_used = None for term in opts.search_terms.split(","): if term.lower() in content.lower(): 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" message = { "type": "stream", "to": [opts.stream], "subject": search_term_used, "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 write_config(config, since_id)