Add twitpost bot.
ADD
zulip_bots/zulip_bots/bots/twitpost/Readme.md
+# 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`

ADD
zulip_bots/zulip_bots/bots/twitpost/fixtures/api_response.json
+{
+ "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
+}

ADD
zulip_bots/zulip_bots/bots/twitpost/test_twitpost.py
+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 <content>` will tweet on twitter with given `<content>`.\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)

ADD
zulip_bots/zulip_bots/bots/twitpost/twitpost.py
+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 <content>` will tweet on twitter " \
+ "with given `<content>`.\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

ADD
zulip_bots/zulip_bots/bots/twitpost/twitter.ini
+[twitter]
+consumer_key=
+consumer_secret=
+access_token=
+access_token_secret=
+

Add twitpost bot.