#!/usr/bin/env python # -*- coding: utf-8 -*- # # Twitter 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 from six.moves.configparser import ConfigParser import zulip VERSION = "0.9" CONFIGFILE = os.path.expanduser("~/.zulip_twitterrc") def write_config(config, since_id, user): # type: (ConfigParser, int, int) -> None config.set('twitter', 'since_id', since_id) config.set('twitter', 'user_id', user) with open(CONFIGFILE, 'wb') as configfile: config.write(configfile) parser = optparse.OptionParser(r""" %prog --user foo@example.com --api-key 0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5 --twitter-id twitter_handle --site=https://zulip.example.com Slurp tweets on your timeline into a specific zulip stream. Run this on your personal machine. Your API key and twitter id are revealed 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. Depends on: https://github.com/bear/python-twitter version 3.1 (`pip install python-twitter`) """) parser.add_option('--twitter-id', help='Twitter username to poll for new tweets from"', metavar='URL') parser.add_option('--stream', help='Default zulip stream to write tweets to') parser.add_option('--limit-tweets', default=15, type='int', help='Maximum number of tweets to push at once') parser.add_option_group(zulip.generate_option_group(parser)) (options, args) = parser.parse_args() if not options.twitter_id: parser.error('You must specify --twitter-id') try: config = 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 or not consumer_secret or not access_token_key or not access_token_secret: parser.error("Please provide a ~/.zulip_twitterrc") 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() try: since_id = config.getint('twitter', 'since_id') except six.moves.configparser.NoOptionError: since_id = -1 try: user_id = config.get('twitter', 'user_id') except six.moves.configparser.NoOptionError: user_id = options.twitter_id client = zulip.Client( email=options.zulip_email, api_key=options.zulip_api_key, site=options.zulip_site, client="ZulipTwitter/" + VERSION, verbose=True) if since_id < 0 or options.twitter_id != user_id: # 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.GetUserTimeline(screen_name=options.twitter_id, count=5) else: # We have a saved last id, so insert all newer tweets into the zulip stream statuses = api.GetUserTimeline(screen_name=options.twitter_id, since_id=since_id) for status in statuses[::-1][:options.limit_tweets]: composed = "%s (%s)" % (status.user.name, status.user.screen_name) message = { "type": "stream", "to": [options.stream], "subject": composed, "content": status.text, } 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, user_id)