2016-04-07 15:03:22 +02:00
|
|
|
#!/usr/bin/env python
|
2013-10-02 17:10:46 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
|
|
|
# Twitter integration for Zulip
|
|
|
|
#
|
2014-02-04 20:15:02 +01:00
|
|
|
# Copyright © 2014 Zulip, Inc.
|
2013-10-02 17:10:46 +02:00
|
|
|
#
|
|
|
|
# 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.
|
|
|
|
|
2016-03-10 17:15:34 +01:00
|
|
|
from __future__ import print_function
|
2013-01-28 18:09:43 +01:00
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
import optparse
|
2016-12-30 03:07:46 +01:00
|
|
|
from six.moves.configparser import ConfigParser, NoSectionError, NoOptionError
|
2013-01-28 18:09:43 +01:00
|
|
|
|
2013-08-07 17:51:03 +02:00
|
|
|
import zulip
|
2013-12-05 23:42:33 +01:00
|
|
|
VERSION = "0.9"
|
2013-08-07 18:30:44 +02:00
|
|
|
CONFIGFILE = os.path.expanduser("~/.zulip_twitterrc")
|
2013-03-08 17:24:52 +01:00
|
|
|
|
2013-01-28 18:09:43 +01:00
|
|
|
def write_config(config, since_id, user):
|
2016-12-29 09:13:10 +01:00
|
|
|
# type: (ConfigParser, int, int) -> None
|
2013-01-28 18:09:43 +01:00
|
|
|
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"""
|
|
|
|
|
2016-11-16 18:35:36 +01:00
|
|
|
%prog --user foo@example.com --api-key 0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5 --twitter-id twitter_handle --site=https://zulip.example.com
|
2013-01-28 18:09:43 +01:00
|
|
|
|
2013-08-07 18:30:44 +02:00
|
|
|
Slurp tweets on your timeline into a specific zulip stream.
|
2013-01-28 18:09:43 +01:00
|
|
|
|
2016-07-02 19:53:19 +02:00
|
|
|
Run this on your personal machine. Your API key and twitter id
|
|
|
|
are revealed to local users through the command line or config
|
|
|
|
file.
|
2013-01-28 18:09:43 +01:00
|
|
|
|
2016-07-02 19:53:19 +02:00
|
|
|
This bot uses OAuth to authenticate with twitter. Please create a
|
|
|
|
~/.zulip_twitterrc with the following contents:
|
2013-01-28 18:09:43 +01:00
|
|
|
|
|
|
|
[twitter]
|
|
|
|
consumer_key =
|
|
|
|
consumer_secret =
|
|
|
|
access_token_key =
|
|
|
|
access_token_secret =
|
|
|
|
|
2016-07-02 19:53:19 +02:00
|
|
|
In order to obtain a consumer key & secret, you must register a
|
|
|
|
new application under your twitter account:
|
2013-01-28 18:09:43 +01:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2016-07-02 19:53:19 +02:00
|
|
|
Make sure to go the application you created and click "create my
|
|
|
|
access token" as well. Fill in the values displayed.
|
2013-01-28 18:09:43 +01:00
|
|
|
|
2016-10-18 08:01:13 +02:00
|
|
|
Depends on: https://github.com/bear/python-twitter version 3.1
|
|
|
|
(`pip install python-twitter`)
|
2013-01-28 18:09:43 +01:00
|
|
|
""")
|
|
|
|
|
|
|
|
parser.add_option('--twitter-id',
|
2016-12-11 14:30:45 +01:00
|
|
|
help='Twitter username to poll for new tweets from"',
|
|
|
|
metavar='URL')
|
2013-01-28 18:09:43 +01:00
|
|
|
parser.add_option('--stream',
|
2016-12-11 14:30:45 +01:00
|
|
|
help='Default zulip stream to write tweets to')
|
2013-01-28 18:09:43 +01:00
|
|
|
parser.add_option('--limit-tweets',
|
2016-12-11 14:30:45 +01:00
|
|
|
default=15,
|
|
|
|
type='int',
|
|
|
|
help='Maximum number of tweets to push at once')
|
2013-01-28 18:09:43 +01:00
|
|
|
|
2013-08-07 17:51:03 +02:00
|
|
|
parser.add_option_group(zulip.generate_option_group(parser))
|
2013-01-28 18:09:43 +01:00
|
|
|
(options, args) = parser.parse_args()
|
|
|
|
|
|
|
|
if not options.twitter_id:
|
|
|
|
parser.error('You must specify --twitter-id')
|
|
|
|
|
|
|
|
try:
|
2016-12-29 09:13:10 +01:00
|
|
|
config = ConfigParser()
|
2013-01-28 18:09:43 +01:00
|
|
|
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')
|
2016-12-30 03:07:46 +01:00
|
|
|
except (NoSectionError, NoOptionError):
|
2016-12-01 06:20:27 +01:00
|
|
|
parser.error("Please provide a ~/.zulip_twitterrc")
|
2013-01-28 18:09:43 +01:00
|
|
|
|
|
|
|
if not consumer_key or not consumer_secret or not access_token_key or not access_token_secret:
|
2016-12-01 06:20:27 +01:00
|
|
|
parser.error("Please provide a ~/.zulip_twitterrc")
|
2013-01-28 18:09:43 +01:00
|
|
|
|
|
|
|
try:
|
|
|
|
import twitter
|
|
|
|
except ImportError:
|
2013-03-08 17:24:52 +01:00
|
|
|
parser.error("Please install twitter-python")
|
2013-01-28 18:09:43 +01:00
|
|
|
|
|
|
|
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()
|
|
|
|
|
2016-10-18 08:01:13 +02:00
|
|
|
if not user.id:
|
2016-03-10 17:15:34 +01:00
|
|
|
print("Unable to log in to twitter with supplied credentials. Please double-check and try again")
|
2013-01-28 18:09:43 +01:00
|
|
|
sys.exit()
|
|
|
|
|
|
|
|
try:
|
|
|
|
since_id = config.getint('twitter', 'since_id')
|
2016-12-30 03:07:46 +01:00
|
|
|
except NoOptionError:
|
2013-01-28 18:09:43 +01:00
|
|
|
since_id = -1
|
|
|
|
|
|
|
|
try:
|
|
|
|
user_id = config.get('twitter', 'user_id')
|
2016-12-30 03:07:46 +01:00
|
|
|
except NoOptionError:
|
2013-01-28 18:09:43 +01:00
|
|
|
user_id = options.twitter_id
|
|
|
|
|
2013-08-07 17:51:03 +02:00
|
|
|
client = zulip.Client(
|
2014-02-21 20:11:29 +01:00
|
|
|
email=options.zulip_email,
|
|
|
|
api_key=options.zulip_api_key,
|
|
|
|
site=options.zulip_site,
|
2013-12-06 23:50:55 +01:00
|
|
|
client="ZulipTwitter/" + VERSION,
|
2013-01-28 18:09:43 +01:00
|
|
|
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
|
2016-11-17 09:13:08 +01:00
|
|
|
statuses = api.GetUserTimeline(screen_name=options.twitter_id, count=5)
|
2013-01-28 18:09:43 +01:00
|
|
|
else:
|
2013-08-07 18:30:44 +02:00
|
|
|
# We have a saved last id, so insert all newer tweets into the zulip stream
|
2016-11-17 09:13:08 +01:00
|
|
|
statuses = api.GetUserTimeline(screen_name=options.twitter_id, since_id=since_id)
|
2013-01-28 18:09:43 +01:00
|
|
|
|
|
|
|
for status in statuses[::-1][:options.limit_tweets]:
|
2016-10-18 08:01:13 +02:00
|
|
|
composed = "%s (%s)" % (status.user.name, status.user.screen_name)
|
2013-01-28 18:09:43 +01:00
|
|
|
message = {
|
|
|
|
"type": "stream",
|
|
|
|
"to": [options.stream],
|
|
|
|
"subject": composed,
|
2016-10-18 08:01:13 +02:00
|
|
|
"content": status.text,
|
2013-01-28 18:09:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ret = client.send_message(message)
|
|
|
|
|
|
|
|
if ret['result'] == 'error':
|
|
|
|
# If sending failed (e.g. no such stream), abort and retry next time
|
2016-03-10 17:15:34 +01:00
|
|
|
print("Error sending message to zulip: %s" % ret['msg'])
|
2013-01-28 18:09:43 +01:00
|
|
|
break
|
|
|
|
else:
|
2016-10-18 08:01:13 +02:00
|
|
|
since_id = status.id
|
2013-01-28 18:09:43 +01:00
|
|
|
|
|
|
|
write_config(config, since_id, user_id)
|