Update to rtmbot 0.3.0
Merge tag '0.3.0'
This commit is contained in:
commit
80527cbe6e
23 changed files with 256 additions and 27 deletions
33
.github/CONTRIBUTING.md
vendored
Normal file
33
.github/CONTRIBUTING.md
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
# Contributing at Slack
|
||||
|
||||

|
||||
|
||||
## Before Contributing
|
||||
|
||||
Before contributing, please read our [Code of Conduct](./CODE_OF_CONDUCT.md). We take it very seriously, and expect that you will as well.
|
||||
|
||||
## New Issues
|
||||
|
||||
Before opening a new issue, please consider:
|
||||
|
||||
- Reading [the documentation](https://github.com/slackhq/python-rtmbot/blob/master/README.md) and [the changelog](https://github.com/slackhq/python-rtmbot/blob/master/CHANGELOG.md) first.
|
||||
- Searching for any related issues and avoid creating duplicated issues.
|
||||
- Adding details, diagnoses, screenshots or any type of useful information in existing issues, even if they are marked as closed. The team will still review it.
|
||||
- Trying out the examples [provided in this repository](https://github.com/slackhq/python-rtmbot/tree/master/examples).
|
||||
- Taking the time to think of a solution and [**open a pull request**](#new-pull-requests) for either improving the documentation, fixing a bug or suggesting a feature.
|
||||
- Finally, **[open an issue]**(https://github.com/slackhq/python-rtmbot/issues/new) to report a bug, ask for help or suggest a feature. The more information you give, the better people can help you.
|
||||
|
||||
|
||||
## New Pull Requests
|
||||
|
||||
We love pull requests and we are generally very receptive to contributions. Things to keep in mind:
|
||||
|
||||
- [Fork the repository](https://github.com/slackhq/python-rtmbot) and make sure to work on a branch up to date with origin master.
|
||||
- Do your thing!
|
||||
- Be mindful about doing atomic commits, adding documentation to your changes, not refactoring too much.
|
||||
- Add tests covering the new code or functionality you are adding.
|
||||
- Add a descriptive title and add any useful information for the reviewer. If your contribution is a user facing thing, please attach a screenshot and/or screencast (gif preferrably).
|
||||
- Read and agree to our [Contributor License Agreement (CLA)](https://docs.google.com/a/slack-corp.com/forms/d/1q_w8rlJG_x_xJOoSUMNl7R35rkpA7N6pUkKhfHHMD9c/viewform). _We cannot accept your PR without your agreement to our CLA_.
|
||||
- Create your pull request (yay!). If it is in relation to an existing issue, please mention it on the title or description.
|
||||
|
||||
[Interested in knowing more about about pull requests at Slack?](https://slack.engineering/on-empathy-pull-requests-979e4257d158#.awxtvmb2z)
|
29
.github/ISSUE_TEMPLATE.md
vendored
Normal file
29
.github/ISSUE_TEMPLATE.md
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
* [ ] I've read and understood the [Contributing guidelines](./CONTRIBUTING.md) and have done my best effort to follow them.
|
||||
* [ ] I've read and agree to the [Code of Conduct](./CODE_OF_CONDUCT.md).
|
||||
* [ ] I've searched for any related issues and avoided creating a duplicate issue.
|
||||
|
||||
#### Description
|
||||
> e.g. Description of the bug or feature
|
||||
|
||||
#### Reproducible in:
|
||||
* [ ] This is reproducible in the sample project.
|
||||
RTMBot version:
|
||||
Python version:
|
||||
OS Version:
|
||||
|
||||
#### Steps to reproduce:
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
### Expected result:
|
||||
> e.g. What you expected to happen
|
||||
|
||||
### Actual result:
|
||||
> e.g. What actually happened
|
||||
|
||||
### Attachments:
|
||||
> e.g. Logs, screenshots, screencast, sample project, funny gif, etc.
|
||||
|
||||
|
15
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
15
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
* [ ] I've read and understood the [Contributing guidelines](./CONTRIBUTING.md) and have done my best effort to follow them.
|
||||
* [ ] I've read and agree to the [Code of Conduct](./CODE_OF_CONDUCT.md).
|
||||
* [ ] I've been mindful about doing atomic commits, adding documentation to my changes, not refactoring too much.
|
||||
* [ ] I've a descriptive title and added any useful information for the reviewer. Where appropriate, I've attached a screenshot and/or screencast (gif preferably).
|
||||
* [ ] I've written tests to cover the new code and functionality included in this PR.
|
||||
* [ ] I've read, agree to, and signed the [Contributor License Agreement (CLA)](https://docs.google.com/a/slack-corp.com/forms/d/1q_w8rlJG_x_xJOoSUMNl7R35rkpA7N6pUkKhfHHMD9c/viewform).
|
||||
|
||||
#### PR Summary
|
||||
> e.g. New functionality for producing whatsits.
|
||||
|
||||
#### Related Issues
|
||||
> e.g. Fixes #206 and closes #230
|
||||
|
||||
#### Test strategy
|
||||
> e.g. Add tests around whatsit production.
|
BIN
.github/contributing_header_slack.png
vendored
Normal file
BIN
.github/contributing_header_slack.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 99 KiB |
9
CHANGELOG.md
Normal file
9
CHANGELOG.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
### v0.2.0 (2016-03-25)
|
||||
|
||||
* Fixed unicode handling to make things work in python 2.7 and 3.5.
|
||||
* Changed versioning scheme to Semver.
|
||||
|
||||
### v0.10 (2016-02-28)
|
||||
|
||||
* Refactored the RTMBot Class to make it more isolated from the script that runs the bot
|
||||
|
34
CODE_OF_CONDUCT.md
Normal file
34
CODE_OF_CONDUCT.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
# Slack open source code of conduct
|
||||
|
||||
|
||||
## Introduction
|
||||
Diversity and inclusion make our community strong. We encourage participation from the most varied and diverse backgrounds possible and want to be very clear about where we stand.
|
||||
|
||||
Our goal is to maintain a safe, helpful and friendly community for everyone, regardless of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other defining characteristic.
|
||||
|
||||
This code and related procedures also apply to unacceptable behavior occurring outside the scope of community activities, in all community venues (online and in-person) as well as in all one-on-one communications, and anywhere such behavior has the potential to adversely affect the safety and well-being of community members.
|
||||
|
||||
## Expected Behavior
|
||||
* Be welcoming.
|
||||
* Be kind.
|
||||
* Look out for each other.
|
||||
|
||||
## Unacceptable Behavior
|
||||
* Conduct or speech which might be considered sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory or offensive in nature.
|
||||
* Unwelcome, suggestive, derogatory or inappropriate nicknames or terms.
|
||||
* Disrespect towards others. (Jokes, innuendo, dismissive attitudes.)
|
||||
* Intimidation or harassment (online or in-person). Please read the [Citizen Code of Conduct](http://citizencodeofconduct.org/) for how we interpret harassment.
|
||||
* Disrespect towards differences of opinion.
|
||||
* Inappropriate attention or contact. Be aware of how your actions affect others. If it makes someone uncomfortable, stop.
|
||||
* Not understanding the differences between constructive criticism and disparagement.
|
||||
* Sustained disruptions.
|
||||
* Violence, threats of violence or violent language.
|
||||
|
||||
## Enforcement
|
||||
Understand that speech and actions have consequences, and unacceptable behavior will not be tolerated.
|
||||
|
||||
If you are the subject of, or witness to any violations of this Code of Conduct, please contact us by submitting a form [here](https://docs.google.com/a/slack-corp.com/forms/d/1NVqj2S2Q49XVIOT5N3L6Tx1oihvk9CpMa_UX8T_6ESo/viewform), or email conduct@slack.com.
|
||||
|
||||
If violations occur, organizers will take any action they deem appropriate for the infraction, up to and including expulsion.
|
||||
|
||||
_Thanks to the [Django Code of Conduct](https://www.djangoproject.com/conduct/), [The Citizen Code of Conduct](http://citizencodeofconduct.org/), [The Rust Code of Conduct](https://www.rust-lang.org/conduct.html) and [The Ada Initiative](http://adainitiative.org/2014/02/18/howto-design-a-code-of-conduct-for-your-community/)._
|
|
@ -1,3 +1,7 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2016 Slack Technologies, Inc
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
19
README.md
19
README.md
|
@ -39,7 +39,7 @@ Installation
|
|||
|
||||
3. Configure rtmbot (https://api.slack.com/bot-users)
|
||||
|
||||
cp doc/example-config/rtmbot.conf .
|
||||
cp docs/example-config/rtmbot.conf .
|
||||
vi rtmbot.conf
|
||||
SLACK_TOKEN: "xoxb-11111111111-222222222222222"
|
||||
|
||||
|
@ -53,7 +53,7 @@ Plugins can be installed as .py files in the ```plugins/``` directory OR as a .p
|
|||
To install the example 'repeat' plugin
|
||||
|
||||
mkdir plugins/repeat
|
||||
cp doc/example-plugins/repeat.py plugins/repeat
|
||||
cp docs/example-plugins/repeat.py plugins/repeat/
|
||||
|
||||
The repeat plugin will now be loaded by the bot on startup.
|
||||
|
||||
|
@ -72,6 +72,8 @@ This will print the incoming message json (dict) to the screen where the bot is
|
|||
|
||||
Plugins having a method defined as ```catch_all(data)``` will receive ALL events from the websocket. This is useful for learning the names of events and debugging.
|
||||
|
||||
Note: If you're using Python 2.x, the incoming data should be a unicode string, be careful you don't coerce it into a normal str object as it will cause errors on output. You can add `from __future__ import unicode_literals` to your plugin file to avoid this.
|
||||
|
||||
####Outgoing data
|
||||
Plugins can send messages back to any channel, including direct messages. This is done by appending a two item array to the outputs global array. The first item in the array is the channel ID and the second is the message text. Example that writes "hello world" when the plugin is started:
|
||||
|
||||
|
@ -92,5 +94,18 @@ Plugins can also run methods on a schedule. This allows a plugin to poll for upd
|
|||
####Plugin misc
|
||||
The data within a plugin persists for the life of the rtmbot process. If you need persistent data, you should use something like sqlite or the python pickle libraries.
|
||||
|
||||
####Direct API Calls
|
||||
You can directly call the Slack web API in your plugins by including the following import:
|
||||
|
||||
from client import slack_client
|
||||
|
||||
You can also rename the client on import so it can be easily referenced like shown below:
|
||||
|
||||
from client import slack_client as sc
|
||||
|
||||
Direct API calls can be called in your plugins in the following form:
|
||||
|
||||
sc.api_call("API.method", "parameters")
|
||||
|
||||
####Todo:
|
||||
Some rtm data should be handled upstream, such as channel and user creation. These should create the proper objects on-the-fly.
|
||||
|
|
10
client.py
Normal file
10
client.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from rtmbot import RtmBot
|
||||
|
||||
slack_client = None
|
||||
|
||||
|
||||
def init(config):
|
||||
global slack_client
|
||||
bot = RtmBot(config)
|
||||
slack_client = bot.slack_client
|
||||
return bot
|
|
@ -1,3 +1,6 @@
|
|||
from __future__ import unicode_literals
|
||||
# don't convert to ascii in py2.7 when creating string to return
|
||||
|
||||
import time
|
||||
outputs = []
|
||||
|
|
@ -1,3 +1,6 @@
|
|||
from __future__ import unicode_literals
|
||||
# don't convert to ascii in py2.7 when creating string to return
|
||||
|
||||
import time
|
||||
crontable = []
|
||||
outputs = []
|
11
docs/example-plugins/directAPIcall.py
Normal file
11
docs/example-plugins/directAPIcall.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from __future__ import unicode_literals
|
||||
from client import slack_client as sc
|
||||
|
||||
|
||||
def process_message(data):
|
||||
'''If a user passes 'print users' in a message, print the users in the slack
|
||||
team to the console. (Don't run this in production probably)'''
|
||||
|
||||
if 'print users' in data['text']:
|
||||
for user in sc.api_call("users.list")["members"]:
|
||||
print(user["name"], user["id"])
|
|
@ -1,3 +1,6 @@
|
|||
from __future__ import unicode_literals
|
||||
# don't convert to ascii in py2.7 when creating string to return
|
||||
|
||||
crontable = []
|
||||
outputs = []
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
# don't convert to ascii in py2.7 when creating string to return
|
||||
|
||||
import os
|
||||
import pickle
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
coveralls==1.1
|
||||
ipdb==0.9.3
|
||||
ipython==4.1.2
|
||||
mock==2.0.0
|
||||
pdbpp==0.8.3
|
||||
pytest>=2.8.2
|
||||
pytest-cov==2.2.1
|
||||
|
|
10
rtmbot.py
10
rtmbot.py
|
@ -1,11 +1,13 @@
|
|||
#!/usr/bin/env python
|
||||
import sys
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import sys
|
||||
import os
|
||||
import yaml
|
||||
from rtmbot import RtmBot
|
||||
import client
|
||||
import service
|
||||
|
||||
sys.path.append(os.getcwd())
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = ArgumentParser()
|
||||
|
@ -23,7 +25,7 @@ config = None
|
|||
with open(args.config or 'rtmbot.conf', 'r') as f:
|
||||
config = yaml.load(f)
|
||||
service.slack = service.SlackService(config.get('SLACK_TOKEN'))
|
||||
bot = RtmBot(config)
|
||||
bot = client.init(config)
|
||||
try:
|
||||
bot.start()
|
||||
except KeyboardInterrupt:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
from __future__ import unicode_literals
|
||||
import sys
|
||||
import glob
|
||||
import os
|
||||
|
@ -30,10 +31,10 @@ class RtmBot(object):
|
|||
self.token = config.get('SLACK_TOKEN')
|
||||
|
||||
# set working directory for loading plugins or other files
|
||||
working_directory = os.path.dirname(sys.argv[0])
|
||||
working_directory = os.path.abspath(os.path.dirname(sys.argv[0]))
|
||||
self.directory = self.config.get('BASE_PATH', working_directory)
|
||||
if not self.directory.startswith('/'):
|
||||
path = '{}/{}'.format(os.getcwd(), self.directory)
|
||||
path = os.path.join(os.getcwd(), self.directory)
|
||||
self.directory = os.path.abspath(path)
|
||||
|
||||
# establish logging
|
||||
|
@ -47,7 +48,7 @@ class RtmBot(object):
|
|||
# initialize stateful fields
|
||||
self.last_ping = 0
|
||||
self.bot_plugins = []
|
||||
self.slack_client = None
|
||||
self.slack_client = SlackClient(self.token)
|
||||
|
||||
def _dbg(self, debug_string):
|
||||
if self.debug:
|
||||
|
@ -55,7 +56,6 @@ class RtmBot(object):
|
|||
|
||||
def connect(self):
|
||||
"""Convenience method that creates Server instance"""
|
||||
self.slack_client = SlackClient(self.token)
|
||||
self.slack_client.rtm_connect()
|
||||
|
||||
def _start(self):
|
||||
|
@ -110,13 +110,14 @@ class RtmBot(object):
|
|||
plugin.do_jobs()
|
||||
|
||||
def load_plugins(self):
|
||||
for plugin in glob.glob(self.directory + '/plugins/*'):
|
||||
plugin_dir = os.path.join(self.directory, 'plugins')
|
||||
for plugin in glob.glob(os.path.join(plugin_dir, '*')):
|
||||
sys.path.insert(0, plugin)
|
||||
sys.path.insert(0, self.directory + '/plugins/')
|
||||
for plugin in glob.glob(self.directory + '/plugins/*.py') + \
|
||||
glob.glob(self.directory + '/plugins/*/*.py'):
|
||||
sys.path.insert(0, plugin_dir)
|
||||
for plugin in glob.glob(os.path.join(plugin_dir, '*.py')) + \
|
||||
glob.glob(os.path.join(plugin_dir, '*', '*.py')):
|
||||
logging.info(plugin)
|
||||
name = plugin.split('/')[-1][:-3]
|
||||
name = plugin.split(os.sep)[-1][:-3]
|
||||
if name in self.config:
|
||||
logging.info("config found for: " + name)
|
||||
plugin_config = self.config.get(name, {})
|
||||
|
|
2
setup.py
2
setup.py
|
@ -4,7 +4,7 @@ from distutils.core import setup
|
|||
|
||||
setup(
|
||||
name='rtmbot',
|
||||
version='0.10',
|
||||
version='0.2.0',
|
||||
description='A Slack bot written in python that connects via the RTM API.',
|
||||
author='Ryan Huber',
|
||||
author_email='rhuber@gmail.com',
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
def test_example():
|
||||
assert True
|
|
@ -1,15 +1,26 @@
|
|||
from testfixtures import LogCapture
|
||||
from rtmbot.core import RtmBot
|
||||
# -*- coding: utf-8 -*-
|
||||
try:
|
||||
from unittest.mock import Mock, create_autospec
|
||||
except ImportError:
|
||||
from mock import Mock, create_autospec
|
||||
|
||||
from testfixtures import LogCapture
|
||||
from slackclient import SlackClient, _channel, _server, _util
|
||||
from rtmbot.core import RtmBot, Plugin
|
||||
|
||||
def init_rtmbot():
|
||||
''' Initializes an instance of RTMBot with some default values '''
|
||||
rtmbot = RtmBot({
|
||||
'SLACK_TOKEN': 'test-12345',
|
||||
'BASE_PATH': '/tmp/',
|
||||
'LOGFILE': '/tmp/rtmbot.log',
|
||||
'DEBUG': True
|
||||
})
|
||||
return rtmbot
|
||||
|
||||
def test_init():
|
||||
with LogCapture() as l:
|
||||
rtmbot = RtmBot({
|
||||
'SLACK_TOKEN': 'test-12345',
|
||||
'BASE_PATH': '/tmp/',
|
||||
'LOGFILE': '/tmp/rtmbot.log',
|
||||
'DEBUG': True
|
||||
})
|
||||
rtmbot = init_rtmbot()
|
||||
|
||||
assert rtmbot.token == 'test-12345'
|
||||
assert rtmbot.directory == '/tmp/'
|
||||
|
@ -18,3 +29,47 @@ def test_init():
|
|||
l.check(
|
||||
('root', 'INFO', 'Initialized in: /tmp/')
|
||||
)
|
||||
|
||||
def test_output():
|
||||
''' Test that sending a message behaves as expected '''
|
||||
rtmbot = init_rtmbot()
|
||||
|
||||
# Mock the slack_client object with Server, Channel objects and needed methods
|
||||
slackclient_mock = create_autospec(SlackClient)
|
||||
server_mock = create_autospec(_server.Server)
|
||||
|
||||
# Mock Server with channels method and correct return value
|
||||
slackclient_mock.server = server_mock
|
||||
searchlist_mock = create_autospec(_util.SearchList)
|
||||
server_mock.channels = searchlist_mock
|
||||
channel_mock = create_autospec(_channel.Channel)
|
||||
slackclient_mock.server.channels.find.return_value = channel_mock
|
||||
|
||||
rtmbot.slack_client = slackclient_mock
|
||||
|
||||
# mock the plugin object to return a sample response
|
||||
plugin_mock = create_autospec(Plugin)
|
||||
plugin_mock.do_output.return_value = [['C12345678', 'test message']]
|
||||
rtmbot.bot_plugins.append(plugin_mock)
|
||||
|
||||
rtmbot.output()
|
||||
|
||||
|
||||
# test that the output matches the expected value
|
||||
channel_mock.send_message.assert_called_with('test message')
|
||||
|
||||
# test that emoji messages work as expected
|
||||
channel_mock.reset_mock()
|
||||
plugin_mock.reset_mock()
|
||||
plugin_mock.do_output.return_value = [['C12345678', '🚀 testing']]
|
||||
rtmbot.output()
|
||||
|
||||
channel_mock.send_message.assert_called_with('🚀 testing')
|
||||
|
||||
# test that unicode messages work as expected
|
||||
channel_mock.reset_mock()
|
||||
plugin_mock.reset_mock()
|
||||
plugin_mock.do_output.return_value = [['C12345678', 'ù hœø3ö']]
|
||||
rtmbot.output()
|
||||
|
||||
channel_mock.send_message.assert_called_with('ù hœø3ö')
|
2
tox.ini
2
tox.ini
|
@ -30,4 +30,4 @@ commands=
|
|||
{toxinidir}/rtmbot.py \
|
||||
{toxinidir}/rtmbot/core.py \
|
||||
{toxinidir}/setup.py \
|
||||
{toxinidir}/doc/example-plugins
|
||||
{toxinidir}/docs/example-plugins
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue