diff --git a/zulip_bots/zulip_bots/bots/twitpost/Readme.md b/zulip_bots/zulip_bots/bots/twitpost/Readme.md new file mode 100644 index 0000000..804efd4 --- /dev/null +++ b/zulip_bots/zulip_bots/bots/twitpost/Readme.md @@ -0,0 +1,44 @@ +# Twitpost Bot + +Twitpost bot is a Zulip bot to tweet from zulip chat. + +To use twitpost bot, you can simply call it with `@twitpost` followed +by a keyword `tweet` followed by the content to be tweeted. +For example: + +`@twitpost tweet hey batman` + +# Setup + +Before starting using the bot, you will need: + +1. consumer_key +2. consumer_secret +3. access_token +4. access_token_secret + +To obtain these 4 keys, follow the following steps : + +1. Login on [Twitter Application Management](https://apps.twitter.com/) using your Twitter account credentials. +2. Create a new Twitter app in the [Twitter Application Management](https://apps.twitter.com/) +3. Provide the required details (Name, Description and Website). +4. Open your app and click on `Keys and Access Tokens`. +5. This completes creation of Twitter app to get the 4 required keys. +6. Take a look at configuration section to configure the bot. + +# Configuration + +Enter the 4 keys in the respective field in `twitter.ini` file. + +Run this bot as described in [here](https://zulipchat.com/api/running-bots#running-a-bot). + +## Usage + +`@twitpost tweet` + +- This command tweets the given content to Twitter. +- Example Usage: `@twitpost tweet hey batman`, `@twitpost tweet hello world!` +- Sample Output: + +`Tweet Posted +https://twitter.com/jasoncosta/status/243145735212777472` diff --git a/zulip_bots/zulip_bots/bots/twitpost/fixtures/api_response.json b/zulip_bots/zulip_bots/bots/twitpost/fixtures/api_response.json new file mode 100644 index 0000000..a7abedc --- /dev/null +++ b/zulip_bots/zulip_bots/bots/twitpost/fixtures/api_response.json @@ -0,0 +1,91 @@ +{ + "coordinates": null, + "favorited": false, + "created_at": "Wed Sep 05 00:37:15 +0000 2012", + "truncated": false, + "id_str": "243145735212777472", + "entities": { + "urls": [], + "hashtags": [ + { + "text": "peterfalk", + "indices": [ + 35, + 45 + ] + } + ], + "user_mentions": [] + }, + "in_reply_to_user_id_str": null, + "text": "Maybe he'll finally find his keys. #peterfalk", + "contributors": null, + "retweet_count": 0, + "id": 243145735212777472, + "in_reply_to_status_id_str": null, + "geo": null, + "retweeted": false, + "in_reply_to_user_id": null, + "place": null, + "user": { + "name": "Jason Costa", + "profile_sidebar_border_color": "86A4A6", + "profile_sidebar_fill_color": "A0C5C7", + "profile_background_tile": false, + "profile_image_url": "http://a0.twimg.com/profile_images/1751674923/new_york_beard_normal.jpg", + "created_at": "Wed May 28 00:20:15 +0000 2008", + "location": "", + "is_translator": true, + "follow_request_sent": false, + "id_str": "14927800", + "profile_link_color": "FF3300", + "entities": { + "url": { + "urls": [ + { + "expanded_url": "http://www.jason-costa.blogspot.com/", + "url": "http://t.co/YCA3ZKY", + "indices": [ + 0, + 19 + ], + "display_url": "jason-costa.blogspot.com" + } + ] + }, + "description": { + "urls": [] + } + }, + "default_profile": false, + "contributors_enabled": false, + "url": "http://t.co/YCA3ZKY", + "favourites_count": 883, + "utc_offset": -28800, + "id": 14927800, + "profile_image_url_https": "https://si0.twimg.com/profile_images/1751674923/new_york_beard_normal.jpg", + "profile_use_background_image": true, + "listed_count": 150, + "profile_text_color": "333333", + "protected": false, + "lang": "en", + "followers_count": 8760, + "time_zone": "Pacific Time (US & Canada)", + "profile_background_image_url_https": "https://si0.twimg.com/images/themes/theme6/bg.gif", + "verified": false, + "profile_background_color": "709397", + "notifications": false, + "description": "Platform at Twitter", + "geo_enabled": true, + "statuses_count": 5532, + "default_profile_image": false, + "friends_count": 166, + "profile_background_image_url": "http://a0.twimg.com/images/themes/theme6/bg.gif", + "show_all_inline_media": true, + "screen_name": "jasoncosta", + "following": false + }, + "source": "My Shiny App", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null +} diff --git a/zulip_bots/zulip_bots/bots/twitpost/test_twitpost.py b/zulip_bots/zulip_bots/bots/twitpost/test_twitpost.py new file mode 100644 index 0000000..0d77a17 --- /dev/null +++ b/zulip_bots/zulip_bots/bots/twitpost/test_twitpost.py @@ -0,0 +1,51 @@ +from zulip_bots.test_lib import ( + StubBotHandler, + BotTestCase, + get_bot_message_handler, +) +from zulip_bots.test_file_utils import ( + read_bot_fixture_data, +) +from unittest.mock import patch +import tweepy +import os +import json + + +class TestTwitpostBot(BotTestCase): + bot_name = "twitpost" + mock_config = {'consumer_key': 'abcdefghijklmnopqrstuvwxy', + 'consumer_secret': 'aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyy', + 'access_token': '123456789012345678-ABCDefgh1234afdsa678lKj6gHhslsi', + 'access_token_secret': 'yf0SI0x6Ct2OmF0cDQc1E0eLKXrVAPFx4QkZF2f9PfFCt'} + api_response = read_bot_fixture_data('twitpost', 'api_response') + + def test_bot_usage(self) -> None: + bot = get_bot_message_handler(self.bot_name) + bot_handler = StubBotHandler() + + with self.mock_config_info(self.mock_config): + bot.initialize(bot_handler) + + self.assertIn('This bot posts on twitter from zulip chat itself', bot.usage()) + + def test_bot_responds_to_empty_message(self) -> None: + with self.mock_config_info(self.mock_config): + self.verify_reply('', 'Please check help for usage.') + + def test_help(self) -> None: + with self.mock_config_info(self.mock_config): + self.verify_reply('help', + "*Help for Twitter-post bot* :twitter: : \n\n" + "The bot tweets on twitter when message starts with @twitpost.\n\n" + "`@twitpost tweet ` will tweet on twitter with given ``.\n" + "Example:\n" + " * @twitpost tweet hey batman\n") + + @patch('tweepy.API.update_status', return_value=api_response) + def test_tweet(self, mockedarg): + test_message = 'tweet Maybe he\'ll finally find his keys. #peterfalk' + bot_response = 'Tweet Posted\n'\ + 'https://twitter.com/jasoncosta/status/243145735212777472' + with self.mock_config_info(self.mock_config): + self.verify_reply(test_message, bot_response) diff --git a/zulip_bots/zulip_bots/bots/twitpost/twitpost.py b/zulip_bots/zulip_bots/bots/twitpost/twitpost.py new file mode 100644 index 0000000..18b0e75 --- /dev/null +++ b/zulip_bots/zulip_bots/bots/twitpost/twitpost.py @@ -0,0 +1,54 @@ +import zulip +import tweepy +from typing import Dict, Any, Union, List, Tuple, Optional + + +class TwitpostBot(object): + + def usage(self) -> str: + return ''' This bot posts on twitter from zulip chat itself. + Use '@twitpost help' to get more information + on the bot usage. ''' + help_content = "*Help for Twitter-post bot* :twitter: : \n\n"\ + "The bot tweets on twitter when message starts "\ + "with @twitpost.\n\n"\ + "`@twitpost tweet ` will tweet on twitter " \ + "with given ``.\n" \ + "Example:\n" \ + " * @twitpost tweet hey batman\n" + + def initialize(self, bot_handler: Any) -> None: + self.config_info = bot_handler.get_config_info('twitter') + auth = tweepy.OAuthHandler(self.config_info['consumer_key'], + self.config_info['consumer_secret']) + auth.set_access_token(self.config_info['access_token'], + self.config_info['access_token_secret']) + self.api = tweepy.API(auth, parser=tweepy.parsers.JSONParser()) + + def handle_message(self, message: Dict[str, str], bot_handler: Any) -> None: + content = message["content"] + + if content.strip() == '': + bot_handler.send_reply(message, 'Please check help for usage.') + return + + if content.strip() == 'help': + bot_handler.send_reply(message, self.help_content) + return + + content = content.split() + + if len(content) > 1 and content[0] == "tweet": + status = self.post(" ".join(content[1:])) + screen_name = status["user"]["screen_name"] + id_str = status["id_str"] + bot_reply = "https://twitter.com/{}/status/{}".format(screen_name, + id_str) + bot_reply = "Tweet Posted\n" + bot_reply + bot_handler.send_reply(message, bot_reply) + + def post(self, text): + return self.api.update_status(text) + + +handler_class = TwitpostBot diff --git a/zulip_bots/zulip_bots/bots/twitpost/twitter.ini b/zulip_bots/zulip_bots/bots/twitpost/twitter.ini new file mode 100644 index 0000000..13c2358 --- /dev/null +++ b/zulip_bots/zulip_bots/bots/twitpost/twitter.ini @@ -0,0 +1,6 @@ +[twitter] +consumer_key= +consumer_secret= +access_token= +access_token_secret= +