bots: Move all bots and the bots API to separate package.
1
.gitignore
vendored
|
@ -10,7 +10,6 @@ dist
|
||||||
build
|
build
|
||||||
eggs
|
eggs
|
||||||
parts
|
parts
|
||||||
var
|
|
||||||
sdist
|
sdist
|
||||||
develop-eggs
|
develop-eggs
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
from __future__ import print_function
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import importlib
|
|
||||||
import logging
|
|
||||||
import optparse
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import provision
|
|
||||||
from types import ModuleType
|
|
||||||
|
|
||||||
our_dir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
sys.path.insert(0, our_dir)
|
|
||||||
|
|
||||||
from bot_lib import run_message_handler_for_bot
|
|
||||||
|
|
||||||
def validate_path(bots_fn):
|
|
||||||
# type: (str) -> None
|
|
||||||
bots_fn = os.path.realpath(bots_fn)
|
|
||||||
if not os.path.dirname(bots_fn).startswith(os.path.normpath(os.path.join(our_dir, "../bots"))):
|
|
||||||
print('Sorry, we will only import code from api/bots.')
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if not bots_fn.endswith('.py'):
|
|
||||||
print('Please use a .py extension for library files.')
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
def get_lib_module(bots_fn):
|
|
||||||
# type: (str) -> ModuleType
|
|
||||||
base_bots_fn = os.path.basename(os.path.splitext(bots_fn)[0])
|
|
||||||
sys.path.insert(1, os.path.dirname(bots_fn))
|
|
||||||
module_name = base_bots_fn
|
|
||||||
module = importlib.import_module(module_name)
|
|
||||||
return module
|
|
||||||
|
|
||||||
def run():
|
|
||||||
# type: () -> None
|
|
||||||
usage = '''
|
|
||||||
./run.py <lib file>
|
|
||||||
Example: ./run.py lib/followup.py
|
|
||||||
(This program loads bot-related code from the
|
|
||||||
library code and then runs a message loop,
|
|
||||||
feeding messages to the library code to handle.)
|
|
||||||
Please make sure you have a current ~/.zuliprc
|
|
||||||
file with the credentials you want to use for
|
|
||||||
this bot.
|
|
||||||
See lib/readme.md for more context.
|
|
||||||
'''
|
|
||||||
|
|
||||||
parser = optparse.OptionParser(usage=usage)
|
|
||||||
parser.add_option('--quiet', '-q',
|
|
||||||
action='store_true',
|
|
||||||
help='Turn off logging output.')
|
|
||||||
parser.add_option('--config-file',
|
|
||||||
action='store',
|
|
||||||
help='(alternate config file to ~/.zuliprc)')
|
|
||||||
parser.add_option('--provision',
|
|
||||||
action='store_true',
|
|
||||||
help='Install dependencies for the bot')
|
|
||||||
parser.add_option('--force',
|
|
||||||
action='store_true',
|
|
||||||
help='Try running the bot even if dependencies install fails.')
|
|
||||||
(options, args) = parser.parse_args()
|
|
||||||
|
|
||||||
if len(args) == 0:
|
|
||||||
print('You must specify a library!')
|
|
||||||
sys.exit(1)
|
|
||||||
bots_fn = args[0]
|
|
||||||
|
|
||||||
validate_path(bots_fn)
|
|
||||||
if options.provision:
|
|
||||||
print("Provisioning")
|
|
||||||
provision.provision_bot(os.path.dirname(bots_fn), options.force)
|
|
||||||
lib_module = get_lib_module(bots_fn)
|
|
||||||
if not options.quiet:
|
|
||||||
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
|
|
||||||
|
|
||||||
run_message_handler_for_bot(
|
|
||||||
lib_module=lib_module,
|
|
||||||
config_file=options.config_file,
|
|
||||||
quiet=options.quiet
|
|
||||||
)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
run()
|
|
|
@ -17,11 +17,11 @@ def dir_join(dir1, dir2):
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
bots_dir = os.path.dirname(os.path.abspath(__file__))
|
tools_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
root_dir = dir_join(bots_dir, '..')
|
bots_dir = os.path.join(tools_dir, '..', 'zulip_bots', 'zulip_bots')
|
||||||
bots_test_dir = dir_join(bots_dir, '../bots')
|
bots_test_dir = dir_join(bots_dir, 'bots')
|
||||||
|
|
||||||
sys.path.insert(0, root_dir)
|
sys.path.insert(0, bots_dir)
|
||||||
sys.path.insert(0, bots_test_dir)
|
sys.path.insert(0, bots_test_dir)
|
||||||
|
|
||||||
btd = bots_test_dir
|
btd = bots_test_dir
|
||||||
|
@ -57,7 +57,7 @@ if __name__ == '__main__':
|
||||||
sys.path.insert(0, dep_path)
|
sys.path.insert(0, dep_path)
|
||||||
try:
|
try:
|
||||||
suites.append(loader.discover(start_dir = dir_join(bots_test_dir, bot_to_test),
|
suites.append(loader.discover(start_dir = dir_join(bots_test_dir, bot_to_test),
|
||||||
top_level_dir = root_dir))
|
top_level_dir = bots_dir))
|
||||||
except ImportError:
|
except ImportError:
|
||||||
logging.error("Bot %s requires __init__.py to be added" % (bot_to_test))
|
logging.error("Bot %s requires __init__.py to be added" % (bot_to_test))
|
||||||
failed = True
|
failed = True
|
5
zulip_bots/MANIFEST.in
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
include zulip_bots/bots/giphy/fixtures/test_1.json
|
||||||
|
include zulip_bots/bots/github_detail/fixtures/test_404.json
|
||||||
|
include zulip_bots/bots/github_detail/fixtures/test_issue.json
|
||||||
|
include zulip_bots/bots/github_detail/fixtures/test_pull.json
|
||||||
|
|
|
@ -24,13 +24,17 @@ to messages in any of the following settings:
|
||||||
account specific authentication, for example: a gmail bot that lets
|
account specific authentication, for example: a gmail bot that lets
|
||||||
one send emails directly through Zulip.
|
one send emails directly through Zulip.
|
||||||
|
|
||||||
|
## Installing
|
||||||
|
|
||||||
|
To install the bots and bots API, run:
|
||||||
|
|
||||||
|
python setup.py install
|
||||||
|
|
||||||
## Running bots
|
## Running bots
|
||||||
|
|
||||||
Here is an example of running the "follow-up" bot from
|
Here is an example of running the "follow-up" bot:
|
||||||
inside a Zulip repo (and in your remote instance):
|
|
||||||
|
|
||||||
cd ~/zulip/api
|
zulip-run-bot followup --config-file ~/.zuliprc-prod
|
||||||
bots_api/run.py bots/followup/followup.py --config-file ~/.zuliprc-prod
|
|
||||||
|
|
||||||
Once the bot code starts running, you will see a
|
Once the bot code starts running, you will see a
|
||||||
message explaining how to use the bot, as well as
|
message explaining how to use the bot, as well as
|
78
zulip_bots/setup.py
Executable file
|
@ -0,0 +1,78 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# We should be installable with either setuptools or distutils.
|
||||||
|
package_info = dict(
|
||||||
|
name='zulip_bots',
|
||||||
|
version='0.3.1',
|
||||||
|
description='Zulip\'s Bot framework',
|
||||||
|
author='Zulip Open Source Project',
|
||||||
|
author_email='zulip-devel@googlegroups.com',
|
||||||
|
classifiers=[
|
||||||
|
'Development Status :: 4 - Beta',
|
||||||
|
'Environment :: Web Environment',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'License :: OSI Approved :: MIT License',
|
||||||
|
'Topic :: Communications :: Chat',
|
||||||
|
],
|
||||||
|
url='https://www.zulip.org/',
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': [
|
||||||
|
'zulip-run-bot=zulip_bots.run:main',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
include_package_data=True,
|
||||||
|
) # type: Dict[str, Any]
|
||||||
|
|
||||||
|
setuptools_info = dict(
|
||||||
|
install_requires=[
|
||||||
|
'zulip>=0.3.1',
|
||||||
|
'mock>=2.0.0',
|
||||||
|
'html2text', # for bots/define
|
||||||
|
'PyDictionary', # for bots/thesaurus
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
package_info.update(setuptools_info)
|
||||||
|
package_info['packages'] = find_packages()
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
from distutils.core import setup
|
||||||
|
from distutils.version import LooseVersion
|
||||||
|
from importlib import import_module
|
||||||
|
|
||||||
|
# Manual dependency check
|
||||||
|
def check_dependency_manually(module_name, version=None):
|
||||||
|
try:
|
||||||
|
module = import_module(module_name)
|
||||||
|
if version is not None:
|
||||||
|
assert(LooseVersion(module.__version__) >= LooseVersion(version))
|
||||||
|
except (ImportError, AssertionError):
|
||||||
|
if version is not None:
|
||||||
|
print("{name}>={version} is not installed.".format(
|
||||||
|
req=req, version=version), file=sys.stderr)
|
||||||
|
else:
|
||||||
|
print("{name} is not installed.".format(name=module_name), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
check_dependency_manually('zulip', '0.3.1')
|
||||||
|
check_dependency_manually('mock', '2.0.0')
|
||||||
|
check_dependency_manually('html2text')
|
||||||
|
check_dependency_manually('PyDictionary')
|
||||||
|
|
||||||
|
# Include all submodules under bots/
|
||||||
|
package_list = ['zulip_bots']
|
||||||
|
dirs = os.listdir('zulip_bots/bots/')
|
||||||
|
for dir_name in dirs:
|
||||||
|
if os.path.isdir(os.path.join('zulip_bots/bots/', dir_name)):
|
||||||
|
package_list.append('zulip_bots.bots.' + dir_name)
|
||||||
|
package_info['packages'] = package_list
|
||||||
|
|
||||||
|
|
||||||
|
setup(**package_info)
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
@ -5,11 +5,10 @@ from __future__ import division
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import importlib
|
import importlib
|
||||||
import sys
|
|
||||||
from math import log10, floor
|
from math import log10, floor
|
||||||
|
|
||||||
import utils
|
|
||||||
import re
|
import re
|
||||||
|
from zulip_bots.bots.converter import utils
|
||||||
|
|
||||||
def is_float(value):
|
def is_float(value):
|
||||||
try:
|
try:
|
2
bots/converter/test_converter.py → zulip_bots/zulip_bots/bots/converter/test_converter.py
Normal file → Executable file
|
@ -3,7 +3,7 @@
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
from bots_test_lib import BotTestCase
|
from zulip_bots.test_lib import BotTestCase
|
||||||
|
|
||||||
class TestConverterBot(BotTestCase):
|
class TestConverterBot(BotTestCase):
|
||||||
bot_name = "converter"
|
bot_name = "converter"
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
2
bots/define/test_define.py → zulip_bots/zulip_bots/bots/define/test_define.py
Normal file → Executable file
|
@ -3,7 +3,7 @@
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
from bots_test_lib import BotTestCase
|
from zulip_bots.test_lib import BotTestCase
|
||||||
|
|
||||||
class TestDefineBot(BotTestCase):
|
class TestDefineBot(BotTestCase):
|
||||||
bot_name = "define"
|
bot_name = "define"
|
Before Width: | Height: | Size: 287 KiB After Width: | Height: | Size: 287 KiB |
Before Width: | Height: | Size: 180 KiB After Width: | Height: | Size: 180 KiB |
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 160 KiB |
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 170 KiB |
2
bots/encrypt/test_encrypt.py → zulip_bots/zulip_bots/bots/encrypt/test_encrypt.py
Normal file → Executable file
|
@ -3,7 +3,7 @@
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
from bots_test_lib import BotTestCase
|
from zulip_bots.test_lib import BotTestCase
|
||||||
|
|
||||||
class TestEncryptBot(BotTestCase):
|
class TestEncryptBot(BotTestCase):
|
||||||
bot_name = "encrypt"
|
bot_name = "encrypt"
|
2
bots/followup/test_followup.py → zulip_bots/zulip_bots/bots/followup/test_followup.py
Normal file → Executable file
|
@ -3,7 +3,7 @@
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
from bots_test_lib import BotTestCase
|
from zulip_bots.test_lib import BotTestCase
|
||||||
|
|
||||||
class TestFollowUpBot(BotTestCase):
|
class TestFollowUpBot(BotTestCase):
|
||||||
bot_name = "followup"
|
bot_name = "followup"
|
2
bots/giphy/test_giphy.py → zulip_bots/zulip_bots/bots/giphy/test_giphy.py
Normal file → Executable file
|
@ -5,7 +5,7 @@ from __future__ import print_function
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from bots_test_lib import BotTestCase
|
from zulip_bots.test_lib import BotTestCase
|
||||||
|
|
||||||
class TestGiphyBot(BotTestCase):
|
class TestGiphyBot(BotTestCase):
|
||||||
bot_name = "giphy"
|
bot_name = "giphy"
|
|
@ -27,7 +27,7 @@ Here is an example of running the `git_hub_comment` bot from
|
||||||
inside a Zulip repo:
|
inside a Zulip repo:
|
||||||
|
|
||||||
`cd ~/zulip/api`
|
`cd ~/zulip/api`
|
||||||
`bots_api/run.py bots/git_hub_comment/git_hub_comment.py --config-file ~/.zuliprc-prod`
|
`zulip-run-bot git_hub_comment --config-file ~/.zuliprc-prod`
|
||||||
|
|
||||||
Once the bot code starts running, you will see a
|
Once the bot code starts running, you will see a
|
||||||
message explaining how to use the bot, as well as
|
message explaining how to use the bot, as well as
|
||||||
|
@ -49,5 +49,3 @@ page to create a user-owned bot.
|
||||||
email=someuser@example.com
|
email=someuser@example.com
|
||||||
key=<your api key>
|
key=<your api key>
|
||||||
site=https://zulip.somewhere.com
|
site=https://zulip.somewhere.com
|
||||||
|
|
||||||
|
|
9
bots/github_detail/test_github_detail.py → zulip_bots/zulip_bots/bots/github_detail/test_github_detail.py
Normal file → Executable file
|
@ -3,16 +3,9 @@
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
our_dir = os.path.dirname(os.path.abspath(__file__))
|
from zulip_bots.test_lib import BotTestCase
|
||||||
sys.path.insert(0, os.path.normpath(os.path.join(our_dir)))
|
|
||||||
# For dev setups, we can find the API in the repo itself.
|
|
||||||
if os.path.exists(os.path.join(our_dir, '..')):
|
|
||||||
sys.path.insert(0, '..')
|
|
||||||
from bots_test_lib import BotTestCase
|
|
||||||
|
|
||||||
class TestGithubDetailBot(BotTestCase):
|
class TestGithubDetailBot(BotTestCase):
|
||||||
bot_name = "github_detail"
|
bot_name = "github_detail"
|
2
bots/helloworld/test_helloworld.py → zulip_bots/zulip_bots/bots/helloworld/test_helloworld.py
Normal file → Executable file
|
@ -5,7 +5,7 @@ from __future__ import print_function
|
||||||
|
|
||||||
from six.moves import zip
|
from six.moves import zip
|
||||||
|
|
||||||
from bots_test_lib import BotTestCase
|
from zulip_bots.test_lib import BotTestCase
|
||||||
|
|
||||||
class TestHelloWorldBot(BotTestCase):
|
class TestHelloWorldBot(BotTestCase):
|
||||||
bot_name = "helloworld"
|
bot_name = "helloworld"
|
2
bots/help/test_help.py → zulip_bots/zulip_bots/bots/help/test_help.py
Normal file → Executable file
|
@ -5,7 +5,7 @@ from __future__ import print_function
|
||||||
|
|
||||||
from six.moves import zip
|
from six.moves import zip
|
||||||
|
|
||||||
from bots_test_lib import BotTestCase
|
from zulip_bots.test_lib import BotTestCase
|
||||||
|
|
||||||
class TestHelpBot(BotTestCase):
|
class TestHelpBot(BotTestCase):
|
||||||
bot_name = "help"
|
bot_name = "help"
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 9 KiB After Width: | Height: | Size: 9 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
@ -1,6 +1,5 @@
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
|
|
||||||
from random import choice
|
from random import choice
|
||||||
|
|
||||||
|
@ -12,9 +11,6 @@ except ImportError:
|
||||||
Please: pip install chatterbot""")
|
Please: pip install chatterbot""")
|
||||||
|
|
||||||
BOTS_DIR = os.path.dirname(os.path.abspath(__file__))
|
BOTS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
os.chdir(os.path.dirname(BOTS_DIR))
|
|
||||||
sys.path.insert(0, os.path.dirname(BOTS_DIR))
|
|
||||||
|
|
||||||
JOKES_PATH = os.path.join(BOTS_DIR, 'assets/var/jokes.json')
|
JOKES_PATH = os.path.join(BOTS_DIR, 'assets/var/jokes.json')
|
||||||
DATABASE_PATH = os.path.join(BOTS_DIR, 'assets/var/database.db')
|
DATABASE_PATH = os.path.join(BOTS_DIR, 'assets/var/database.db')
|
||||||
DIRECTORY_PATH = os.path.join(BOTS_DIR, 'assets')
|
DIRECTORY_PATH = os.path.join(BOTS_DIR, 'assets')
|
2
bots/thesaurus/test_thesaurus.py → zulip_bots/zulip_bots/bots/thesaurus/test_thesaurus.py
Normal file → Executable file
|
@ -3,7 +3,7 @@
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
from bots_test_lib import BotTestCase
|
from zulip_bots.test_lib import BotTestCase
|
||||||
|
|
||||||
class TestThesaurusBot(BotTestCase):
|
class TestThesaurusBot(BotTestCase):
|
||||||
bot_name = "thesaurus"
|
bot_name = "thesaurus"
|
2
bots/virtual_fs/test_virtual_fs.py → zulip_bots/zulip_bots/bots/virtual_fs/test_virtual_fs.py
Normal file → Executable file
|
@ -3,7 +3,7 @@
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
from bots_test_lib import BotTestCase
|
from zulip_bots.test_lib import BotTestCase
|
||||||
|
|
||||||
class TestVirtualFsBot(BotTestCase):
|
class TestVirtualFsBot(BotTestCase):
|
||||||
bot_name = "virtual_fs"
|
bot_name = "virtual_fs"
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |