interactive bots: Create Baremetrics bot.
ADD
zulip_bots/zulip_bots/bots/baremetrics/__init__.py
zulip_bots/zulip_bots/bots/baremetrics/__init__.py
ADD
zulip_bots/zulip_bots/bots/baremetrics/assets/list-commands.png
zulip_bots/zulip_bots/bots/baremetrics/assets/list-commands.png
Binary files /dev/null and b/zulip_bots/zulip_bots/bots/baremetrics/assets/list-commands.png differ

ADD
zulip_bots/zulip_bots/bots/baremetrics/baremetrics.conf
+[baremetrics]
+api_key = <api_key>

ADD
zulip_bots/zulip_bots/bots/baremetrics/baremetrics.py
+# See readme.md for instructions on running this code.
+
+from typing import Any
+import requests
+
+class BaremetricsHandler(object):
+ def initialize(self, bot_handler: Any) -> None:
+ self.config_info = bot_handler.get_config_info('baremetrics')
+ self.api_key = self.config_info['api_key']
+
+ self.auth_header = {
+ 'Authorization': 'Bearer ' + self.api_key
+ }
+
+ self.commands = ['help', 'list-commands', 'account-info', 'list-sources', 'list-plans <source_id>',
+ 'list-customers <source_id>',
+ 'list-subscriptions <source_id>']
+
+ self.descriptions = ['Display bot info', 'Display the list of available commands', 'Display the account info',
+ 'List the sources', 'List the plans for the source', 'List the customers in the source',
+ 'List the subscriptions in the source']
+
+ def usage(self) -> str:
+ return '''
+ This bot gives updates about customer behavior, financial performance, and analytics
+ for an organization using the Baremetrics Api.\n
+ Enter `list-commands` to show the list of available commands.
+ Version 1.0
+ '''
+
+ def handle_message(self, message: Any, bot_handler: Any) -> None:
+ message['content'] = message['content'].strip()
+
+ if message['content'].lower() == 'help':
+ bot_handler.send_reply(message, self.usage())
+ return
+
+ if message['content'].lower() == 'list-commands':
+ response = '**Available Commands:** \n'
+ for command, description in zip(self.commands, self.descriptions):
+ response += ' - {} : {}\n'.format(command, description)
+
+ bot_handler.send_reply(message, response)
+ return
+
+ if message['content'] == '':
+ bot_handler.send_reply(message, 'No Command Specified')
+ return
+
+ response = self.generate_response(message['content'])
+ bot_handler.send_reply(message, response)
+
+ def generate_response(self, command: str) -> str:
+ try:
+ if command.lower() == 'account-info':
+ return self.get_account_info()
+
+ if command.lower() == 'list-sources':
+ return self.get_sources()
+
+ part_commands = command.split()
+
+ try:
+ if part_commands[0].lower() == 'list-plans':
+ return self.get_plans(part_commands[1])
+
+ if part_commands[0].lower() == 'list-customers':
+ return self.get_customers(part_commands[1])
+
+ if part_commands[0].lower() == 'list-subscriptions':
+ return self.get_subscriptions(part_commands[1])
+
+ except IndexError:
+ return 'Missing Params.'
+ except KeyError:
+ return 'Invalid Response From API.'
+
+ return 'Invalid Command.'
+
+ def get_account_info(self) -> str:
+ url = "https://api.baremetrics.com/v1/account"
+ account_response = requests.get(url, headers=self.auth_header)
+
+ account_data = account_response.json()
+ account_data = account_data['account']
+
+ response = '**Your account information:** \n'
+ response += 'Id: {id}\n'.format(id=account_data['id'])
+ response += 'Company: {company}\n'.format(company=account_data['company'])
+ response += 'Default Currency: {currency_name}'.format(currency_name=account_data['default_currency']['name'])
+
+ return response
+
+ def get_sources(self) -> str:
+ url = 'https://api.baremetrics.com/v1/sources'
+ sources_response = requests.get(url, headers=self.auth_header)
+
+ sources_data = sources_response.json()
+ sources_data = sources_data['sources']
+
+ response = '**Listing sources:** \n'
+ for index, source in enumerate(sources_data):
+ response += '{}.ID: {}\nProvider: {}\nProvider ID: {}\n\n'.format(index + 1, source['id'],
+ source['provider'],
+ source['provider_id'])
+
+ return response
+
+ def get_plans(self, source_id: str) -> str:
+ url = 'https://api.baremetrics.com/v1/{}/plans'.format(source_id)
+ plans_response = requests.get(url, headers=self.auth_header)
+
+ plans_data = plans_response.json()
+ plans_data = plans_data['plans']
+
+ template = '{}.Name: {}\nActive: {}\nInterval: {}\nInterval Count: {}\nAmounts: \n'
+ response = '**Listing plans:** \n'
+ for index, plan in enumerate(plans_data):
+ response += template.format(index + 1, plan['name'], plan['active'], plan['interval'],
+ plan['interval_count'])
+
+ for amount in plan['amounts']:
+ response += ' - {} {}\n'.format(amount['amount'], amount['currency'])
+
+ response += '\n'
+
+ return response
+
+ def get_customers(self, source_id: str) -> str:
+ url = 'https://api.baremetrics.com/v1/{}/customers'.format(source_id)
+ customers_response = requests.get(url, headers=self.auth_header)
+
+ customers_data = customers_response.json()
+ customers_data = customers_data['customers']
+
+ template = '{}.Name: {}\nDisplay Name: {}\nOID: {}\nActive: {}\nEmail: {}\nNotes: {}\nCurrent Plans: \n'
+ response = '**Listing customers:** \n'
+ for index, customer in enumerate(customers_data):
+ response += template.format(index + 1, customer['display_name'], customer['name'], customer['oid'],
+ customer['is_active'], customer['email'], customer['notes'])
+
+ for plan in customer['current_plans']:
+ response += ' - {}\n'.format(plan['name'])
+
+ response += '\n'
+
+ return response
+
+ def get_subscriptions(self, source_id: str) -> str:
+ url = 'https://api.baremetrics.com/v1/{}/subscriptions'.format(source_id)
+ subscriptions_response = requests.get(url, headers=self.auth_header)
+
+ subscriptions_data = subscriptions_response.json()
+ subscriptions_data = subscriptions_data['subscriptions']
+
+ template = '{}.Customer Name: {}\nCustomer Display Name: {}\nCustomer OID: {}\nCustomer Email: {}\n' \
+ 'Active: {}\nPlan Name: {}\nPlan Amounts: \n'
+ response = '**Listing subscriptions:** \n'
+ for index, subscription in enumerate(subscriptions_data):
+ response += template.format(index + 1, subscription['customer']['name'],
+ subscription['customer']['display_name'],
+ subscription['customer']['oid'], subscription['customer']['email'],
+ subscription['active'], subscription['plan']['name'])
+
+ for amount in subscription['plan']['amounts']:
+ response += ' - {} {}\n'.format(amount['amount'], amount['symbol'])
+
+ response += '\n'
+
+ return response
+
+handler_class = BaremetricsHandler

ADD
zulip_bots/zulip_bots/bots/baremetrics/doc.md
+# Baremetrics bot
+
+The Baremetrics bot is a Zulip bot that gives updates about customer behavior, financial performance, and
+analytics for an organization using the [Baremetrics](https://baremetrics.com/) API.
+
+To use the Baremetrics bot, you can simply call it with `@<botname>` followed
+by a command, like so:
+
+```
+@Baremetrics help
+```
+
+## Setup
+
+Before usage, you will need to configure the bot by putting the value of the `<api_key>` in the config file.
+To do this, follow the given steps:
+
+1. Login at [Baremetrics Console](https://app.baremetrics.com/settings/api).
+2. Note the `Live API Key`.
+3. Open up `zulip_bots/bots/baremetrics/baremetrics.conf` in an editor and
+ change the value of the `<api_key>` attribute to the noted `Live API Key`.
+
+## Developer Notes
+
+Be sure to add the command and its description to their respective lists (named `commands` and `descriptions`)
+so that it can be displayed with the other available commands using `@<botname> list-commands`. Also modify
+the `test_list_commands_command` in `test_baremetrics.py`.
+
+## Links
+
+ - [Baremetrics](https://baremetrics.com/)
+ - [Baremetrics Developer API](https://developers.baremetrics.com/reference)
+ - [Baremetrics Dashboard](https://app.baremetrics.com/setup)
+
+## Usage
+
+`@Baremetrics list-commands` - This command gives a list of all available commands along with short
+short descriptions.
+
+Example:
+![](assets/list-commands.png)

ADD
zulip_bots/zulip_bots/bots/baremetrics/fixtures/account_info.json
+{
+ "request": {
+ "api_url": "https://api.baremetrics.com/v1/account",
+ "headers": {
+ "Authorization": "Bearer TEST"
+ }
+ },
+ "response": {
+ "account": {
+ "id": 376418,
+ "default_currency": {
+ "id": "usd",
+ "alternate_symbols": [
+ "US$"
+ ],
+ "decimal_mark": ".",
+ "disambiguate_symbol": "US$",
+ "html_entity": "$",
+ "iso_code": "USD",
+ "iso_numeric": "840",
+ "name": "United States Dollar",
+ "priority": 1,
+ "smallest_denomination": 1,
+ "subunit": "Cent",
+ "subunit_to_unit": 100,
+ "symbol": "$",
+ "symbol_first": true,
+ "thousands_separator": ","
+ },
+ "company": "NA",
+ "created_at": 1514301115,
+ "stack": "seg"
+ }
+ },
+ "response-headers": {
+ "X-TokenExpires": "0",
+ "Server": "cloudflare-nginx",
+ "X-RateLimit-Remaining": "3593",
+ "X-Powered-By": "Phusion Passenger 5.0.30",
+ "Set-Cookie": "__cfduid=dd26464e3e6779b6a05b27d2681aea0851514374567; expires=Thu, 27-Dec-18 11:36:07 GMT; path=/; domain=.baremetrics.com; HttpOnly; Secure, LSW_WEB=\"LSW_WEB1\"; path=/",
+ "X-Runtime": "0.031730",
+ "Access-Control-Allow-Credentials": "false",
+ "Access-Control-Allow-Methods": "GET, OPTIONS, POST, PUT, DELETE",
+ "Content-Encoding": "gzip",
+ "Connection": "keep-alive",
+ "ETag": "W/\"110de39b319ed195bf1414ac4f3c9a01\"",
+ "Date": "Wed, 27 Dec 2017 11:36:08 GMT",
+ "X-RateLimit-Limit": "3600",
+ "X-Version": "721",
+ "X-Commit": "2dcec7190a2bd01c8d3d8527d386486c4bb5474c",
+ "Status": "200 OK",
+ "Content-Type": "application/json; charset=utf-8",
+ "Transfer-Encoding": "chunked",
+ "Access-Control-Allow-Headers": "Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type",
+ "Cache-Control": "max-age=0, private, must-revalidate",
+ "X-Request-Id": "0b292131-81bb-4df2-bdb9-089a50b17e63",
+ "CF-RAY": "3d3bfaf6feee2eff-DEL"
+ }
+}

ADD
zulip_bots/zulip_bots/bots/baremetrics/fixtures/list_customers.json
+{
+ "request": {
+ "api_url": "https://api.baremetrics.com/v1/TEST/customers",
+ "headers": {
+ "Authorization": "Bearer TEST"
+ }
+ },
+ "response": {
+ "customers": [],
+ "meta": {
+ "pagination": {
+ "has_more": false,
+ "page": 0,
+ "per_page": 30,
+ "total_count": 0
+ }
+ }
+ },
+ "response-headers": {
+ "X-TokenExpires": "0",
+ "Server": "cloudflare-nginx",
+ "X-RateLimit-Remaining": "3593",
+ "X-Powered-By": "Phusion Passenger 5.0.30",
+ "Set-Cookie": "__cfduid=dd26464e3e6779b6a05b27d2681aea0851514374567; expires=Thu, 27-Dec-18 11:36:07 GMT; path=/; domain=.baremetrics.com; HttpOnly; Secure, LSW_WEB=\"LSW_WEB1\"; path=/",
+ "X-Runtime": "0.031730",
+ "Access-Control-Allow-Credentials": "false",
+ "Access-Control-Allow-Methods": "GET, OPTIONS, POST, PUT, DELETE",
+ "Content-Encoding": "gzip",
+ "Connection": "keep-alive",
+ "ETag": "W/\"110de39b319ed195bf1414ac4f3c9a01\"",
+ "Date": "Wed, 27 Dec 2017 11:36:08 GMT",
+ "X-RateLimit-Limit": "3600",
+ "X-Version": "721",
+ "X-Commit": "2dcec7190a2bd01c8d3d8527d386486c4bb5474c",
+ "Status": "200 OK",
+ "Content-Type": "application/json; charset=utf-8",
+ "Transfer-Encoding": "chunked",
+ "Access-Control-Allow-Headers": "Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type",
+ "Cache-Control": "max-age=0, private, must-revalidate",
+ "X-Request-Id": "0b292131-81bb-4df2-bdb9-089a50b17e63",
+ "CF-RAY": "3d3bfaf6feee2eff-DEL"
+ }
+}

ADD
zulip_bots/zulip_bots/bots/baremetrics/fixtures/list_plans.json
+{
+ "request": {
+ "api_url": "https://api.baremetrics.com/v1/TEST/plans",
+ "headers": {
+ "Authorization": "Bearer TEST"
+ }
+ },
+ "response": {
+ "plans": [],
+ "meta": {
+ "pagination": {
+ "has_more": false,
+ "page": 0,
+ "per_page": 30
+ }
+ }
+ },
+ "response-headers": {
+ "X-TokenExpires": "0",
+ "Server": "cloudflare-nginx",
+ "X-RateLimit-Remaining": "3593",
+ "X-Powered-By": "Phusion Passenger 5.0.30",
+ "Set-Cookie": "__cfduid=dd26464e3e6779b6a05b27d2681aea0851514374567; expires=Thu, 27-Dec-18 11:36:07 GMT; path=/; domain=.baremetrics.com; HttpOnly; Secure, LSW_WEB=\"LSW_WEB1\"; path=/",
+ "X-Runtime": "0.031730",
+ "Access-Control-Allow-Credentials": "false",
+ "Access-Control-Allow-Methods": "GET, OPTIONS, POST, PUT, DELETE",
+ "Content-Encoding": "gzip",
+ "Connection": "keep-alive",
+ "ETag": "W/\"110de39b319ed195bf1414ac4f3c9a01\"",
+ "Date": "Wed, 27 Dec 2017 11:36:08 GMT",
+ "X-RateLimit-Limit": "3600",
+ "X-Version": "721",
+ "X-Commit": "2dcec7190a2bd01c8d3d8527d386486c4bb5474c",
+ "Status": "200 OK",
+ "Content-Type": "application/json; charset=utf-8",
+ "Transfer-Encoding": "chunked",
+ "Access-Control-Allow-Headers": "Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type",
+ "Cache-Control": "max-age=0, private, must-revalidate",
+ "X-Request-Id": "0b292131-81bb-4df2-bdb9-089a50b17e63",
+ "CF-RAY": "3d3bfaf6feee2eff-DEL"
+ }
+}

ADD
zulip_bots/zulip_bots/bots/baremetrics/fixtures/list_sources.json
+{
+ "request": {
+ "api_url": "https://api.baremetrics.com/v1/sources",
+ "headers": {
+ "Authorization": "Bearer TEST"
+ }
+ },
+ "response": {
+ "sources": [
+ {
+ "id": "5f7QC5NC0Ywgcu",
+ "provider": "baremetrics",
+ "provider_id": null
+ }
+ ],
+ "meta": {
+ "pagination": {
+ "has_more": false,
+ "page": 0,
+ "per_page": 30
+ }
+ }
+ },
+ "response-headers": {
+ "X-TokenExpires": "0",
+ "Server": "cloudflare-nginx",
+ "X-RateLimit-Remaining": "3593",
+ "X-Powered-By": "Phusion Passenger 5.0.30",
+ "Set-Cookie": "__cfduid=dd26464e3e6779b6a05b27d2681aea0851514374567; expires=Thu, 27-Dec-18 11:36:07 GMT; path=/; domain=.baremetrics.com; HttpOnly; Secure, LSW_WEB=\"LSW_WEB1\"; path=/",
+ "X-Runtime": "0.031730",
+ "Access-Control-Allow-Credentials": "false",
+ "Access-Control-Allow-Methods": "GET, OPTIONS, POST, PUT, DELETE",
+ "Content-Encoding": "gzip",
+ "Connection": "keep-alive",
+ "ETag": "W/\"110de39b319ed195bf1414ac4f3c9a01\"",
+ "Date": "Wed, 27 Dec 2017 11:36:08 GMT",
+ "X-RateLimit-Limit": "3600",
+ "X-Version": "721",
+ "X-Commit": "2dcec7190a2bd01c8d3d8527d386486c4bb5474c",
+ "Status": "200 OK",
+ "Content-Type": "application/json; charset=utf-8",
+ "Transfer-Encoding": "chunked",
+ "Access-Control-Allow-Headers": "Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type",
+ "Cache-Control": "max-age=0, private, must-revalidate",
+ "X-Request-Id": "0b292131-81bb-4df2-bdb9-089a50b17e63",
+ "CF-RAY": "3d3bfaf6feee2eff-DEL"
+ }
+}

ADD
zulip_bots/zulip_bots/bots/baremetrics/fixtures/list_subscriptions.json
+{
+ "request": {
+ "api_url": "https://api.baremetrics.com/v1/TEST/subscriptions",
+ "headers": {
+ "Authorization": "Bearer TEST"
+ }
+ },
+ "response": {
+ "subscriptions": [],
+ "meta": {
+ "pagination": {
+ "has_more": false,
+ "page": 0,
+ "per_page": 30
+ }
+ }
+ },
+ "response-headers": {
+ "X-TokenExpires": "0",
+ "Server": "cloudflare-nginx",
+ "X-RateLimit-Remaining": "3593",
+ "X-Powered-By": "Phusion Passenger 5.0.30",
+ "Set-Cookie": "__cfduid=dd26464e3e6779b6a05b27d2681aea0851514374567; expires=Thu, 27-Dec-18 11:36:07 GMT; path=/; domain=.baremetrics.com; HttpOnly; Secure, LSW_WEB=\"LSW_WEB1\"; path=/",
+ "X-Runtime": "0.031730",
+ "Access-Control-Allow-Credentials": "false",
+ "Access-Control-Allow-Methods": "GET, OPTIONS, POST, PUT, DELETE",
+ "Content-Encoding": "gzip",
+ "Connection": "keep-alive",
+ "ETag": "W/\"110de39b319ed195bf1414ac4f3c9a01\"",
+ "Date": "Wed, 27 Dec 2017 11:36:08 GMT",
+ "X-RateLimit-Limit": "3600",
+ "X-Version": "721",
+ "X-Commit": "2dcec7190a2bd01c8d3d8527d386486c4bb5474c",
+ "Status": "200 OK",
+ "Content-Type": "application/json; charset=utf-8",
+ "Transfer-Encoding": "chunked",
+ "Access-Control-Allow-Headers": "Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type",
+ "Cache-Control": "max-age=0, private, must-revalidate",
+ "X-Request-Id": "0b292131-81bb-4df2-bdb9-089a50b17e63",
+ "CF-RAY": "3d3bfaf6feee2eff-DEL"
+ }
+}

ADD
zulip_bots/zulip_bots/bots/baremetrics/test_baremetrics.py
+from zulip_bots.test_lib import BotTestCase
+
+class TestBaremetricsBot(BotTestCase):
+ bot_name = "baremetrics"
+
+ def test_bot_responds_to_empty_message(self) -> None:
+ with self.mock_config_info({'api_key': 'TEST'}):
+ self.verify_reply('', 'No Command Specified')
+
+ def test_help_query(self) -> None:
+ with self.mock_config_info({'api_key': 'TEST'}):
+ self.verify_reply('help', '''
+ This bot gives updates about customer behavior, financial performance, and analytics
+ for an organization using the Baremetrics Api.\n
+ Enter `list-commands` to show the list of available commands.
+ Version 1.0
+ ''')
+
+ def test_list_commands_command(self) -> None:
+ with self.mock_config_info({'api_key': 'TEST'}):
+ self.verify_reply('list-commands', '**Available Commands:** \n - help : Display bot info\n - list-commands '
+ ': Display the list of available commands\n - account-info : Display '
+ 'the account info\n - list-sources : List the sources\n - list-plans '
+ '<source_id> : List the plans for the source\n - list-customers '
+ '<source_id> : List the customers in the source\n - list-subscriptions '
+ '<source_id> : List the subscriptions in the source\n')
+
+ def test_account_info_command(self) -> None:
+ with self.mock_config_info({'api_key': 'TEST'}):
+ with self.mock_http_conversation('account_info'):
+ self.verify_reply('account-info', '**Your account information:** \nId: 376418\nCompany: NA\nDefault '
+ 'Currency: United States Dollar')
+
+ def test_list_sources_command(self) -> None:
+ with self.mock_config_info({'api_key': 'TEST'}):
+ with self.mock_http_conversation('list_sources'):
+ self.verify_reply('list-sources', '**Listing sources:** \n1.ID: 5f7QC5NC0Ywgcu\nProvider: '
+ 'baremetrics\nProvider ID: None\n\n')
+
+ def test_list_plans_command(self) -> None:
+ with self.mock_config_info({'api_key': 'TEST'}):
+ with self.mock_http_conversation('list_plans'):
+ self.verify_reply('list-plans TEST', '**Listing plans:** \n')
+
+ def test_list_customers_command(self) -> None:
+ with self.mock_config_info({'api_key': 'TEST'}):
+ with self.mock_http_conversation('list_customers'):
+ self.verify_reply('list-customers TEST', '**Listing customers:** \n')
+
+ def test_list_subscriptions_command(self) -> None:
+ with self.mock_config_info({'api_key': 'TEST'}):
+ with self.mock_http_conversation('list_subscriptions'):
+ self.verify_reply('list-subscriptions TEST', '**Listing subscriptions:** \n')

interactive bots: Create Baremetrics bot.