Skip to content

Commit

Permalink
Merge branch 'feature/twitter' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
ralphbean committed Nov 15, 2012
2 parents 2c47005 + 09f3358 commit f355d93
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ dist
_build
html-output
htmldoc
_tweet-real.py
4 changes: 4 additions & 0 deletions doc/commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ fedmsg-irc
~~~~~~~~~~
.. autofunction:: fedmsg.commands.ircbot.ircbot

fedmsg-tweet
~~~~~~~~~~
.. autofunction:: fedmsg.commands.tweet.tweet

fedmsg-gateway
~~~~~~~~~~~~~~
.. autofunction:: fedmsg.commands.gateway.gateway
Expand Down
60 changes: 60 additions & 0 deletions doc/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,66 @@ Glossary of Configuration Values
body will be discarded and not echoed into ``#fedora-fedmsg``. This is
an area that could use some improvement.

tweet_endpoints
``list`` - A list of twitter/statusnet configuration dicts. This is the
primary way of configuring the ``fedmsg-tweet`` bot implemented in
:func:`fedmsg.commands.tweet.tweet`.

Each dict contains a number of possible options. Take the following
example:

>>> tweet_endpoints=[
... tweet_settings=dict(
... base_url="http://api.twitter.com",
... consumer_key="123456789ABCDEF",
... consumer_secret="123456789ABCDEF",
... access_token_key="12345678ABCDEF",
... access_token_secret="1234567ABCDEF",
... ),
... dict(
... base_url="http://identi.ca/api",
... consumer_key="12345676ABCDEF",
... consumer_secret="12345678ABCDEF",
... access_token_key="12355ABCEEF",
... access_token_secret="123456ABCDEF",
... ),
... ],

The ``base_url`` entry specifies which service to use. The other
options are all oauth credentials.

See https://dev.twitter.com/docs/auth/tokens-devtwittercom about getting
credentials for twitter.com. You can get all four authn values from
their site.

Statusnet is a bit more tricky. You'll need to get your
``consumer_key`` and ``consumer_secret`` yourself from http://identi.ca/
and then perform the "oauth dance" with `this python script
<https://gist.github.com/4070630>`_ in order to get your
``access_token_key`` and ``access_token_secret``.

bitly_settings
``dict`` - A dictionary containing credentials to shorten links against
http://bit.ly/. It must contain values for ``api_user`` and ``api_key``
which can be obtained from http://bit.ly/

This is used primarily for :func:`fedmsg.commands.tweet.tweet` but could
in theory be used elsewhere (like in
:func:`fedmsg.commands.ircbot.ircbot`)

tweet_hibernate_duration
``float`` - A number of seconds that :func:`fedmsg.commands.tweet.tweet`
should go to sleep if it encounters a rate limit error from either
statusnet or twitter.com. Set this relatively high, multiple minutes
(120 or 180) since you don't want to exhaust your allowance.
There is a daily limit of 1,000 messages. See http://bit.ly/W6agqr
for more information.

tweet_intermessage_pause
``float`` - A number of seconds that :func:`fedmsg.commands.tweet.tweet`
should go to sleep inbetween every message it posts. Set this
relatively low to 0.5 or 1.

