bots: Remove unmaintained bots.
|
@ -1,22 +0,0 @@
|
||||||
# Unmaintained bots
|
|
||||||
|
|
||||||
This directory contains bots that are currently not maintained by the Zulip
|
|
||||||
community. They are untested and potentially buggy or completely nonfunctional.
|
|
||||||
We only know that they worked at the time of their creation.
|
|
||||||
|
|
||||||
We see potential in every bot included in this directory. Many were moved simply
|
|
||||||
because they didn't contain automated tests. Feel free to use or revive
|
|
||||||
these bots.
|
|
||||||
|
|
||||||
## Reviving a bot
|
|
||||||
|
|
||||||
To revive an unmaintained bot, go through our [Writing bots](
|
|
||||||
https://chat.zulip.org/api/writing-bots) guide and check if
|
|
||||||
the bot meets the outlined criteria.
|
|
||||||
In particular, this means that the bot should:
|
|
||||||
* contain automated tests.
|
|
||||||
* be well-documented, with usage examples.
|
|
||||||
* provide a command set reasonably easy and convenient to use for a human.
|
|
||||||
|
|
||||||
Once a bot fulfills all the criteria, feel free to submit a pull request to add it
|
|
||||||
to the `bots` directory. We are happy to include it there.
|
|
|
@ -1,2 +0,0 @@
|
||||||
[Google.com]
|
|
||||||
api_key = abcdefghijklmnopqrstuvwxyz
|
|
|
@ -1,220 +0,0 @@
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import datetime as dt
|
|
||||||
import requests
|
|
||||||
|
|
||||||
class CommuteHandler(object):
|
|
||||||
'''
|
|
||||||
This plugin provides information regarding commuting
|
|
||||||
from an origin to a destination, providing a multitude of information.
|
|
||||||
It looks for messages starting with @mention of the bot.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def initialize(self, bot_handler):
|
|
||||||
self.api_key = bot_handler.get_config_info('commute', 'Google.com')['api_key']
|
|
||||||
|
|
||||||
def usage(self):
|
|
||||||
return '''
|
|
||||||
This plugin will allow briefings of estimated travel times,
|
|
||||||
distances and fare information for transit travel.
|
|
||||||
It can vary outputs depending on traffic conditions, departure and
|
|
||||||
arrival times as well as user preferences
|
|
||||||
(toll avoidance, preference for bus travel, etc.).
|
|
||||||
It looks for messages starting with @mention of the bot.
|
|
||||||
|
|
||||||
Users should input an origin and a destination
|
|
||||||
in any stream or through private messages to the bot to receive a
|
|
||||||
response in the same stream or through private messages if the
|
|
||||||
input was originally private.
|
|
||||||
|
|
||||||
Sample input:
|
|
||||||
@mention-botname origins=Chicago,IL,USA destinations=New+York,NY,USA
|
|
||||||
@mention-botname help
|
|
||||||
'''
|
|
||||||
|
|
||||||
help_info = '''
|
|
||||||
Obligatory Inputs:
|
|
||||||
Origin e.g. origins=New+York,NY,USA
|
|
||||||
Destination e.g. destinations=Chicago,IL,USA
|
|
||||||
Optional Inputs:
|
|
||||||
Mode Of Transport (inputs: driving, walking, bicycling, transit)
|
|
||||||
Default mode (no mode input) is driving
|
|
||||||
e.g. mode=driving or mode=transit
|
|
||||||
Units (metric or imperial)
|
|
||||||
e.g. units=metric
|
|
||||||
Restrictions (inputs: tolls, highways, ferries, indoor)
|
|
||||||
e.g. avoid=tolls
|
|
||||||
Departure Time (inputs: now or (YYYY, MM, DD, HH, MM, SS) departing)
|
|
||||||
e.g. departure_time=now or departure_time=2016,12,17,23,40,40
|
|
||||||
Arrival Time (inputs: (YYYY, MM, DD, HH, MM, SS) arriving)
|
|
||||||
e.g. arrival_time=2016,12,17,23,40,40
|
|
||||||
Languages:
|
|
||||||
Languages list: https://developers.google.com/maps/faq#languagesupport)
|
|
||||||
e.g. language=fr
|
|
||||||
|
|
||||||
Sample request:
|
|
||||||
@mention-botname origins=Chicago,IL,USA destinations=New+York,NY,USA language=fr
|
|
||||||
|
|
||||||
Please note:
|
|
||||||
Fare information can be derived, though is solely dependent on the
|
|
||||||
availability of the information
|
|
||||||
python run.py bots/followup/followup.py --config-file ~/.zuliprc-local
|
|
||||||
released by public transport operators.
|
|
||||||
Duration in traffic can only be derived if a departure time is set.
|
|
||||||
If a location has spaces in its name, please use a + symbol in the
|
|
||||||
place of the space/s.
|
|
||||||
A departure time and a arrival time can not be inputted at the same time
|
|
||||||
To add more than 1 input for a category,
|
|
||||||
e.g. more than 1 destinations,
|
|
||||||
use (|), e.g. destinations=Empire+State+Building|Statue+Of+Liberty
|
|
||||||
No spaces within addresses.
|
|
||||||
Departure times and arrival times must be in the UTC timezone,
|
|
||||||
you can use the timezone bot.
|
|
||||||
'''
|
|
||||||
|
|
||||||
# determines if bot will respond as a private message/ stream message
|
|
||||||
def send_info(self, message, letter, bot_handler):
|
|
||||||
bot_handler.send_reply(message, letter)
|
|
||||||
|
|
||||||
def calculate_seconds(self, time_str):
|
|
||||||
times = time_str.split(',')
|
|
||||||
times = [int(x) for x in times]
|
|
||||||
# UNIX time from January 1, 1970 00:00:00
|
|
||||||
unix_start_date = dt.datetime(1970, 1, 1, 0, 0, 0)
|
|
||||||
requested_time = dt.datetime(*times)
|
|
||||||
total_seconds = str(int((requested_time-unix_start_date)
|
|
||||||
.total_seconds()))
|
|
||||||
return total_seconds
|
|
||||||
|
|
||||||
# adds departure time and arrival time paramaters into HTTP request
|
|
||||||
def add_time_to_params(self, params):
|
|
||||||
# limited to UTC timezone because of difficulty with user inputting
|
|
||||||
# correct string for input
|
|
||||||
if 'departure_time' in params:
|
|
||||||
if 'departure_time' != 'now':
|
|
||||||
params['departure_time'] = self.calculate_seconds(params['departure_time'])
|
|
||||||
elif 'arrival_time' in params:
|
|
||||||
params['arrival_time'] = self.calculate_seconds(params['arrival_time'])
|
|
||||||
return
|
|
||||||
|
|
||||||
# gets content for output and sends it to user
|
|
||||||
def get_send_content(self, rjson, params, message, bot_handler):
|
|
||||||
try:
|
|
||||||
# JSON list of output variables
|
|
||||||
variable_list = rjson["rows"][0]["elements"][0]
|
|
||||||
# determines if user has valid inputs
|
|
||||||
not_found = (variable_list["status"] == "NOT_FOUND")
|
|
||||||
invalid_request = (rjson["status"] == "INVALID_REQUEST")
|
|
||||||
no_result = (variable_list["status"] == "ZERO_RESULTS")
|
|
||||||
|
|
||||||
if no_result:
|
|
||||||
self.send_info(message,
|
|
||||||
"Zero results\nIf stuck, try '@commute help'.",
|
|
||||||
bot_handler)
|
|
||||||
return
|
|
||||||
elif not_found or invalid_request:
|
|
||||||
raise IndexError
|
|
||||||
except IndexError:
|
|
||||||
self.send_info(message,
|
|
||||||
"Invalid input, please see instructions."
|
|
||||||
"\nIf stuck, try '@commute help'.", bot_handler)
|
|
||||||
return
|
|
||||||
|
|
||||||
# origin and destination strings
|
|
||||||
begin = 'From: ' + rjson["origin_addresses"][0]
|
|
||||||
end = 'To: ' + rjson["destination_addresses"][0]
|
|
||||||
distance = 'Distance: ' + variable_list["distance"]["text"]
|
|
||||||
duration = 'Duration: ' + variable_list["duration"]["text"]
|
|
||||||
output = begin + '\n' + end + '\n' + distance
|
|
||||||
# if user doesn't know that default mode is driving
|
|
||||||
if 'mode' not in params:
|
|
||||||
mode = 'Mode of Transport: Driving'
|
|
||||||
output += '\n' + mode
|
|
||||||
|
|
||||||
# determines if fare information is available
|
|
||||||
try:
|
|
||||||
fare = ('Fare: ' + variable_list["fare"]["currency"] +
|
|
||||||
variable_list["fare"]["text"])
|
|
||||||
output += '\n' + fare
|
|
||||||
except (KeyError, IndexError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# determines if traffic duration information is available
|
|
||||||
try:
|
|
||||||
traffic_duration = ('Duration in traffic: ' +
|
|
||||||
variable_list["duration_in_traffic"]
|
|
||||||
["text"])
|
|
||||||
output += '\n' + traffic_duration
|
|
||||||
except (KeyError, IndexError):
|
|
||||||
output += '\n' + duration
|
|
||||||
|
|
||||||
# bot sends commute information to user
|
|
||||||
self.send_info(message, output, bot_handler)
|
|
||||||
|
|
||||||
# creates parameters for HTTP request
|
|
||||||
def parse_pair(self, content_list):
|
|
||||||
result = {}
|
|
||||||
for item in content_list:
|
|
||||||
# enables key value pair
|
|
||||||
org = item.split('=')
|
|
||||||
# ensures that invalid inputs are not entered into url request
|
|
||||||
if len(org) != 2:
|
|
||||||
continue
|
|
||||||
key, value = org
|
|
||||||
result[key] = value
|
|
||||||
return result
|
|
||||||
|
|
||||||
def receive_response(self, params, message, bot_handler):
|
|
||||||
def validate_requests(request):
|
|
||||||
if request.status_code == 200:
|
|
||||||
return request.json()
|
|
||||||
else:
|
|
||||||
self.send_info(message,
|
|
||||||
"Something went wrong. Please try again." +
|
|
||||||
" Error: {error_num}.\n{error_text}"
|
|
||||||
.format(error_num=request.status_code,
|
|
||||||
error_text=request.text), bot_handler)
|
|
||||||
return
|
|
||||||
r = requests.get('https://maps.googleapis.com/maps/api/' +
|
|
||||||
'distancematrix/json', params=params)
|
|
||||||
result = validate_requests(r)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def handle_message(self, message, bot_handler):
|
|
||||||
original_content = message['content']
|
|
||||||
query = original_content.split()
|
|
||||||
|
|
||||||
if "help" in query:
|
|
||||||
self.send_info(message, self.help_info, bot_handler)
|
|
||||||
return
|
|
||||||
|
|
||||||
params = self.parse_pair(query)
|
|
||||||
params['key'] = self.api_key
|
|
||||||
self.add_time_to_params(params)
|
|
||||||
|
|
||||||
rjson = self.receive_response(params, message, bot_handler)
|
|
||||||
if not rjson:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.get_send_content(rjson, params, message, bot_handler)
|
|
||||||
|
|
||||||
handler_class = CommuteHandler
|
|
||||||
handler = CommuteHandler()
|
|
||||||
|
|
||||||
def test_parse_pair():
|
|
||||||
result = handler.parse_pair(['departure_time=2016,12,20,23,59,00',
|
|
||||||
'dog_foo=cat-foo'])
|
|
||||||
assert result == dict(departure_time='2016,12,20,23,59,00',
|
|
||||||
dog_foo='cat-foo')
|
|
||||||
|
|
||||||
def test_calculate_seconds():
|
|
||||||
result = handler.calculate_seconds('2016,12,20,23,59,00')
|
|
||||||
assert result == str(1482278340)
|
|
||||||
|
|
||||||
def test_helper_functions():
|
|
||||||
test_parse_pair()
|
|
||||||
test_calculate_seconds()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
test_helper_functions()
|
|
||||||
print('Success')
|
|
|
@ -1,71 +0,0 @@
|
||||||
This bot will allow briefings of estimated travel times, distances and
|
|
||||||
fare information for transit travel.
|
|
||||||
|
|
||||||
It can respond to: departure times, arrival times, user preferences
|
|
||||||
(toll avoidance, highway avoidance) and a mode of transport
|
|
||||||
|
|
||||||
It can output: fare information, more detailed addresses on origin and
|
|
||||||
destination, duration in traffic information, metric and imperical
|
|
||||||
units and information in various languages.
|
|
||||||
|
|
||||||
The bot will respond to the same stream input was in. And if called as
|
|
||||||
private message, the bot will reply with a private message.
|
|
||||||
|
|
||||||
To setup the bot, you will first need to add a valid API key to commute.conf
|
|
||||||
|
|
||||||
Move
|
|
||||||
|
|
||||||
```
|
|
||||||
~/zulip/api/bots/commute/commute.conf
|
|
||||||
```
|
|
||||||
|
|
||||||
into
|
|
||||||
|
|
||||||
```
|
|
||||||
~/commute.conf
|
|
||||||
```
|
|
||||||
|
|
||||||
To add an API key, please visit:
|
|
||||||
https://developers.google.com/maps/documentation/distance-matrix/start
|
|
||||||
to retrieve a key and copy your api key into commute.conf
|
|
||||||
|
|
||||||
Sample input and output:
|
|
||||||
|
|
||||||
<pre><code>@commute help</code></pre>
|
|
||||||
|
|
||||||
<pre><code>Obligatory Inputs:
|
|
||||||
Origin e.g. origins=New+York,NY,USA
|
|
||||||
Destination e.g. destinations=Chicago,IL,USA
|
|
||||||
|
|
||||||
Optional Inputs:
|
|
||||||
Mode Of Transport (inputs: driving, walking, bicycling, transit)
|
|
||||||
Default mode (no mode input) is driving
|
|
||||||
e.g. mode=driving or mode=transit
|
|
||||||
Units (metric or imperial)
|
|
||||||
e.g. units=metric
|
|
||||||
Restrictions (inputs: tolls, highways, ferries, indoor)
|
|
||||||
e.g. avoid=tolls
|
|
||||||
Departure Time (inputs: now or (YYYY, MM, DD, HH, MM, SS) departing)
|
|
||||||
e.g. departure_time=now or departure_time=2016,12,17,23,40,40
|
|
||||||
Arrival Time (inputs: (YYYY, MM, DD, HH, MM, SS) arriving)
|
|
||||||
e.g. arrival_time=2016,12,17,23,40,40
|
|
||||||
Languages:
|
|
||||||
Languages list: https://developers.google.com/maps/faq#languagesupport)
|
|
||||||
e.g. language=fr
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
Sample request:
|
|
||||||
<pre><code>
|
|
||||||
@commute origins=Chicago,IL,USA destinations=New+York,NY,USA language=fr
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
Please note:
|
|
||||||
Fare information can be derived, though is solely dependent on the
|
|
||||||
availability of the information released by public transport operators.
|
|
||||||
Duration in traffic can only be derived if a departure time is set.
|
|
||||||
If a location has spaces in its name, please use a + symbol in the
|
|
||||||
place of the space/s.
|
|
||||||
A departure time and a arrival time can not be inputted at the same time
|
|
||||||
No spaces within addresses.
|
|
||||||
Departure times and arrival times must be in the UTC timezone,
|
|
||||||
you can use the timezone bot.
|
|
|
@ -1,32 +0,0 @@
|
||||||
# FourSquare Bot
|
|
||||||
|
|
||||||
* This is a bot that returns a list of restaurants from a user input of location,
|
|
||||||
proximity and restaurant type in that exact order. The number of returned
|
|
||||||
restaurants are capped at 3 per request.
|
|
||||||
|
|
||||||
* The list of restaurants are brought to Zulip using an API. The bot sends a GET
|
|
||||||
request to https://api.foursquare.com/v2/. If the user does not correctly input
|
|
||||||
a location, proximity and a restaurant type, the bot will return an error message.
|
|
||||||
|
|
||||||
* For example, if the user says "@foursquare 'Chicago, IL' 80000 seafood", the bot
|
|
||||||
will return:
|
|
||||||
|
|
||||||
Food nearby 'Chicago, IL' coming right up:
|
|
||||||
|
|
||||||
Dee's Seafood Co.
|
|
||||||
2723 S Poplar Ave, Chicago, IL 60608, United States
|
|
||||||
Fish Markets
|
|
||||||
|
|
||||||
Seafood Harbor
|
|
||||||
2131 S Archer Ave (at Wentworth Ave), Chicago, IL 60616, United States
|
|
||||||
Seafood Restaurants
|
|
||||||
|
|
||||||
Joe's Seafood, Prime Steak & Stone Crab
|
|
||||||
60 E Grand Ave (at N Rush St), Chicago, IL 60611, United States
|
|
||||||
Seafood Restaurants
|
|
||||||
|
|
||||||
* If the user enters a wrong word, like "@foursquare 80000 donuts" or "@foursquare",
|
|
||||||
then an error message saying invalid input will be displayed.
|
|
||||||
|
|
||||||
* To get the required API key, visit: https://developer.foursquare.com/overview/auth
|
|
||||||
for more information.
|
|
|
@ -1,2 +0,0 @@
|
||||||
[Foursquare]
|
|
||||||
api_key = abcdefghijksm
|
|
|
@ -1,99 +0,0 @@
|
||||||
from __future__ import print_function
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import datetime as dt
|
|
||||||
import re
|
|
||||||
import requests
|
|
||||||
from six.moves import range
|
|
||||||
|
|
||||||
class FoursquareHandler(object):
|
|
||||||
def initialize(self, bot_handler):
|
|
||||||
self.api_key = bot_handler.get_config_info('foursquare', 'Foursquare')['api_key']
|
|
||||||
|
|
||||||
def usage(self):
|
|
||||||
return '''
|
|
||||||
This plugin allows users to search for restaurants nearby an inputted
|
|
||||||
location to a limit of 3 venues for every location. The name, address
|
|
||||||
and description of the restaurant will be outputted.
|
|
||||||
It looks for messages starting with '@mention-bot'.
|
|
||||||
If you need help, simply type:
|
|
||||||
@mention-bot /help into the Compose Message box
|
|
||||||
|
|
||||||
Sample input:
|
|
||||||
@mention-bot Chicago, IL
|
|
||||||
@mention-bot help
|
|
||||||
'''
|
|
||||||
|
|
||||||
help_info = '''
|
|
||||||
The Foursquare bot can receive keyword limiters that specify the location, distance (meters) and
|
|
||||||
cusine of a restaurant in that exact order.
|
|
||||||
Please note the required use of quotes in the search location.
|
|
||||||
|
|
||||||
Example Inputs:
|
|
||||||
@mention-bot 'Millenium Park' 8000 donuts
|
|
||||||
@mention-bot 'Melbourne, Australia' 40000 seafood
|
|
||||||
'''
|
|
||||||
|
|
||||||
def format_json(self, venues):
|
|
||||||
def format_venue(venue):
|
|
||||||
name = venue['name']
|
|
||||||
address = ', '.join(venue['location']['formattedAddress'])
|
|
||||||
keyword = venue['categories'][0]['pluralName']
|
|
||||||
blurb = '\n'.join([name, address, keyword])
|
|
||||||
return blurb
|
|
||||||
|
|
||||||
return '\n'.join(format_venue(venue) for venue in venues)
|
|
||||||
|
|
||||||
def send_info(self, message, letter, bot_handler):
|
|
||||||
bot_handler.send_reply(message, letter)
|
|
||||||
|
|
||||||
def handle_message(self, message, bot_handler):
|
|
||||||
words = message['content'].split()
|
|
||||||
if "/help" in words:
|
|
||||||
self.send_info(message, self.help_info, bot_handler)
|
|
||||||
return
|
|
||||||
|
|
||||||
# These are required inputs for the HTTP request.
|
|
||||||
try:
|
|
||||||
params = {'limit': '3'}
|
|
||||||
params['near'] = re.search('\'[A-Za-z]\w+[,]?[\s\w+]+?\'', message['content']).group(0)
|
|
||||||
params['v'] = 20170108
|
|
||||||
params['oauth_token'] = self.api_key
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Optional params for HTTP request.
|
|
||||||
if len(words) >= 1:
|
|
||||||
try:
|
|
||||||
params['radius'] = re.search('([0-9]){3,}', message['content']).group(0)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
params['query'] = re.search('\s([A-Za-z]+)$', message['content']).group(0)[1:]
|
|
||||||
except AttributeError:
|
|
||||||
params['query'] = 'food'
|
|
||||||
|
|
||||||
response = requests.get('https://api.foursquare.com/v2/venues/search?',
|
|
||||||
params=params)
|
|
||||||
print(response.url)
|
|
||||||
if response.status_code == 200:
|
|
||||||
received_json = response.json()
|
|
||||||
else:
|
|
||||||
self.send_info(message,
|
|
||||||
"Invalid Request\nIf stuck, try '@mention-bot help'.",
|
|
||||||
bot_handler)
|
|
||||||
return
|
|
||||||
|
|
||||||
if received_json['meta']['code'] == 200:
|
|
||||||
response_msg = ('Food nearby ' + params['near'] +
|
|
||||||
' coming right up:\n' +
|
|
||||||
self.format_json(received_json['response']['venues']))
|
|
||||||
self.send_info(message, response_msg, bot_handler)
|
|
||||||
return
|
|
||||||
|
|
||||||
self.send_info(message,
|
|
||||||
"Invalid Request\nIf stuck, try '@mention-bot help'.",
|
|
||||||
bot_handler)
|
|
||||||
return
|
|
||||||
|
|
||||||
handler_class = FoursquareHandler
|
|
Before Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB |
|
@ -1,56 +0,0 @@
|
||||||
# Howdoi bot
|
|
||||||
|
|
||||||
This bot will allow users to get technical answers from
|
|
||||||
[StackOverflow](https://stackoverflow.com). It is build on top of the
|
|
||||||
python command line tool [howdoi](https://github.com/gleitz/howdoi) by
|
|
||||||
Benjamin Gleitzman.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Simply prepend your questions with one of the following commands. The
|
|
||||||
answer will be formatted differently depending the chosen command.
|
|
||||||
|
|
||||||
| Command | Respond |
|
|
||||||
| ----------------------- | ------------------------------------------------------ |
|
|
||||||
| `@mention-bot howdowe` | Concise answer to the same stream. |
|
|
||||||
| `@mention-bot howdowe!` | Same as `@mention-bot howdowe` but with full answer and URL of the solutions. |
|
|
||||||
| `@mention-bot howdoi` | Concise answer replied to sender via private message. |
|
|
||||||
| `@mention-bot howdoi!` | Same as `@mention-bot howdoi` but with full answer and URL of the solutions. |
|
|
||||||
|
|
||||||
## Screenshots
|
|
||||||
|
|
||||||
#### Example 1
|
|
||||||
|
|
||||||
Question -> `@mention-bot howdowe use supervisor in elixir`
|
|
||||||
|
|
||||||
![howdowe question](assets/question_howdowe.png)
|
|
||||||
|
|
||||||
Answer -> Howdoi would try to **only** respond with the coding section
|
|
||||||
of the answer.
|
|
||||||
|
|
||||||
![howdowe answer](assets/answer_howdowe.png)
|
|
||||||
|
|
||||||
#### Example 2
|
|
||||||
|
|
||||||
Question -> `@mention-bot howdoi! stack vs heap`
|
|
||||||
|
|
||||||
![howdoi! question](assets/question_howdoi_all.png)
|
|
||||||
|
|
||||||
Answer -> Howdoi would return the **full** stackoverflow answer via
|
|
||||||
**private message** to the original sender. The URL of the answer can be
|
|
||||||
seen at the bottom of the message.
|
|
||||||
|
|
||||||
![howdoi! answer](assets/answer_howdoi_all.png)
|
|
||||||
|
|
||||||
**Note:**
|
|
||||||
|
|
||||||
* Line wrapped is enabled with a maximum line length of 85 characters.
|
|
||||||
This could be adjusted in the source code (`HowdoiHandler.MAX_LINE_LENGTH`).
|
|
||||||
|
|
||||||
* *Howdoi* generally perform better if you ask a question using keywords
|
|
||||||
instead of a complete sentences (eg: "How do i make a decorator in Python"
|
|
||||||
-> "python decorator").
|
|
||||||
|
|
||||||
* __[*Limitation*]__ If a answer contains multiple code blocks, the `@mention-bot howdoi`
|
|
||||||
and `@mention-bot howdowe` commands would only return the first coding section, use
|
|
||||||
`@mention-bot howdo[we|i]!` in that case.
|
|
|
@ -1,118 +0,0 @@
|
||||||
"""
|
|
||||||
This bot uses the python library `howdoi` which is not a
|
|
||||||
dependency of Zulip. To use this module, you will have to
|
|
||||||
install it in your local machine. In your terminal, enter
|
|
||||||
the following command:
|
|
||||||
|
|
||||||
$ sudo pip install howdoi --upgrade
|
|
||||||
|
|
||||||
Note:
|
|
||||||
* You might have to use `pip3` if you are using python 3.
|
|
||||||
* The install command would also download any dependency
|
|
||||||
required by `howdoi`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
from textwrap import fill
|
|
||||||
try:
|
|
||||||
from howdoi.howdoi import howdoi
|
|
||||||
except ImportError:
|
|
||||||
logging.error("Dependency missing!!\n%s" % (__doc__))
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
|
|
||||||
class HowdoiHandler(object):
|
|
||||||
'''
|
|
||||||
This plugin facilitates searching Stack Overflow for
|
|
||||||
techanical answers based on the Python library `howdoi`.
|
|
||||||
To get the best possible answer, only include keywords
|
|
||||||
in your questions.
|
|
||||||
|
|
||||||
There are two possible commands:
|
|
||||||
* @mention-bot howdowe > This would return the answer to the same
|
|
||||||
stream that it was called from.
|
|
||||||
|
|
||||||
* @mention-bot howdoi > The bot would send a private message to the
|
|
||||||
user containing the answer.
|
|
||||||
|
|
||||||
By default, howdoi only returns the coding section of the
|
|
||||||
first search result if possible, to see the full answer
|
|
||||||
from Stack Overflow, append a '!' to the commands.
|
|
||||||
(ie '@mention-bot howdoi!', '@mention-bot howdowe!')
|
|
||||||
'''
|
|
||||||
|
|
||||||
MAX_LINE_LENGTH = 85
|
|
||||||
|
|
||||||
def usage(self):
|
|
||||||
return '''
|
|
||||||
This plugin will allow users to get techanical
|
|
||||||
answers from Stackoverflow. Users should preface
|
|
||||||
their questions with one of the following:
|
|
||||||
|
|
||||||
* @mention-bot howdowe > Answer to the same stream
|
|
||||||
* @mention-bot howdoi > Answer via private message
|
|
||||||
|
|
||||||
* @mention-bot howdowe! OR @mention-bot howdoi! > Full answer from SO
|
|
||||||
'''
|
|
||||||
|
|
||||||
def line_wrap(self, string, length):
|
|
||||||
lines = string.split("\n")
|
|
||||||
|
|
||||||
wrapped = [(fill(line) if len(line) > length else line)
|
|
||||||
for line in lines]
|
|
||||||
|
|
||||||
return "\n".join(wrapped).strip()
|
|
||||||
|
|
||||||
def get_answer(self, command, query):
|
|
||||||
question = query[len(command):].strip()
|
|
||||||
result = howdoi(dict(
|
|
||||||
query=question,
|
|
||||||
num_answers=1,
|
|
||||||
pos=1,
|
|
||||||
all=command[-1] == '!',
|
|
||||||
color=False
|
|
||||||
))
|
|
||||||
_answer = self.line_wrap(result, HowdoiHandler.MAX_LINE_LENGTH)
|
|
||||||
|
|
||||||
answer = "Answer to '%s':\n```\n%s\n```" % (question, _answer)
|
|
||||||
|
|
||||||
return answer
|
|
||||||
|
|
||||||
def handle_message(self, message, bot_handler):
|
|
||||||
question = message['content'].strip()
|
|
||||||
|
|
||||||
if question.startswith('howdowe!'):
|
|
||||||
bot_handler.send_message(dict(
|
|
||||||
type='stream',
|
|
||||||
to=message['display_recipient'],
|
|
||||||
subject=message['subject'],
|
|
||||||
content=self.get_answer('howdowe!', question)
|
|
||||||
))
|
|
||||||
|
|
||||||
elif question.startswith('howdoi!'):
|
|
||||||
bot_handler.send_message(dict(
|
|
||||||
type='private',
|
|
||||||
to=message['sender_email'],
|
|
||||||
content=self.get_answer('howdoi!', question)
|
|
||||||
))
|
|
||||||
|
|
||||||
elif question.startswith('howdowe'):
|
|
||||||
bot_handler.send_message(dict(
|
|
||||||
type='stream',
|
|
||||||
to=message['display_recipient'],
|
|
||||||
subject=message['subject'],
|
|
||||||
content=self.get_answer('howdowe', question)
|
|
||||||
))
|
|
||||||
|
|
||||||
elif question.startswith('howdoi'):
|
|
||||||
bot_handler.send_message(dict(
|
|
||||||
type='private',
|
|
||||||
to=message['sender_email'],
|
|
||||||
content=self.get_answer('howdoi', question)
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
handler_class = HowdoiHandler
|
|
Before Width: | Height: | Size: 83 KiB |
Before Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 64 KiB |
|
@ -1,86 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"joke":"Did you hear about the guy whose whole left side was cut off? He's all right now."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"I'm reading a book about anti-gravity. It's impossible to put down."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"I wondered why the baseball was getting bigger. Then it hit me."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"I'm glad I know sign language, it's pretty handy."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"My friend's bakery burned down last night. Now his business is toast."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"Why did the cookie cry? It was feeling crumby."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"I used to be a banker, but I lost interest."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"A drum and a symbol fall off a cliff"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"Why do seagulls fly over the sea? Because they aren't bay-gulls!"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"Why did the fireman wear red, white, and blue suspenders? To hold his pants up."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"Why didn't the crab share his food? Because crabs are territorial animals, that don't share anything."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"Why was the javascript developer sad? Because he didn't Node how to Express himself."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"What do I look like? A JOKE MACHINE!?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"How did the hipster burn the roof of his mouth? He ate the pizza before it was cool."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"Why is it hard to make puns for kleptomaniacs? They are always taking things literally."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"I'm not a humorless, cold hearted, machine. I have feelings you know... or was supposed to."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"Two fish in a tank. One looks to the other and says 'Can you even drive this thing???'"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"Two fish swim down a river, and hit a wall. One says: 'Dam!'"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"What's funnier than a monkey dancing with an elephant? Two monkeys dancing with an elephant."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"How did Darth Vader know what Luke was getting for Christmas? He felt his presents."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"What's red and bad for your teeth? A Brick."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"What's orange and sounds like a parrot? A Carrot."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"What do you call a cow with no legs? Ground beef"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"Two guys walk into a bar. You'd think the second one would have noticed."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"What is a centipedes's favorite Beatle song? I want to hold your hand, hand, hand, hand..."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"What do you call a chicken crossing the road? Poultry in moton. "
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"What do you call a fake noodle? An impasta"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"joke":"How many tickles does it take to tickle an octupus? Ten-tickles!"
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,30 +0,0 @@
|
||||||
John
|
|
||||||
|
|
||||||
Instructions:
|
|
||||||
You'll have to install chatterbot to use this bot.
|
|
||||||
Please run: pip install chatterbot on your command line.
|
|
||||||
The script will need to download some NLTK packages after running in your
|
|
||||||
home directory. With the mission of humanizing bot interactions, John aims to be your
|
|
||||||
virtual assistant at the hour of asking for help in Zulip. John is an
|
|
||||||
interactive bot that uses machine learning heuristics to simulate a
|
|
||||||
conversation with the user. He has a great sense of humor and
|
|
||||||
is also powered by Open Source code!
|
|
||||||
|
|
||||||
![Joke John](assets/joke.png)
|
|
||||||
|
|
||||||
How it works?
|
|
||||||
John is initially trained with Corpus files, or large text files.
|
|
||||||
Dialogues are loaded into a json "database", he will try to follow them
|
|
||||||
once it receives input from a user. John will query the database and
|
|
||||||
try to find the response that best matches the input according to the Levenshtein distance
|
|
||||||
which is a string metric for measuring the difference between two sequences. If several
|
|
||||||
responses have the same acurracy, he will choose one at random.
|
|
||||||
|
|
||||||
![Meet John](assets/greetings.png)
|
|
||||||
|
|
||||||
Can he learn by himself?
|
|
||||||
John's engine allows him to learn from his conversations with people. However,
|
|
||||||
without strict supervision bots that learn from people can do harm, so learning
|
|
||||||
is currently restricted to his initial corpus.
|
|
||||||
|
|
||||||
![Assist](assets/assist.png)
|
|
|
@ -1,111 +0,0 @@
|
||||||
import json
|
|
||||||
import os
|
|
||||||
|
|
||||||
from random import choice
|
|
||||||
|
|
||||||
try:
|
|
||||||
from chatterbot import ChatBot
|
|
||||||
from chatterbot.trainers import ChatterBotCorpusTrainer, ListTrainer
|
|
||||||
except ImportError:
|
|
||||||
raise ImportError("""It looks like you are missing chatterbot.
|
|
||||||
Please: pip install chatterbot""")
|
|
||||||
|
|
||||||
BOTS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
DATABASE_PATH = os.path.join(BOTS_DIR, 'assets/var/database.db')
|
|
||||||
DIRECTORY_PATH = os.path.join(BOTS_DIR, 'assets')
|
|
||||||
VAR_PATH = os.path.join(BOTS_DIR, 'assets/var')
|
|
||||||
JOKES_PATH = 'assets/var/jokes.json'
|
|
||||||
|
|
||||||
if not os.path.exists(DIRECTORY_PATH):
|
|
||||||
os.makedirs(DIRECTORY_PATH)
|
|
||||||
|
|
||||||
if not os.path.exists(VAR_PATH):
|
|
||||||
os.makedirs(VAR_PATH)
|
|
||||||
|
|
||||||
# Create a new instance of a ChatBot
|
|
||||||
def create_chat_bot(no_learn):
|
|
||||||
return ChatBot("John",
|
|
||||||
storage_adapter="chatterbot.storage.JsonFileStorageAdapter",
|
|
||||||
logic_adapters=
|
|
||||||
[
|
|
||||||
"chatterbot.logic.MathematicalEvaluation",
|
|
||||||
{
|
|
||||||
"import_path": "chatterbot.logic.BestMatch",
|
|
||||||
"response_selection_method": "chatterbot.response_selection.get_random_response",
|
|
||||||
"statement_comparison_function": "chatterbot.comparisons.levenshtein_distance"
|
|
||||||
}],
|
|
||||||
output_adapter="chatterbot.output.OutputAdapter",
|
|
||||||
output_format='text',
|
|
||||||
database=DATABASE_PATH,
|
|
||||||
silence_performance_warning="True",
|
|
||||||
read_only=no_learn)
|
|
||||||
|
|
||||||
|
|
||||||
class JohnHandler(object):
|
|
||||||
'''
|
|
||||||
This bot aims to be Zulip's virtual assistant. It
|
|
||||||
finds the best match from a certain input.
|
|
||||||
Also understands the English language and can
|
|
||||||
mantain a conversation, joke and give useful information.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def usage(self):
|
|
||||||
return '''
|
|
||||||
This bot aims to be Zulip's virtual assistant. It
|
|
||||||
finds the best match from a certain input.
|
|
||||||
Also understands the English language and can
|
|
||||||
mantain a conversation, joke and give useful information.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def initialize(self, bot_handler):
|
|
||||||
self.bot = create_chat_bot(False)
|
|
||||||
self.bot.set_trainer(ListTrainer)
|
|
||||||
self.bot.train([
|
|
||||||
"I want to contribute",
|
|
||||||
"""Contributors are more than welcomed! Please read
|
|
||||||
https://github.com/zulip/zulip#how-to-get-involved-with-contributing-to-zulip
|
|
||||||
to learn how to contribute.""",
|
|
||||||
])
|
|
||||||
self.bot.train([
|
|
||||||
"What is Zulip?",
|
|
||||||
"""Zulip is a powerful, open source group chat application. Written in Python
|
|
||||||
and using the Django framework, Zulip supports both private messaging and group
|
|
||||||
chats via conversation streams. You can learn more about the product and its
|
|
||||||
features at https://www.zulip.org.""",
|
|
||||||
])
|
|
||||||
self.bot.train([
|
|
||||||
"I would like to request a remote dev instance",
|
|
||||||
"""Greetings! You should receive a response from one of our mentors soon.
|
|
||||||
In the meantime, why don't you learn more about running Zulip on a development
|
|
||||||
environment? https://zulip.readthedocs.io/en/latest/development/using.html""",
|
|
||||||
])
|
|
||||||
self.bot.train([
|
|
||||||
"Joke!",
|
|
||||||
"Only if you ask nicely!",
|
|
||||||
])
|
|
||||||
self.bot.train([
|
|
||||||
"What is your name?",
|
|
||||||
"I am John, my job is to assist you with Zulip.",
|
|
||||||
])
|
|
||||||
self.bot.train([
|
|
||||||
"What can you do?",
|
|
||||||
"I can provide useful information and jokes if you follow etiquette.",
|
|
||||||
])
|
|
||||||
with bot_handler.open(JOKES_PATH) as data_file:
|
|
||||||
for joke in json.load(data_file):
|
|
||||||
self.bot.train([
|
|
||||||
"Please can you tell me a joke?",
|
|
||||||
joke['joke'],
|
|
||||||
])
|
|
||||||
self.bot.set_trainer(ChatterBotCorpusTrainer)
|
|
||||||
self.bot.train(
|
|
||||||
"chatterbot.corpus.english"
|
|
||||||
)
|
|
||||||
self.chatterbot = create_chat_bot(True)
|
|
||||||
|
|
||||||
def handle_message(self, message, bot_handler):
|
|
||||||
original_content = message['content']
|
|
||||||
bot_response = str(self.chatterbot.get_response(original_content))
|
|
||||||
bot_handler.send_reply(message, bot_response)
|
|
||||||
|
|
||||||
handler_class = JohnHandler
|
|