Example #1
0
    def __init__(self, conn, app_secret, account_id):

        lg.info("TwitterWebHooks::__init__()...")

        self.app_secret = app_secret
        self.bot_id = account_id
        self.conn = conn
        self.events_adapter = TwitterWebhookAdapter(self.app_secret, "/webhooks/twitter")
Example #2
0
def test_server_start(mocker):
    # Verify server started with correct params
    adapter = TwitterWebhookAdapter("CONSUMER_SECRET")
    mocker.spy(adapter, 'server')
    adapter.start(port=3000)
    adapter.server.run.assert_called_once_with(debug=False,
                                               host='127.0.0.1',
                                               port=3000)
Example #3
0
def test_invalid_request_signature(client):
    # Verify [package metadata header is set
    adapter = TwitterWebhookAdapter("CONSUMER_SECRET")

    data = bytes(pytest.twitter_like_event_fixture, 'ascii')
    signature = "bad signature"

    with pytest.raises(TwitterWebhookAdapterException) as excinfo:
        res = client.post('/webhooks/twitter',
                          data=data,
                          content_type='application/json',
                          headers={'X-Twitter-Webhooks-Signature': signature})

    assert str(excinfo.value) == 'Invalid request signature'
from TwitterAPI import TwitterAPI
from twitterwebhooks import TwitterWebhookAdapter
import os
import json

CONSUMER_KEY = os.environ.get('CONSUMER_KEY', None)
CONSUMER_SECRET = os.environ.get('CONSUMER_SECRET', None)
ACCESS_TOKEN = os.environ.get('ACCESS_TOKEN', None)
ACCESS_TOKEN_SECRET = os.environ.get('ACCESS_TOKEN_SECRET', None)

events_adapter = TwitterWebhookAdapter(CONSUMER_SECRET, "/webhooks/twitter")
twtr = TwitterAPI(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET)

logger = events_adapter.server.logger


def get_account_id():
    # Helper for fetching the bot's ID
    credentials = twtr.request('account/verify_credentials').json()
    return credentials['id']