zmq_enabled
``bool`` - A value that must be true. It is present solely
for compatibility/interoperability with `moksha
Expand Down
29 changes: 29 additions & 0 deletions fedmsg.d/tweet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
config = dict(
tweet_intermessage_pause=1, # seconds
tweet_hibernate_duration=60 * 3, # seconds
tweet_endpoints=[
# Actually using twitter.com is unlikely due to their daily
# tweet limit.
#dict(
# base_url="http://api.twitter.com",
# consumer_key="get",
# consumer_secret="these",
# access_token_key="from",
# access_token_secret="dev.twitter.com",
#),

# However, statusnet seems to be more permissive.
dict(
base_url="http://identi.ca/api",
consumer_key="get this from",
consumer_secret="http://identi.ca/",
access_token_key="generate these with",
access_token_secret="https://gist.github.com/4070630",
),
],
bitly_settings=dict(
api_user="get this from",
api_key="http://bit.ly/"
),
)

109 changes: 109 additions & 0 deletions fedmsg/commands/tweet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# This file is part of fedmsg.
# Copyright (C) 2012 Red Hat, Inc.
#
# fedmsg is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# fedmsg is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with fedmsg; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors: Ralph Bean <rbean@redhat.com>
#

import time

import twitter as twitter_api
import bitlyapi

import fedmsg
import fedmsg.text
from fedmsg.commands import command


@command(name="fedmsg-tweet", extra_args=[], daemonizable=True)
def tweet(**kw):
""" Rebroadcast messages to twitter and statusnet
New values in the fedmsg configuration are needed for this to work. Lists
and dicts of authentication credentials such as:
- :term:`tweet_endpoints`
- :term:`bitly_settings`
And scalars to help with rate limiting such as:
- :term:`tweet_hibernate_duration`
- :term:`tweet_intermessage_pause`
"""

# First, sanity checking.
if not kw.get('tweet_endpoints', None):
raise ValueError("Not configured to tweet.")

# Boilerplate..
kw['publish_endpoint'] = None
kw['name'] = 'relay_inbound'
kw['mute'] = True

# Set up fedmsg
fedmsg.init(**kw)
fedmsg.text.make_processors(**kw)

# Set up twitter and statusnet.. multiple accounts if configured
settings = kw.get('tweet_endpoints', [])
apis = [twitter_api.Api(**endpoint) for endpoint in settings]

# Set up bitly
settings = kw['bitly_settings']
bitly = bitlyapi.BitLy(
settings['api_user'],
settings['api_key'],
)

# How long to sleep if we spew too fast.
hibernate_duration = kw['tweet_hibernate_duration']
# Sleep a second or two inbetween messages to try and avoid the hibernate
intermessage_pause = kw['tweet_intermessage_pause']

def _post_to_api(api, message):
try:
api.PostUpdate(message)
except Exception as e:
if 'Too many notices too fast;' in str(e):
# Cool our heels then try again.
print "Sleeping for", hibernate_duration
time.sleep(hibernate_duration)
_post_to_api(api, message)
elif 'json decoding' in str(e):
# Let it slide ... no idea what this one is.
pass
elif 'duplicate' in str(e):
# Let it slide ...
pass
else:
raise

for name, ep, topic, msg in fedmsg.tail_messages(**kw):
message = fedmsg.text.msg2subtitle(msg, **kw)
link = fedmsg.text.msg2link(msg, **kw)

if link:
link = bitly.shorten(longUrl=link)['url']
message = (message[:138] + " ")[:139 - len(link)] + link
else:
message = message[:140]

print("Tweeting %r" % message)
for api in apis:
_post_to_api(api, message)

time.sleep(intermessage_pause)
87 changes: 87 additions & 0 deletions init.d/fedmsg-tweet.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/bin/bash
# fedmsg-tweet - This init script runs the FedMsg Twitter bot
#
# chkconfig: - 25 85
# description: Enabled the fedmsg tweet daemon
# processname: fedmsg-tweet
# config: /etc/fedmsg.d/*
# pidfile: /var/run/fedmsg/fedmsg-tweet.pid

### BEGIN INIT INFO
# Provides: fedmsg-tweet
# Required-Start: $local_fs $network
# Required-Stop: $local_fs $network
# Default-Start:
# Default-Stop: 0 1 6
# Short-Description: start or stop the fedmsg-tweet
# Description: Starts a fedmsg-hub with the tweetbot enabled.
### END INIT INFO

# Source function library.
. /etc/init.d/functions

PROG=fedmsg-tweet
USER=fedmsg
PIDFILE=/var/run/fedmsg/$PROG.pid
OPTIONS=--daemon
SUBSYS=/var/lock/subsys/$PROG

start() {
echo -n "Starting FedMsg Twitter bot: "
if [ -f $PIDFILE.lock ]; then
echo FedMsg Twitter bot already running
exit 2;
fi

if [ ! -d /var/run/fedmsg ]; then
mkdir /var/run/fedmsg
chown $USER:$USER /var/run/fedmsg
fi

daemon --user $USER $PROG $OPTIONS
RETVAL=$?
echo

if [ $RETVAL -eq 0 ]; then
success
touch $SUBSYS
else
failure
fi
}

stop() {
echo -n $"Stopping FedMsg Twitter bot: "
killproc -p ${PIDFILE} $PROG
echo
rm -f ${SUBSYS}
RETVAL=$?
echo
}

case "$1" in
start)
start
;;
stop)
stop
;;
status)
if [ -f $PIDFILE ]; then
echo $"FedMsg Twitter bot is running."
RETVAL=0
else
echo $"FedMsg Twitter bot is not running."
RETVAL=3
fi
;;
restart)
stop
start
;;
*)
echo "Usage: {start|stop|status|reload|restart}"
exit 1
;;
esac
exit $?
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
"fedmsg-config=fedmsg.commands.config:config",
"fedmsg-irc=fedmsg.commands.ircbot:ircbot",
"fedmsg-collectd=fedmsg.commands.collectd:collectd",
"fedmsg-tweet=fedmsg.commands.tweet:tweet",
],
'moksha.consumer': [
"fedmsg-dummy=fedmsg.consumers.dummy:DummyConsumer",
Expand Down

0 comments on commit f355d93

Please sign in to comment.