Compare commits
39 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
004632537b | ||
|
d528213c5f | ||
|
c3613f196a | ||
|
fdca6da624 | ||
|
5c9278bc63 | ||
|
35f5bb684b | ||
|
7c62e59c3a | ||
|
b995b866ec | ||
|
9f62c6c67e | ||
|
855413a093 | ||
|
cea5e6f855 | ||
|
e7b7a5b832 | ||
|
0417dddf2f | ||
|
a0378e09c9 | ||
|
5b241eee13 | ||
|
0e510cc8b6 | ||
|
1c4fdb8d9e | ||
|
8c71f7bae0 | ||
|
9faa59a41d | ||
|
787a69757f | ||
|
23355bb699 | ||
|
9af3abd4a5 | ||
|
4da89fa815 | ||
|
ff3520f8fd | ||
|
a98c82b332 | ||
|
8d01f70a5c | ||
|
80dcc75a58 | ||
|
0bb6eb6b6a | ||
|
5b6ec72d0b | ||
|
0b8bbb4213 | ||
|
9960209c47 | ||
|
74a028dfca | ||
|
cbc9550f97 | ||
|
9797f731a7 | ||
|
1aa17eebfb | ||
|
0da04acc4e | ||
|
4ae8753095 | ||
|
fdb16287ce | ||
|
bb7ff3fdea |
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
github: archangelic
|
||||||
|
ko_fi: archangelic
|
||||||
|
liberapay: archangelic
|
||||||
|
custom: ["https://paypal.me/archangelic", "https://cash.app/$archangelic"]
|
30
.github/workflows/python-publish.yml
vendored
Normal file
30
.github/workflows/python-publish.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# This workflows will upload a Python Package using Twine when a release is created
|
||||||
|
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
|
||||||
|
|
||||||
|
name: Upload Python Package
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install setuptools wheel twine
|
||||||
|
- name: Build and publish
|
||||||
|
env:
|
||||||
|
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
|
||||||
|
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
python setup.py upload
|
@ -1 +1 @@
|
|||||||
include README.rst
|
include README.md
|
||||||
|
12
README.md
12
README.md
@ -127,14 +127,14 @@ These options are the same for both IRC and Twitch
|
|||||||
|
|
||||||
There are two types of plugins, commands and listeners. Commands only activate if a message starts with the command word, while listeners receive all messages and are parsed by the plugin for maximum flexibility.
|
There are two types of plugins, commands and listeners. Commands only activate if a message starts with the command word, while listeners receive all messages and are parsed by the plugin for maximum flexibility.
|
||||||
|
|
||||||
In your chosen plugins directory ("plugins" by default) make a python file with a function. You use the `@pinhook.plugin.register` decorator to create command plugins, or `@pinhook.plugin.listener` to create listeners.
|
In your chosen plugins directory ("plugins" by default) make a python file with a function. You use the `@pinhook.plugin.command` decorator to create command plugins, or `@pinhook.plugin.listener` to create listeners.
|
||||||
|
|
||||||
The function will need to be structured as such:
|
The function will need to be structured as such:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import pinhook.plugin
|
import pinhook.plugin
|
||||||
|
|
||||||
@pinhook.plugin.register('!test')
|
@pinhook.plugin.command('!test')
|
||||||
def test_plugin(msg):
|
def test_plugin(msg):
|
||||||
message = '{}: this is a test!'.format(msg.nick)
|
message = '{}: this is a test!'.format(msg.nick)
|
||||||
return pinhook.plugin.message(message)
|
return pinhook.plugin.message(message)
|
||||||
@ -162,15 +162,12 @@ It also contains the following IRC functions:
|
|||||||
* `action`: same as privmsg, but does a CTCP action. (i.e., `/me does a thing`)
|
* `action`: same as privmsg, but does a CTCP action. (i.e., `/me does a thing`)
|
||||||
* `notice`: send a notice
|
* `notice`: send a notice
|
||||||
|
|
||||||
You can optionally use the `@pinhook.plugin.ops` decorator to denote that a command should only be executable by a bot op.
|
You can optionally set a command to be used only by ops
|
||||||
|
|
||||||
* If you specify the optional second argument, it will be displayed when a non-op attempts to execute the command
|
|
||||||
|
|
||||||
The function will need to be structured as such:
|
The function will need to be structured as such:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@pinhook.plugin.register('!test')
|
@pinhook.plugin.command('!test', ops=True, ops_msg='This command can only be run by an op')
|
||||||
@pinhook.plugin.ops('!test', 'Only ops can run this command!')
|
|
||||||
def test_plugin(msg):
|
def test_plugin(msg):
|
||||||
return pinhook.plugin.message('This was run by an op!')
|
return pinhook.plugin.message('This was run by an op!')
|
||||||
```
|
```
|
||||||
@ -188,3 +185,4 @@ Here is a list of live bots using pinhook:
|
|||||||
|
|
||||||
* [pinhook-tilde](https://github.com/archangelic/pinhook-tilde) - fun bot for tilde.town
|
* [pinhook-tilde](https://github.com/archangelic/pinhook-tilde) - fun bot for tilde.town
|
||||||
* [adminbot](https://github.com/tildetown/adminbot) - admin helper bot for tilde.town, featuring some of the ways you can change the Bot class to suit your needs
|
* [adminbot](https://github.com/tildetown/adminbot) - admin helper bot for tilde.town, featuring some of the ways you can change the Bot class to suit your needs
|
||||||
|
* [lucibot](https://github.com/Lucidiot/lucibot)
|
||||||
|
249
README.rst
249
README.rst
@ -1,249 +0,0 @@
|
|||||||
pinhook
|
|
||||||
=======
|
|
||||||
|
|
||||||
|Supported Python versions| |Package License| |PyPI package format|
|
|
||||||
|Package development status| |With love from tilde.town|
|
|
||||||
|
|
||||||
The pluggable python framework for IRC bots and Twitch bots
|
|
||||||
|
|
||||||
- `Installation <#installation>`__
|
|
||||||
- `Creating an IRC Bot <#creating-an-irc-bot>`__
|
|
||||||
|
|
||||||
- `From Config File <#from-config-file>`__
|
|
||||||
- `From Python File <#from-python-file>`__
|
|
||||||
|
|
||||||
- `Creating a Twitch Bot <#creating-a-twitch-bot>`__
|
|
||||||
- `Creating plugins <#creating-plugins>`__
|
|
||||||
- `Examples <#examples>`__
|
|
||||||
|
|
||||||
Installation
|
|
||||||
------------
|
|
||||||
|
|
||||||
Pinhook can be installed from PyPI:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
pip install pinhook
|
|
||||||
|
|
||||||
Creating an IRC Bot
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
A pinhook bot can be initialized using the command line tool ``pinhook``
|
|
||||||
with a config file, or by importing it into a python file to extend the
|
|
||||||
base class.
|
|
||||||
|
|
||||||
From Config File
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Pinhook supports configuration files in YAML, TOML, and JSON formats.
|
|
||||||
|
|
||||||
Example YAML config:
|
|
||||||
|
|
||||||
.. code:: YAML
|
|
||||||
|
|
||||||
nickname: "ph-bot"
|
|
||||||
server: "irc.somewhere.net"
|
|
||||||
channels:
|
|
||||||
- "#foo"
|
|
||||||
- "#bar"
|
|
||||||
|
|
||||||
Required configuration keys:
|
|
||||||
|
|
||||||
- ``nickname``: (string) nickname for your bot
|
|
||||||
- ``server``: (string) server for the bot to connect
|
|
||||||
- ``channels``: (array of strings) list of channels to connect to once
|
|
||||||
connected
|
|
||||||
|
|
||||||
Optional keys:
|
|
||||||
|
|
||||||
- ``port``: (default: ``6667``) choose a custom port to connect to the
|
|
||||||
server
|
|
||||||
- ``ops``: (default: empty list) list of operators who can do things
|
|
||||||
like make the bot join other channels or quit
|
|
||||||
- ``plugin_dir``: (default: ``"plugins"``) directory where the bot
|
|
||||||
should look for plugins
|
|
||||||
- ``log_level``: (default: ``"info"``) string indicating logging level.
|
|
||||||
Logging can be disabled by setting this to ``"off"``
|
|
||||||
- ``ns_pass``: this is the password to identify with nickserv
|
|
||||||
- ``server_pass``: password for the server
|
|
||||||
- ``ssl_required``: (default: ``False``) boolean to turn ssl on or off
|
|
||||||
|
|
||||||
Once you have your configuration file ready and your plugins in place,
|
|
||||||
you can start your bot from the command line:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
pinhook config.yaml
|
|
||||||
|
|
||||||
Pinhook will try to detect the config format from the file extension,
|
|
||||||
but the format can also be supplied using the ``--format`` option.
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
$ pinhook --help
|
|
||||||
Usage: pinhook [OPTIONS] CONFIG
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-f, --format [json|yaml|toml]
|
|
||||||
--help Show this message and exit.
|
|
||||||
|
|
||||||
From Python File
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
To create the bot, just create a python file with the following:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
from pinhook.bot import Bot
|
|
||||||
|
|
||||||
bot = Bot(
|
|
||||||
channels=['#foo', '#bar'],
|
|
||||||
nickname='ph-bot',
|
|
||||||
server='irc.freenode.net'
|
|
||||||
)
|
|
||||||
bot.start()
|
|
||||||
|
|
||||||
This will start a basic bot and look for plugins in the 'plugins'
|
|
||||||
directory to add functionality.
|
|
||||||
|
|
||||||
Optional arguments are:
|
|
||||||
|
|
||||||
- ``port``: (default: ``6667``) choose a custom port to connect to the
|
|
||||||
server
|
|
||||||
- ``ops``: (default: empty list) list of operators who can do things
|
|
||||||
like make the bot join other channels or quit
|
|
||||||
- ``plugin_dir``: (default: ``"plugins"``) directory where the bot
|
|
||||||
should look for plugins
|
|
||||||
- ``log_level``: (default: ``"info"``) string indicating logging level.
|
|
||||||
Logging can be disabled by setting this to ``"off"``
|
|
||||||
- ``ns_pass``: this is the password to identify with nickserv
|
|
||||||
- ``server_pass``: password for the server
|
|
||||||
- ``ssl_required``: (default: ``False``) boolean to turn ssl on or off
|
|
||||||
|
|
||||||
Creating a Twitch Bot
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
Pinhook has a baked in way to connect directly to a twitch channel
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
from pinhook.bot import TwitchBot
|
|
||||||
|
|
||||||
bot = TwitchBot(
|
|
||||||
nickname='ph-bot',
|
|
||||||
channel='#channel',
|
|
||||||
token='super-secret-oauth-token'
|
|
||||||
)
|
|
||||||
bot.start()
|
|
||||||
|
|
||||||
This function has far less options, as the server, port, and ssl are
|
|
||||||
already handled by twitch.
|
|
||||||
|
|
||||||
Optional aguments are:
|
|
||||||
|
|
||||||
- ``ops``
|
|
||||||
- ``plugin_dir``
|
|
||||||
- ``log_level``
|
|
||||||
|
|
||||||
These options are the same for both IRC and Twitch
|
|
||||||
|
|
||||||
Creating plugins
|
|
||||||
----------------
|
|
||||||
|
|
||||||
There are two types of plugins, commands and listeners. Commands only
|
|
||||||
activate if a message starts with the command word, while listeners
|
|
||||||
receive all messages and are parsed by the plugin for maximum
|
|
||||||
flexibility.
|
|
||||||
|
|
||||||
In your chosen plugins directory ("plugins" by default) make a python
|
|
||||||
file with a function. You use the ``@pinhook.plugin.register`` decorator
|
|
||||||
to create command plugins, or ``@pinhook.plugin.listener`` to create
|
|
||||||
listeners.
|
|
||||||
|
|
||||||
The function will need to be structured as such:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
import pinhook.plugin
|
|
||||||
|
|
||||||
@pinhook.plugin.register('!test')
|
|
||||||
def test_plugin(msg):
|
|
||||||
message = '{}: this is a test!'.format(msg.nick)
|
|
||||||
return pinhook.plugin.message(message)
|
|
||||||
|
|
||||||
The function will need to accept a single argument in order to accept a
|
|
||||||
``Message`` object from the bot.
|
|
||||||
|
|
||||||
The ``Message`` object has the following attributes:
|
|
||||||
|
|
||||||
- ``cmd``: (for command plugins) the command that triggered the
|
|
||||||
function
|
|
||||||
- ``nick``: the user who triggered the command
|
|
||||||
- ``arg``: (for command plugins) all the trailing text after the
|
|
||||||
command. This is what you will use to get optional information for
|
|
||||||
the command
|
|
||||||
- ``text``: (for listener plugins) the entire text of the message
|
|
||||||
- ``channel``: the channel where the command was initiated
|
|
||||||
- ``ops``: the list of bot operators
|
|
||||||
- ``botnick``: the nickname of the bot
|
|
||||||
- ``logger``: instance of ``Bot``'s logger
|
|
||||||
- ``datetime``: aware ``datetime.datetime`` object when the ``Message``
|
|
||||||
object was created
|
|
||||||
- ``timestamp``: float for the unix timestamp when the ``Message``
|
|
||||||
object was created
|
|
||||||
- ``bot``: the initialized Bot class
|
|
||||||
|
|
||||||
It also contains the following IRC functions:
|
|
||||||
|
|
||||||
- ``privmsg``: send a message to an arbitrary channel or user
|
|
||||||
- ``action``: same as privmsg, but does a CTCP action. (i.e.,
|
|
||||||
``/me does a thing``)
|
|
||||||
- ``notice``: send a notice
|
|
||||||
|
|
||||||
You can optionally use the ``@pinhook.plugin.ops`` decorator to denote
|
|
||||||
that a command should only be executable by a bot op.
|
|
||||||
|
|
||||||
- If you specify the optional second argument, it will be displayed
|
|
||||||
when a non-op attempts to execute the command
|
|
||||||
|
|
||||||
The function will need to be structured as such:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
@pinhook.plugin.register('!test')
|
|
||||||
@pinhook.plugin.ops('!test', 'Only ops can run this command!')
|
|
||||||
def test_plugin(msg):
|
|
||||||
return pinhook.plugin.message('This was run by an op!')
|
|
||||||
|
|
||||||
The plugin function can return one of the following in order to give a
|
|
||||||
response to the command:
|
|
||||||
|
|
||||||
- ``pinhook.plugin.message``: basic message in channel where command
|
|
||||||
was triggered
|
|
||||||
- ``pinhook.plugin.action``: CTCP action in the channel where command
|
|
||||||
was triggered (basically like using ``/me does a thing``)
|
|
||||||
|
|
||||||
Examples
|
|
||||||
--------
|
|
||||||
|
|
||||||
There are some basic examples in the ``examples`` directory in this
|
|
||||||
repository.
|
|
||||||
|
|
||||||
Here is a list of live bots using pinhook:
|
|
||||||
|
|
||||||
- `pinhook-tilde <https://github.com/archangelic/pinhook-tilde>`__ -
|
|
||||||
fun bot for tilde.town
|
|
||||||
- `adminbot <https://github.com/tildetown/adminbot>`__ - admin helper
|
|
||||||
bot for tilde.town, featuring some of the ways you can change the Bot
|
|
||||||
class to suit your needs
|
|
||||||
|
|
||||||
.. |Supported Python versions| image:: https://img.shields.io/pypi/pyversions/pinhook.svg
|
|
||||||
:target: https://pypi.org/project/pinhook
|
|
||||||
.. |Package License| image:: https://img.shields.io/pypi/l/pinhook.svg
|
|
||||||
:target: https://github.com/archangelic/pinhook/blob/master/LICENSE
|
|
||||||
.. |PyPI package format| image:: https://img.shields.io/pypi/format/pinhook.svg
|
|
||||||
:target: https://pypi.org/project/pinhook
|
|
||||||
.. |Package development status| image:: https://img.shields.io/pypi/status/pinhook.svg
|
|
||||||
:target: https://pypi.org/project/pinhook
|
|
||||||
.. |With love from tilde.town| image:: https://img.shields.io/badge/with%20love%20from-tilde%20town-e0b0ff.svg
|
|
||||||
:target: https://tilde.town
|
|
@ -1 +1 @@
|
|||||||
theme: jekyll-theme-midnight
|
theme: jekyll-theme-minimal
|
@ -17,7 +17,7 @@ def build_output(rolls, modifier):
|
|||||||
output = start
|
output = start
|
||||||
return output
|
return output
|
||||||
|
|
||||||
@pinhook.plugin.register('!roll')
|
@pinhook.plugin.command('!roll')
|
||||||
def roll(msg):
|
def roll(msg):
|
||||||
matches = dicepattern.match(msg.arg)
|
matches = dicepattern.match(msg.arg)
|
||||||
if matches:
|
if matches:
|
||||||
|
@ -17,7 +17,7 @@ def build_output(rolls, modifier):
|
|||||||
output = start
|
output = start
|
||||||
return output
|
return output
|
||||||
|
|
||||||
@pinhook.plugin.register('!roll')
|
@pinhook.plugin.command('!roll')
|
||||||
def roll(msg):
|
def roll(msg):
|
||||||
matches = dicepattern.match(msg.arg)
|
matches = dicepattern.match(msg.arg)
|
||||||
if matches:
|
if matches:
|
||||||
|
@ -1 +1 @@
|
|||||||
__version__ = '1.8.0'
|
__version__ = '1.9.7'
|
||||||
|
241
pinhook/bot.py
241
pinhook/bot.py
@ -1,10 +1,11 @@
|
|||||||
|
from collections import OrderedDict
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
import imp
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import ssl
|
import ssl
|
||||||
import time
|
import time
|
||||||
import pinhook.plugin
|
|
||||||
|
from . import log
|
||||||
|
from . import plugin
|
||||||
|
|
||||||
import irc.bot
|
import irc.bot
|
||||||
|
|
||||||
@ -13,6 +14,20 @@ irc.client.ServerConnection.buffer_class.errors = 'replace'
|
|||||||
|
|
||||||
|
|
||||||
class Bot(irc.bot.SingleServerIRCBot):
|
class Bot(irc.bot.SingleServerIRCBot):
|
||||||
|
internal_commands = {
|
||||||
|
'join': 'join a channel',
|
||||||
|
'quit': 'force the bot to quit',
|
||||||
|
'reload': 'force bot to reload all plugins',
|
||||||
|
'enable': 'enable a plugin',
|
||||||
|
'disable': 'disable a plugin',
|
||||||
|
'op': 'add a user as bot operator',
|
||||||
|
'deop': 'remove a user as bot operator',
|
||||||
|
'ops': 'list all ops',
|
||||||
|
'ban': 'ban a user from using the bot',
|
||||||
|
'unban': 'remove bot ban for user',
|
||||||
|
'banlist': 'currently banned nicks'
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, channels, nickname, server, **kwargs):
|
def __init__(self, channels, nickname, server, **kwargs):
|
||||||
self.port = kwargs.get('port', 6667)
|
self.port = kwargs.get('port', 6667)
|
||||||
self.ops = kwargs.get('ops', [])
|
self.ops = kwargs.get('ops', [])
|
||||||
@ -21,9 +36,12 @@ class Bot(irc.bot.SingleServerIRCBot):
|
|||||||
self.ns_pass = kwargs.get('ns_pass', None)
|
self.ns_pass = kwargs.get('ns_pass', None)
|
||||||
self.nickserv = kwargs.get('nickserv', 'NickServ')
|
self.nickserv = kwargs.get('nickserv', 'NickServ')
|
||||||
self.log_level = kwargs.get('log_level', 'info')
|
self.log_level = kwargs.get('log_level', 'info')
|
||||||
|
self.log_file = kwargs.get('log_file', None)
|
||||||
self.server_pass = kwargs.get('server_pass', None)
|
self.server_pass = kwargs.get('server_pass', None)
|
||||||
self.cmd_prefix = kwargs.get('cmd_prefix', '!')
|
self.cmd_prefix = kwargs.get('cmd_prefix', '!')
|
||||||
self.use_prefix_for_plugins = kwargs.get('use_prefix_for_plugins', False)
|
self.use_prefix_for_plugins = kwargs.get('use_prefix_for_plugins', False)
|
||||||
|
self.disable_help = kwargs.get('disable_help', False)
|
||||||
|
self.banned_users = kwargs.get('banned_users', [])
|
||||||
if self.ssl_required:
|
if self.ssl_required:
|
||||||
factory = irc.connection.Factory(wrapper=ssl.wrap_socket)
|
factory = irc.connection.Factory(wrapper=ssl.wrap_socket)
|
||||||
irc.bot.SingleServerIRCBot.__init__(self, [(server, self.port, self.server_pass)], nickname, nickname, connect_factory=factory)
|
irc.bot.SingleServerIRCBot.__init__(self, [(server, self.port, self.server_pass)], nickname, nickname, connect_factory=factory)
|
||||||
@ -31,13 +49,12 @@ class Bot(irc.bot.SingleServerIRCBot):
|
|||||||
irc.bot.SingleServerIRCBot.__init__(self, [(server, self.port, self.server_pass)], nickname, nickname)
|
irc.bot.SingleServerIRCBot.__init__(self, [(server, self.port, self.server_pass)], nickname, nickname)
|
||||||
self.chanlist = channels
|
self.chanlist = channels
|
||||||
self.bot_nick = nickname
|
self.bot_nick = nickname
|
||||||
self.start_logging(self.log_level)
|
self.start_logging()
|
||||||
self.output_message = pinhook.plugin.message
|
self.internal_commands = {self.cmd_prefix + k: v for k,v in self.internal_commands.items()}
|
||||||
self.output_action = pinhook.plugin.action
|
plugin.load_plugins(self.plugin_dir, use_prefix=self.use_prefix_for_plugins, cmd_prefix=self.cmd_prefix)
|
||||||
self.load_plugins()
|
|
||||||
|
|
||||||
class Message:
|
class Message:
|
||||||
def __init__(self, bot, channel, nick, botnick, ops, logger, action, privmsg, notice, cmd=None, arg=None, text=None, nick_list=None):
|
def __init__(self, bot, channel, nick, botnick, ops, logger, action, privmsg, notice, msg_type, cmd=None, arg=None, text=None, nick_list=None):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.datetime = datetime.now(timezone.utc)
|
self.datetime = datetime.now(timezone.utc)
|
||||||
self.timestamp = self.datetime.timestamp()
|
self.timestamp = self.datetime.timestamp()
|
||||||
@ -50,68 +67,34 @@ class Bot(irc.bot.SingleServerIRCBot):
|
|||||||
self.action = action
|
self.action = action
|
||||||
self.privmsg = privmsg
|
self.privmsg = privmsg
|
||||||
self.notice = notice
|
self.notice = notice
|
||||||
|
self.msg_type = msg_type
|
||||||
if cmd:
|
if cmd:
|
||||||
self.cmd = cmd
|
self.cmd = cmd
|
||||||
self.arg = arg
|
self.arg = arg
|
||||||
if text:
|
if text != None:
|
||||||
self.text = text
|
self.text = text
|
||||||
if not (cmd or text):
|
if not (cmd==None or text==None):
|
||||||
raise TypeError('missing cmd or text parameter')
|
raise TypeError('missing cmd or text parameter')
|
||||||
|
|
||||||
def start_logging(self, level):
|
def start_logging(self):
|
||||||
if level == 'error':
|
self.logger = log.logger
|
||||||
|
if self.log_file:
|
||||||
|
log.set_log_file(self.log_file)
|
||||||
|
else:
|
||||||
|
log.set_log_file('{}.log'.format(self.bot_nick))
|
||||||
|
if self.log_level == 'error':
|
||||||
level = logging.ERROR
|
level = logging.ERROR
|
||||||
elif level == 'warning':
|
elif self.log_level == 'warning':
|
||||||
level = logging.WARNING
|
level = logging.WARNING
|
||||||
elif level == 'info':
|
elif self.log_level == 'info':
|
||||||
level = logging.INFO
|
level = logging.INFO
|
||||||
elif level == 'debug':
|
elif self.log_level == 'debug':
|
||||||
level = logging.DEBUG
|
level = logging.DEBUG
|
||||||
self.logger = logging.getLogger(self.bot_nick)
|
|
||||||
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(module)s - %(message)s')
|
|
||||||
# Set console logger
|
|
||||||
ch = logging.StreamHandler()
|
|
||||||
ch.setFormatter(formatter)
|
|
||||||
# Set file logger
|
|
||||||
fh = logging.FileHandler('{}.log'.format(self.bot_nick))
|
|
||||||
fh.setFormatter(formatter)
|
|
||||||
# Set levels
|
# Set levels
|
||||||
if level != "off":
|
if self.log_level != "off":
|
||||||
self.logger.setLevel(level)
|
self.logger.setLevel(level)
|
||||||
ch.setLevel(level)
|
|
||||||
fh.setLevel(level)
|
|
||||||
# Add handlers
|
|
||||||
self.logger.addHandler(ch)
|
|
||||||
self.logger.addHandler(fh)
|
|
||||||
self.logger.info('Logging started!')
|
self.logger.info('Logging started!')
|
||||||
|
|
||||||
def load_plugins(self):
|
|
||||||
# clear plugin list to ensure no old plugins remain
|
|
||||||
self.logger.info('clearing plugin cache')
|
|
||||||
pinhook.plugin.clear_plugins()
|
|
||||||
# ensure plugin folder exists
|
|
||||||
self.logger.info('checking plugin directory')
|
|
||||||
if not os.path.exists(self.plugin_dir):
|
|
||||||
self.logger.info('plugin directory {} not found, creating'.format(self.plugin_dir))
|
|
||||||
os.makedirs(self.plugin_dir)
|
|
||||||
# load all plugins
|
|
||||||
for m in os.listdir(self.plugin_dir):
|
|
||||||
if m.endswith('.py'):
|
|
||||||
try:
|
|
||||||
name = m[:-3]
|
|
||||||
self.logger.info('loading plugin {}'.format(name))
|
|
||||||
fp, pathname, description = imp.find_module(name, [self.plugin_dir])
|
|
||||||
imp.load_module(name, fp, pathname, description)
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.exception('could not load plugin')
|
|
||||||
# gather all commands and listeners
|
|
||||||
if self.use_prefix_for_plugins: # use prefixes if needed
|
|
||||||
pinhook.plugin.cmds = {self.cmd_prefix + k: v for k,v in pinhook.plugin.cmds.items()}
|
|
||||||
for cmd in pinhook.plugin.cmds:
|
|
||||||
self.logger.debug('adding command {}'.format(cmd))
|
|
||||||
for lstnr in pinhook.plugin.lstnrs:
|
|
||||||
self.logger.debug('adding listener {}'.format(lstnr))
|
|
||||||
|
|
||||||
def on_welcome(self, c, e):
|
def on_welcome(self, c, e):
|
||||||
if self.ns_pass:
|
if self.ns_pass:
|
||||||
self.logger.info('identifying with nickserv')
|
self.logger.info('identifying with nickserv')
|
||||||
@ -129,43 +112,96 @@ class Bot(irc.bot.SingleServerIRCBot):
|
|||||||
def on_action(self, c, e):
|
def on_action(self, c, e):
|
||||||
self.process_event(c, e)
|
self.process_event(c, e)
|
||||||
|
|
||||||
def call_help(self, op):
|
def call_help(self, nick, op):
|
||||||
helplist = sorted([i for i in pinhook.plugin.cmds if op or not ('ops' in pinhook.plugin.cmds[i] and pinhook.plugin.cmds[i]['ops'])])
|
cmds = {k:v.help_text for k,v in plugin.cmds.items() if not plugin.cmds[k].ops}
|
||||||
msg = ', '.join(helplist)
|
cmds.update({self.cmd_prefix + 'help': 'returns this output to private message'})
|
||||||
return self.output_message('Available commands: {}'.format(msg))
|
if op:
|
||||||
|
cmds.update({k:v.help_text for k,v in plugin.cmds.items() if plugin.cmds[k].ops})
|
||||||
|
cmds.update({k:v for k,v in self.internal_commands.items()})
|
||||||
|
helpout = OrderedDict(sorted(cmds.items()))
|
||||||
|
for h in helpout:
|
||||||
|
self.connection.privmsg(nick, '{} -- {}'.format(h, helpout[h]))
|
||||||
|
time.sleep(.1)
|
||||||
|
self.connection.privmsg(nick, 'List of listeners: {}'.format(', '.join([l for l in plugin.lstnrs])))
|
||||||
|
return None
|
||||||
|
|
||||||
def call_internal_commands(self, channel, nick, cmd, text, arg, c):
|
def call_internal_commands(self, channel, nick, cmd, text, arg, c):
|
||||||
|
if not cmd.startswith(self.cmd_prefix):
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
cmd = cmd[len(self.cmd_prefix):]
|
||||||
output = None
|
output = None
|
||||||
if nick in self.ops:
|
if nick in self.ops:
|
||||||
op = True
|
op = True
|
||||||
else:
|
else:
|
||||||
op = False
|
op = False
|
||||||
if cmd == self.cmd_prefix + 'join' and op:
|
if cmd == 'join' and op:
|
||||||
|
try:
|
||||||
c.join(*arg.split())
|
c.join(*arg.split())
|
||||||
self.logger.info('joining {} per request of {}'.format(arg, nick))
|
self.logger.info('joining {} per request of {}'.format(arg, nick))
|
||||||
output = self.output_message('{}: joined {}'.format(nick, arg.split()[0]))
|
output = plugin.message('{}: joined {}'.format(nick, arg.split()[0]))
|
||||||
elif cmd == self.cmd_prefix + 'quit' and op:
|
except:
|
||||||
|
self.logger.exception('issue with join command: {}join #channel <channel key>'.format(self.cmd_prefix))
|
||||||
|
elif cmd == 'quit' and op:
|
||||||
self.logger.info('quitting per request of {}'.format(nick))
|
self.logger.info('quitting per request of {}'.format(nick))
|
||||||
c.quit("See y'all later!")
|
if not arg:
|
||||||
|
arg = "See y'all later!"
|
||||||
|
c.quit(arg)
|
||||||
quit()
|
quit()
|
||||||
elif cmd == self.cmd_prefix + 'help':
|
elif cmd == 'help' and not self.disable_help:
|
||||||
output = self.call_help(op)
|
self.call_help(nick, op)
|
||||||
elif cmd == self.cmd_prefix + 'reload' and op:
|
elif cmd == 'reload' and op:
|
||||||
self.logger.info('reloading plugins per request of {}'.format(nick))
|
self.logger.info('reloading plugins per request of {}'.format(nick))
|
||||||
self.load_plugins()
|
plugin.load_plugins(self.plugin_dir, use_prefix=self.use_prefix_for_plugins, cmd_prefix=self.cmd_prefix)
|
||||||
output = self.output_message('Plugins reloaded')
|
output = plugin.message('Plugins reloaded')
|
||||||
|
elif cmd == 'enable' and op:
|
||||||
|
if arg in plugin.plugins:
|
||||||
|
if plugin.plugins[arg].enabled:
|
||||||
|
output = plugin.message("{}: '{}' already enabled".format(nick, arg))
|
||||||
|
else:
|
||||||
|
plugin.plugins[arg].enable()
|
||||||
|
output = plugin.message("{}: '{}' enabled!".format(nick, arg))
|
||||||
|
else:
|
||||||
|
output = plugin.message("{}: '{}' not found".format(nick, arg))
|
||||||
|
elif cmd == 'disable' and op:
|
||||||
|
if arg in plugin.plugins:
|
||||||
|
if not plugin.plugins[arg].enabled:
|
||||||
|
output = plugin.message("{}: '{}' already disabled".format(nick, arg))
|
||||||
|
else:
|
||||||
|
plugin.plugins[arg].disable()
|
||||||
|
output = plugin.message("{}: '{}' disabled!".format(nick, arg))
|
||||||
|
elif cmd == 'op' and op:
|
||||||
|
for o in arg.split(' '):
|
||||||
|
self.ops.append(o)
|
||||||
|
output = plugin.message('{}: {} added as op'.format(nick, arg))
|
||||||
|
elif cmd == 'deop' and op:
|
||||||
|
for o in arg.split(' '):
|
||||||
|
self.ops = [i for i in self.ops if i != o]
|
||||||
|
output = plugin.message('{}: {} removed as op'.format(nick, arg))
|
||||||
|
elif cmd == 'ops' and op:
|
||||||
|
output = plugin.message('current ops: {}'.format(', '.join(self.ops)))
|
||||||
|
elif cmd == 'ban' and op:
|
||||||
|
for o in arg.split(' '):
|
||||||
|
self.banned_users.append(o)
|
||||||
|
output = plugin.message('{}: banned {}'.format(nick, arg))
|
||||||
|
elif cmd == 'unban' and op:
|
||||||
|
for o in arg.split(' '):
|
||||||
|
self.banned_users = [i for i in self.banned_users if i != o]
|
||||||
|
output = plugin.message('{}: removed ban for {}'.format(nick, arg))
|
||||||
|
elif cmd == 'banlist':
|
||||||
|
output = plugin.message('currently banned: {}'.format(', '.join(self.banned_users)))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def call_plugins(self, privmsg, action, notice, chan, cmd, text, nick_list, nick, arg):
|
def call_plugins(self, privmsg, action, notice, chan, cmd, text, nick_list, nick, arg, msg_type):
|
||||||
output = None
|
output = None
|
||||||
if cmd in pinhook.plugin.cmds:
|
if cmd in plugin.cmds:
|
||||||
try:
|
try:
|
||||||
if 'ops' in pinhook.plugin.cmds[cmd] and nick not in self.ops:
|
if plugin.cmds[cmd].ops and nick not in self.ops:
|
||||||
if pinhook.plugin.cmds[cmd]['ops_msg']:
|
if plugin.cmds[cmd].ops_msg:
|
||||||
output = self.output_message(pinhook.plugin.cmds[cmd]['ops_msg'])
|
output = plugin.message(plugin.cmds[cmd].ops_msg)
|
||||||
else:
|
elif plugin.cmds[cmd].enabled:
|
||||||
self.logger.debug('executing {}'.format(cmd))
|
self.logger.debug('executing {}'.format(cmd))
|
||||||
output = pinhook.plugin.cmds[cmd]['run'](self.Message(
|
output = plugin.cmds[cmd].run(self.Message(
|
||||||
bot=self,
|
bot=self,
|
||||||
channel=chan,
|
channel=chan,
|
||||||
cmd=cmd,
|
cmd=cmd,
|
||||||
@ -177,15 +213,17 @@ class Bot(irc.bot.SingleServerIRCBot):
|
|||||||
notice=notice,
|
notice=notice,
|
||||||
botnick=self.bot_nick,
|
botnick=self.bot_nick,
|
||||||
ops=self.ops,
|
ops=self.ops,
|
||||||
logger=self.logger
|
logger=self.logger,
|
||||||
|
msg_type=msg_type
|
||||||
))
|
))
|
||||||
except Exception as e:
|
except Exception:
|
||||||
self.logger.exception('issue with command {}'.format(cmd))
|
self.logger.exception('issue with command {}'.format(cmd))
|
||||||
else:
|
else:
|
||||||
for lstnr in pinhook.plugin.lstnrs:
|
for lstnr in plugin.lstnrs:
|
||||||
|
if plugin.lstnrs[lstnr].enabled:
|
||||||
try:
|
try:
|
||||||
self.logger.debug('whispering to listener: {}'.format(lstnr))
|
self.logger.debug('whispering to listener: {}'.format(lstnr))
|
||||||
listen_output = pinhook.plugin.lstnrs[lstnr](self.Message(
|
listen_output = plugin.lstnrs[lstnr].run(self.Message(
|
||||||
bot=self,
|
bot=self,
|
||||||
channel=chan,
|
channel=chan,
|
||||||
text=text,
|
text=text,
|
||||||
@ -196,11 +234,12 @@ class Bot(irc.bot.SingleServerIRCBot):
|
|||||||
notice=notice,
|
notice=notice,
|
||||||
botnick=self.bot_nick,
|
botnick=self.bot_nick,
|
||||||
ops=self.ops,
|
ops=self.ops,
|
||||||
logger=self.logger
|
logger=self.logger,
|
||||||
|
msg_type=msg_type
|
||||||
))
|
))
|
||||||
if listen_output:
|
if listen_output:
|
||||||
output = listen_output
|
output = listen_output
|
||||||
except Exception as e:
|
except Exception:
|
||||||
self.logger.exception('issue with listener {}'.format(lstnr))
|
self.logger.exception('issue with listener {}'.format(lstnr))
|
||||||
if output:
|
if output:
|
||||||
self.logger.debug(f'returning output: {output.msg}')
|
self.logger.debug(f'returning output: {output.msg}')
|
||||||
@ -208,7 +247,16 @@ class Bot(irc.bot.SingleServerIRCBot):
|
|||||||
|
|
||||||
def process_event(self, c, e):
|
def process_event(self, c, e):
|
||||||
nick = e.source.nick
|
nick = e.source.nick
|
||||||
|
if nick == self.bot_nick:
|
||||||
|
pass
|
||||||
|
if e.arguments:
|
||||||
text = e.arguments[0]
|
text = e.arguments[0]
|
||||||
|
else:
|
||||||
|
text = ''
|
||||||
|
if e.type == 'privmsg' or e.type == 'pubmsg':
|
||||||
|
msg_type = 'message'
|
||||||
|
else:
|
||||||
|
msg_type = e.type
|
||||||
if e.target == self.bot_nick:
|
if e.target == self.bot_nick:
|
||||||
chan = nick
|
chan = nick
|
||||||
nick_list = [nick]
|
nick_list = [nick]
|
||||||
@ -238,6 +286,7 @@ class Bot(irc.bot.SingleServerIRCBot):
|
|||||||
'privmsg': c.privmsg,
|
'privmsg': c.privmsg,
|
||||||
'action': c.action,
|
'action': c.action,
|
||||||
'notice': c.notice,
|
'notice': c.notice,
|
||||||
|
'msg_type': msg_type
|
||||||
}
|
}
|
||||||
output = self.call_plugins(**plugin_info)
|
output = self.call_plugins(**plugin_info)
|
||||||
if output:
|
if output:
|
||||||
@ -248,14 +297,14 @@ class Bot(irc.bot.SingleServerIRCBot):
|
|||||||
if not output.msg:
|
if not output.msg:
|
||||||
return
|
return
|
||||||
for msg in output.msg:
|
for msg in output.msg:
|
||||||
if output.msg_type == pinhook.plugin.OutputType.Message:
|
if output.msg_type == plugin.OutputType.Message:
|
||||||
self.logger.debug('output message: {}'.format(msg))
|
self.logger.debug('output message: {}'.format(msg))
|
||||||
try:
|
try:
|
||||||
c.privmsg(chan, msg)
|
c.privmsg(chan, msg)
|
||||||
except irc.client.MessageTooLong:
|
except irc.client.MessageTooLong:
|
||||||
self.logger.error('output message too long: {}'.format(msg))
|
self.logger.error('output message too long: {}'.format(msg))
|
||||||
break
|
break
|
||||||
elif output.msg_type == pinhook.plugin.OutputType.Action:
|
elif output.msg_type == plugin.OutputType.Action:
|
||||||
self.logger.debug('output action: {}'.format(msg))
|
self.logger.debug('output action: {}'.format(msg))
|
||||||
try:
|
try:
|
||||||
c.action(chan, msg)
|
c.action(chan, msg)
|
||||||
@ -268,17 +317,24 @@ class Bot(irc.bot.SingleServerIRCBot):
|
|||||||
|
|
||||||
|
|
||||||
class TwitchBot(Bot):
|
class TwitchBot(Bot):
|
||||||
def __init__(self, nickname, channel, token, plugin_dir='plugins', log_level='info', ops=[]):
|
def __init__(self, nickname, channel, token, **kwargs):
|
||||||
|
self.port = kwargs.get('port', 6667)
|
||||||
|
self.ops = kwargs.get('ops', [])
|
||||||
|
self.plugin_dir = kwargs.get('plugin_dir', 'plugins')
|
||||||
|
self.log_level = kwargs.get('log_level', 'info')
|
||||||
|
self.log_file = kwargs.get('log_file', None)
|
||||||
|
self.server_pass = kwargs.get('server_pass', None)
|
||||||
|
self.cmd_prefix = kwargs.get('cmd_prefix', '!')
|
||||||
|
self.use_prefix_for_plugins = kwargs.get('use_prefix_for_plugins', False)
|
||||||
|
self.disable_help = kwargs.get('disable_help', False)
|
||||||
|
self.banned_users = kwargs.get('banned_users', [])
|
||||||
self.bot_nick = nickname
|
self.bot_nick = nickname
|
||||||
self.start_logging(log_level)
|
self.start_logging()
|
||||||
self.channel = channel
|
self.channel = channel
|
||||||
self.plugin_dir = plugin_dir
|
|
||||||
self.ops = ops
|
|
||||||
server = 'irc.twitch.tv'
|
|
||||||
port = 6667
|
|
||||||
self.logger.info('Joining Twitch Server')
|
self.logger.info('Joining Twitch Server')
|
||||||
irc.bot.SingleServerIRCBot.__init__(self, [(server, port, 'oauth:'+token)], nickname, nickname)
|
irc.bot.SingleServerIRCBot.__init__(self, [('irc.twitch.tv', 6667, 'oauth:'+token)], nickname, nickname)
|
||||||
self.load_plugins()
|
self.internal_commands = {self.cmd_prefix + k: v for k,v in self.internal_commands.items()}
|
||||||
|
plugin.load_plugins(self.plugin_dir, use_prefix=self.use_prefix_for_plugins, cmd_prefix=self.cmd_prefix)
|
||||||
|
|
||||||
def on_welcome(self, c, e):
|
def on_welcome(self, c, e):
|
||||||
self.logger.info('requesting permissions')
|
self.logger.info('requesting permissions')
|
||||||
@ -287,4 +343,3 @@ class TwitchBot(Bot):
|
|||||||
c.cap('REQ', ':twitch.tv/commands')
|
c.cap('REQ', ':twitch.tv/commands')
|
||||||
self.logger.info('Joining channel ' + self.channel)
|
self.logger.info('Joining channel ' + self.channel)
|
||||||
c.join(self.channel)
|
c.join(self.channel)
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ def read_conf(config, conf_format):
|
|||||||
elif config.name.endswith(('.toml', '.tml')):
|
elif config.name.endswith(('.toml', '.tml')):
|
||||||
conf_format = 'toml'
|
conf_format = 'toml'
|
||||||
else:
|
else:
|
||||||
click.echo('Could not detect file format, please supply using --format option', err=True)
|
raise click.ClickException('Could not detect file format, please supply using --format option')
|
||||||
if conf_format == 'json':
|
if conf_format == 'json':
|
||||||
import json
|
import json
|
||||||
to_json = json.loads(config.read())
|
to_json = json.loads(config.read())
|
||||||
@ -36,7 +36,7 @@ def read_conf(config, conf_format):
|
|||||||
try:
|
try:
|
||||||
import yaml
|
import yaml
|
||||||
except ImportError:
|
except ImportError:
|
||||||
click.echo('yaml not installed, please use `pip3 install pinhook[yaml]` to install', err=True)
|
raise click.ClickException('yaml not installed, please use `pip3 install pinhook[yaml]` to install')
|
||||||
else:
|
else:
|
||||||
to_yaml = yaml.load(config.read(), Loader=yaml.FullLoader)
|
to_yaml = yaml.load(config.read(), Loader=yaml.FullLoader)
|
||||||
output = schema.load(to_yaml)
|
output = schema.load(to_yaml)
|
||||||
@ -44,7 +44,7 @@ def read_conf(config, conf_format):
|
|||||||
try:
|
try:
|
||||||
import toml
|
import toml
|
||||||
except ImportError:
|
except ImportError:
|
||||||
click.echo('toml not installed, please use `pip3 install pinhook[toml]` to install', err=True)
|
raise click.ClicKException('toml not installed, please use `pip3 install pinhook[toml]` to install')
|
||||||
else:
|
else:
|
||||||
to_toml = toml.load(config.name)
|
to_toml = toml.load(config.name)
|
||||||
output = schema.load(to_toml)
|
output = schema.load(to_toml)
|
||||||
|
14
pinhook/log.py
Normal file
14
pinhook/log.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger('bot')
|
||||||
|
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(module)s - %(message)s')
|
||||||
|
# Set console logger
|
||||||
|
streamhandler = logging.StreamHandler()
|
||||||
|
streamhandler.setFormatter(formatter)
|
||||||
|
logger.addHandler(streamhandler)
|
||||||
|
|
||||||
|
def set_log_file(filename):
|
||||||
|
# Set file logger
|
||||||
|
filehandler = logging.FileHandler(filename)
|
||||||
|
filehandler.setFormatter(formatter)
|
||||||
|
logger.addHandler(filehandler)
|
@ -1,11 +1,14 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
import importlib
|
||||||
|
import os
|
||||||
|
|
||||||
|
from .log import logger
|
||||||
|
|
||||||
|
plugins = {}
|
||||||
cmds = {}
|
cmds = {}
|
||||||
lstnrs = {}
|
lstnrs = {}
|
||||||
|
|
||||||
|
|
||||||
class OutputType(Enum):
|
class OutputType(Enum):
|
||||||
Message = 'message'
|
Message = 'message'
|
||||||
Action = 'action'
|
Action = 'action'
|
||||||
@ -23,47 +26,139 @@ class Output:
|
|||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
|
class _BasePlugin:
|
||||||
|
enabled = True
|
||||||
|
logger = logger
|
||||||
|
|
||||||
|
def enable(self):
|
||||||
|
self.enabled = True
|
||||||
|
|
||||||
|
def disable(self):
|
||||||
|
self.enabled = False
|
||||||
|
|
||||||
|
|
||||||
|
class Listener(_BasePlugin):
|
||||||
|
def __init__(self, name, run=None):
|
||||||
|
self.name = name
|
||||||
|
if run:
|
||||||
|
self.run = run
|
||||||
|
self._add_listener()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _add_listener(self):
|
||||||
|
lstnrs[self.name] = self
|
||||||
|
plugins[self.name] = self
|
||||||
|
|
||||||
|
|
||||||
|
class Command(_BasePlugin):
|
||||||
|
def __init__(self, cmd, **kwargs):
|
||||||
|
self.cmd = cmd
|
||||||
|
self.help_text = kwargs.get('help_text', 'N/A')
|
||||||
|
self.ops = kwargs.get('ops', False)
|
||||||
|
self.ops_msg = kwargs.get('ops_msg', '')
|
||||||
|
self.run = kwargs.get('run', self.run)
|
||||||
|
self._add_command()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.cmd
|
||||||
|
|
||||||
|
def run(self, msg):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _enable_ops(self, ops_msg):
|
||||||
|
self.ops = True
|
||||||
|
self.ops_msg = ops_msg
|
||||||
|
|
||||||
|
def _update_plugin(self, **kwargs):
|
||||||
|
self.help_text = kwargs.get('help_text', 'N/A')
|
||||||
|
self.run = kwargs.get('run', self.run)
|
||||||
|
|
||||||
|
def _add_command(self):
|
||||||
|
cmds[self.cmd] = self
|
||||||
|
plugins[self.cmd] = self
|
||||||
|
|
||||||
|
|
||||||
def action(msg):
|
def action(msg):
|
||||||
return Output(OutputType.Action, msg)
|
return Output(OutputType.Action, msg)
|
||||||
|
|
||||||
|
|
||||||
def message(msg):
|
def message(msg):
|
||||||
return Output(OutputType.Message, msg)
|
return Output(OutputType.Message, msg)
|
||||||
|
|
||||||
|
def _add_command(command, help_text, func, ops=False, ops_msg=''):
|
||||||
def _add_plugin(command, help_text, func):
|
|
||||||
if command not in cmds:
|
if command not in cmds:
|
||||||
cmds[command] = {}
|
Command(command, help_text=help_text, ops=ops, ops_msg=ops_msg, run=func)
|
||||||
cmds[command].update({
|
else:
|
||||||
'run': func,
|
cmds[command]._update_plugin(help_text=help_text, run=func)
|
||||||
'help': help_text
|
|
||||||
})
|
|
||||||
|
|
||||||
def _ops_plugin(command, ops_msg, func):
|
def _ops_plugin(command, ops_msg, func):
|
||||||
if command not in cmds:
|
if command not in cmds:
|
||||||
cmds[command] = {}
|
Command(command, ops=True, ops_msg=ops_msg)
|
||||||
cmds[command].update({
|
else:
|
||||||
'ops': True,
|
cmds[command]._enable_ops(ops_msg)
|
||||||
'ops_msg': ops_msg,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
def _add_listener(name, func):
|
def _add_listener(name, func):
|
||||||
lstnrs[name] = func
|
Listener(name, run=func)
|
||||||
|
|
||||||
|
|
||||||
def clear_plugins():
|
def clear_plugins():
|
||||||
cmds.clear()
|
cmds.clear()
|
||||||
lstnrs.clear()
|
lstnrs.clear()
|
||||||
|
|
||||||
|
def load_plugins(plugin_dir, use_prefix=False, cmd_prefix='!'):
|
||||||
|
# i'm not sure why i need this but i do
|
||||||
|
global cmds
|
||||||
|
global plugins
|
||||||
|
global lstnrs
|
||||||
|
#check for all the disabled plugins so that we don't re-enable them
|
||||||
|
disabled_plugins = [i for i in plugins if not plugins[i].enabled]
|
||||||
|
logger.debug(disabled_plugins)
|
||||||
|
# clear plugin list to ensure no old plugins remain
|
||||||
|
logger.info('clearing plugin cache')
|
||||||
|
clear_plugins()
|
||||||
|
# ensure plugin folder exists
|
||||||
|
logger.info('checking plugin directory')
|
||||||
|
if not os.path.exists(plugin_dir):
|
||||||
|
logger.info('plugin directory {} not found, creating'.format(plugin_dir))
|
||||||
|
os.makedirs(plugin_dir)
|
||||||
|
# load all plugins
|
||||||
|
for m in os.listdir(plugin_dir):
|
||||||
|
if m.endswith('.py'):
|
||||||
|
try:
|
||||||
|
name = m[:-3]
|
||||||
|
logger.info('loading plugin {}'.format(name))
|
||||||
|
spec = importlib.machinery.PathFinder().find_spec(name, [plugin_dir])
|
||||||
|
spec.loader.load_module()
|
||||||
|
except Exception:
|
||||||
|
logger.exception('could not load plugin')
|
||||||
|
# gather all commands and listeners
|
||||||
|
if use_prefix: # use prefixes if needed
|
||||||
|
cmds = {cmd_prefix + k: v for k,v in cmds.items()}
|
||||||
|
for p in plugins:
|
||||||
|
if p in disabled_plugins:
|
||||||
|
plugins[p].disable()
|
||||||
|
for cmd in cmds:
|
||||||
|
logger.debug('adding command {}'.format(cmd))
|
||||||
|
for lstnr in lstnrs:
|
||||||
|
logger.debug('adding listener {}'.format(lstnr))
|
||||||
|
|
||||||
def register(command, help_text=None):
|
def command(command, help_text='N/A', ops=False, ops_msg=''):
|
||||||
@wraps(command)
|
@wraps(command)
|
||||||
def register_for_command(func):
|
def register_for_command(func):
|
||||||
_add_plugin(command, help_text, func)
|
_add_command(command, help_text, func, ops=ops, ops_msg=ops_msg)
|
||||||
return func
|
return func
|
||||||
return register_for_command
|
return register_for_command
|
||||||
|
|
||||||
|
def register(command, help_text='N/A'):
|
||||||
|
logger.warn('@register decorator has been deprecated in favor of @command. This will cause errors in future versions.')
|
||||||
|
@wraps(command)
|
||||||
|
def register_for_command(func):
|
||||||
|
_add_command(command, help_text, func)
|
||||||
|
return func
|
||||||
|
return register_for_command
|
||||||
|
|
||||||
def listener(name):
|
def listener(name):
|
||||||
def register_as_listener(func):
|
def register_as_listener(func):
|
||||||
@ -72,6 +167,7 @@ def listener(name):
|
|||||||
return register_as_listener
|
return register_as_listener
|
||||||
|
|
||||||
def ops(command, msg=None):
|
def ops(command, msg=None):
|
||||||
|
logger.warn('use of the @ops decorator has been deprecated in favor of using the @command decorator with the ops and ops_msg options. Use will cause errors in future versions.')
|
||||||
@wraps(command)
|
@wraps(command)
|
||||||
def register_ops_command(func):
|
def register_ops_command(func):
|
||||||
_ops_plugin(command, msg, func)
|
_ops_plugin(command, msg, func)
|
||||||
|
2
setup.py
2
setup.py
@ -41,7 +41,7 @@ here = os.path.abspath(os.path.dirname(__file__))
|
|||||||
|
|
||||||
# Import the README and use it as the long-description.
|
# Import the README and use it as the long-description.
|
||||||
# Note: this will only work if 'README.rst' is present in your MANIFEST.in file!
|
# Note: this will only work if 'README.rst' is present in your MANIFEST.in file!
|
||||||
with io.open(os.path.join(here, 'README.rst'), encoding='utf-8') as f:
|
with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f:
|
||||||
long_description = '\n' + f.read()
|
long_description = '\n' + f.read()
|
||||||
|
|
||||||
# Load the package's __version__.py module as a dictionary.
|
# Load the package's __version__.py module as a dictionary.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user