def send_dm(recipient_id, message_text):
    # Helper for sending DMs
    event = {
            "event": {
                "type": "message_create",
                "message_create": {
                    "target": {
                        "recipient_id": recipient_id
                    },
Example #5
0
def test_existing_flask():
    valid_flask = Flask(__name__)
    valid_adapter = TwitterWebhookAdapter("CONSUMER_SECRET",
                                          "/webhooks/twitter", valid_flask)
    assert isinstance(valid_adapter, TwitterWebhookAdapter)
Example #6
0
def test_server_not_flask():
    with pytest.raises(TypeError) as e:
        invalid_flask = "I am not a Flask"
        TwitterWebhookAdapter("CONSUMER_SECRET", "/webhooks/twitter",
                              invalid_flask)
    assert e.value.args[0] == 'Server must be an instance of Flask'
Example #7
0
class TwitterWebHooks(object):
    app_secret = None
    logger = None
    events_adapter = None
    bot_id = None
    conn = None
    port = '3000'
    host = '0.0.0.0'

    @classmethod
    def _timestamp_utc(cls, dt):
        if isinstance(dt, basestring):
            dt = parse(dt)

        return calendar.timegm(dt.utctimetuple())

    @classmethod
    def _timestamp_utc_now(cls):
        dt = datetime.utcnow()
        return calendar.timegm(dt.utctimetuple())

    def __init__(self, conn, app_secret, account_id):

        lg.info("TwitterWebHooks::__init__()...")

        self.app_secret = app_secret
        self.bot_id = account_id
        self.conn = conn
        self.events_adapter = TwitterWebhookAdapter(self.app_secret, "/webhooks/twitter")

    def follow_followers(self):
        lg.debug('TwitterWebHooks::follow_followers(): started')

        # only get the latest followers but this relies on Twitter returns the latest followers first
        followers = []
        for fid in self.conn.cursor(self.conn.get_followers_ids, count=5000):
            followers.append(fid)

        friends = []
        for fid in self.conn.cursor(self.conn.get_friends_ids, count=5000):
            friends.append(fid)

        pending = []
        for fid in self.conn.cursor(self.conn.get_outgoing_friendship_ids):
            pending.append(fid)

        lg.debug('TwitterWebHooks::follow_followers(): looking for new followers')
        to_follow = [f for f in followers[:100] if f not in friends and f not in pending]
        lg.debug('TwitterWebHooks::follow_followers(): about to send follow request')

        # only follow 10 at a time
        actions = []
        for user_id in to_follow[:10]:
            try:
                resp = self.conn.create_friendship(user_id=user_id)
                time.sleep(1)
            except TwythonError as e:
                # either really failed (e.g. sent request before) or already friends
                lg.warning("TwitterWebHooks::follow_followers: failed to follow user %s: %s", user_id, e.msg)
                continue
            else:
                lg.debug('TwitterWebHooks::follow_followers(): just sent request to follow user %s', user_id)

            msg = {'id': str(user_id),
                   'created_utc': self._timestamp_utc_now(),
                   'author': {'name': resp['screen_name']},
                   'body': '+register',
                   'type': 'mention'}
            # make the msg id unique
            msg['id'] += ('@' + str(msg['created_utc']))

            action = ctb_action.eval_message(ctb_misc.DotDict(msg), self.ctb)
            actions.append(action)

        return actions

    def _parse_follow(self, event_data):
        event = event_data['event']
        source_id = event['source']['id']
        source_name = event['source']['screen_name']

        msg = {'id': source_id,
               'created_utc': self._timestamp_utc_now(),
               'author': {'name': source_name},
               'body': '+register',
               'type': 'direct_message'}
        # make the msg id unique
        msg['id'] += ('@' + str(msg['created_utc']))

        lg.debug(msg)
        action = ctb_action.eval_message(ctb_misc.DotDict(msg), self.ctb)
        return action

    def _parse_event(self, event_data):

        # check pending tips
        if (datetime.utcnow() - self.last_expiry).seconds > 60 * 60:
            self.ctb.expire_pending_tips()
            self.last_expiry = datetime.utcnow()

    def _parse_mention(self, event_data):

        event = event_data['event']
        author_name = event['user']['screen_name']

        # we do allow the bot to issue commands
        msg = {'created_utc': self._timestamp_utc_now(),
               'author': {'name': author_name},
               'type': 'mention'}
        msg['id'] = event['id_str'] + str(msg['created_utc'])[(30-len(event['id_str'])):]

        text = event['text']
        msg['body'] = text.replace('@' + self.username, '').strip()
        lg.debug(msg)

        action = ctb_action.eval_message(ctb_misc.DotDict(msg), self.ctb)
        return action

    def _parse_direct_message(self, event_data):

        event = event_data['event']
        recipient_id = event['message_create']['target']['recipient_id']
        sender_id = event['message_create']['sender_id']
        sender_screen_name = event_data['users'][sender_id]['screen_name']
        recipient_screen_name = event_data['users'][recipient_id]['screen_name']
        message_text = event['message_create']['message_data']['text']

        msg = {'created_utc': self._timestamp_utc_now(),
               'author': {'name': sender_screen_name},
               'recipient_id': sender_id,
               'type': 'direct_message'}
        msg['id'] = event['id'] + str(msg['created_utc'])[(30-len(event['id'])):]

        text = event['message_create']['message_data']['text']
        msg['body'] = text.replace('@' + self.username, '').strip()
        lg.debug(msg)

        action = ctb_action.eval_message(ctb_misc.DotDict(msg), self.ctb)

        return action

    def send_dm(self, recipient_id, message_text):
        # Helper for sending DMs
        event = {
            "event": {
                "type": "message_create",
                "message_create": {
                    "target": {
                        "recipient_id": recipient_id
                    },
                    "message_data": {
                        "text": message_text
                    }
                }
            }
        }

        response = self.conn.post('direct_messages/events/new', json.dumps(event))
        return response

    def run(self):
        """
        Returns a Twitter webhook object
        """

        @self.events_adapter.on("direct_message_events")
        def handle_message(event_data):
            event = event_data['event']
            if event['type'] == 'message_create':
                recipient_id = event['message_create']['target']['recipient_id']
                sender_id = event['message_create']['sender_id']
                sender_screen_name = event_data['users'][sender_id]['screen_name']
                recipient_screen_name = event_data['users'][recipient_id]['screen_name']
                message_text = event['message_create']['message_data']['text']

                # Filter out bot messages
                if str(sender_id) == str(self.bot_id):
                    lg.info("IGNORING [Event {}] Incoming DM: To {} from {} \"{}\"".format(
                        event['id'],
                        recipient_screen_name,
                        sender_screen_name,
                        message_text
                    ))
                else:
                    lg.info("[Event {}] Incoming DM: To {} from {} \"{}\"".format(
                        event['id'],
                        recipient_screen_name,
                        sender_screen_name,
                        message_text
                    ))
                    try:
                        # dm_id = self.send_dm(sender_id, "ACK! {}".format(event['id']))['event']['id']
                        actions = self._parse_direct_message(event_data)

                        if not actions:
                            return

                        if not isinstance(actions, list):
                            actions = [actions]

                        for action in actions:
                            if action:
                                lg.info("TwitterWebHook::on_success(): %s from %s", action.type, action.u_from.name)
                                lg.debug("TwitterWebHook::on_success(): comment body: <%s>", action.msg.body)
                                action.do()

                    except Exception as e:
                        lg.info("An error occurred sending DM: {}".format(e))

        @self.events_adapter.on("tweet_create_events")
        def handle_message(event_data):
            event = event_data['event']

            # tweet_create_events (@mentions)
            fields = fields = ['created_at', 'id', 'user', 'entities', 'text']
            if all(field in event for field in fields):
                # received tweet_create_events (@mentions)

                # ignore retweets
                if event['retweeted'] == True:
                    return None
                recipient_id = event_data['for_user_id']
                sender_id = event['user']['id_str']
                sender_screen_name = event['user']['screen_name']
                message_text = event['text']
                author_name = event['user']['screen_name']

                # if author was tipbot, return
                if author_name == self.username or '@' + self.username not in event['text']:
                    return None
                else:
                    lg.info("[Event {}] Incoming Mention: From {} \"{}\"".format(
                        event['id'],
                        sender_screen_name,
                        message_text
                    ))
                    try:
                        actions = self._parse_mention(event_data)

                        if not actions:
                            return

                        if not isinstance(actions, list):
                            actions = [actions]

                        for action in actions:
                            if action:
                                lg.info("TwitterWebHook::on_success(): %s from %s", action.type, action.u_from.name)
                                lg.debug("TwitterWebHook::on_success(): comment body: <%s>", action.msg.body)
                                action.do()

                    except Exception as e:
                        lg.info("An error occurred responding to Mention: {}".format(e))

        @self.events_adapter.on("favorite_events")
        def handle_message(event_data):
            event = event_data['event']
            faved_status = event['favorited_status']
            faved_status_id = faved_status['id']
            faved_status_screen_name = faved_status['user']['screen_name']
            faved_by_screen_name = event['user']['screen_name']
            lg.info("@{} faved @{}'s tweet: {}".format(faved_by_screen_name, faved_status_screen_name, faved_status_id))
            # print(json.dumps(event_data, indent=4, sort_keys=True))

        @self.events_adapter.on("follow_events")
        def handle_message(event_data):

            try:
                # if unfollow, do nothing
                if event_data['event']['type'] == 'unfollow':
                    lg.info("TwitterWebHook::on_success(): %s received for %s", event_data['event']['type'], event_data['event']['target']['name'])
                    return

                # if follow event from tipbot, ignore. we are just following back
                if event_data['event']['source']['id'] == str(self.bot_id):
                    lg.info("TwitterWebHook::on_success(): %s received for %s, from %s. ignoring", event_data['event']['type'], event_data['event']['target']['screen_name'], event_data['event']['source']['screen_name'])
                    return

                actions = self._parse_follow(event_data)

                if not actions:
                    return

                if not isinstance(actions, list):
                    actions = [actions]

                for action in actions:
                    if action:
                        lg.info("TwitterWebHook::on_success(): %s from %s", action.type, action.u_from.name)
                        lg.debug("TwitterWebHook::on_success(): comment body: <%s>", action.msg.body)
                        action.do()

            except Exception as e:
                lg.info("An error occurred responding to Mention: {}".format(e))

        @self.events_adapter.on("any")
        def handle_message(event_data):
            # Loop through events array and log received events
            for s in filter(lambda x: '_event' in x, list(event_data)):
                lg.info("[any] Received event: {}".format(s))

            actions = self._parse_event(event_data)

            if not actions:
                return

            if not isinstance(actions, list):
                actions = [actions]

            for action in actions:
                if action:
                    lg.info("TwitterWebHooks::on_success(): %s from %s", action.type, action.u_from.name)
                    lg.debug("TwitterWebHooks::on_success(): comment body: <%s>", action.msg.body)
                    action.do()


        # Handler for error events
        @self.events_adapter.on("error")
        def error_handler(err):
            print("ERROR: " + str(err))

        lg.info("TwitterWebHooks::connecting(): Listening for Events")
        self.events_adapter.start(port = self.port, host = self.host)
import pytest
from twitterwebhooks import TwitterWebhookAdapter

ADAPTER = TwitterWebhookAdapter('CONSUMER_SECRET', '/webhooks/twitter')


def test_event_emission(client):
    # Events should trigger an event
    @ADAPTER.on('reaction_added')
    def event_handler(event):
        assert event["reaction"] == 'grinning'

    data = bytes(pytest.twitter_like_event_fixture, 'ascii')
    signature = pytest.create_signature(ADAPTER.consumer_secret, data)
    res = client.post(
        '/webhooks/twitter',
        data=data,
        content_type='application/json',
        headers={
            'X-Twitter-Webhooks-Signature': signature
        }
    )

    assert res.status_code == 200
def app():
    adapter = TwitterWebhookAdapter("CONSUMER_SECRET")
    app = adapter.server
    app.testing = True
    return app