Compare commits

...

17 Commits

Author SHA1 Message Date
Mal Hancock 004632537b Set theme jekyll-theme-minimal 2022-01-09 22:29:47 -08:00
mallory d528213c5f fix for listeners 2020-12-18 09:48:21 -08:00
mallory c3613f196a change message logic 2020-12-18 09:35:37 -08:00
mallory fdca6da624 changes to allow processing other events 2020-12-18 08:43:54 -08:00
Mal Hancock 5c9278bc63
Update __version__.py 2020-11-12 09:02:55 -08:00
Lars Kellogg-Stedman 35f5bb684b
correct UnboundLocalError in read_conf (#76)
When encountering an error, read_conf uses click.echo(...) to emit an
error message but then continue execution, causing the read_conf
method to throw an error of the form:

    UnboundLocalError: local variable 'output' referenced before assignment

By raising click.ClickException instead of calling click.echo, we
ensure that pinhook exits with an error message (and no traceback).
2020-11-12 08:10:31 -08:00
mallory 7c62e59c3a fix issue with reloading plugins in some contexts 2020-09-23 11:25:15 -07:00
mallory b995b866ec do not duplicate output function 2020-09-23 11:17:59 -07:00
Mal Hancock 9f62c6c67e
use built-in setup.py upload 2020-05-29 09:50:04 -07:00
Mal Hancock 855413a093
Create python-publish.yml 2020-05-29 09:25:06 -07:00
Mal Hancock cea5e6f855
Update FUNDING.yml 2020-05-29 09:19:04 -07:00
Mal Hancock e7b7a5b832
Update FUNDING.yml 2020-05-29 09:16:44 -07:00
Mal Hancock 0417dddf2f
Create FUNDING.yml 2020-05-29 08:37:20 -07:00
Mal Hancock a0378e09c9
version 1.9.3 (#74)
* bring TwitchBot up to date

* prevent responses from bot nick
2020-05-28 14:51:56 -07:00
Mal Hancock 5b241eee13
change example plugins to match current standards 2020-04-28 13:10:43 -07:00
Mal Hancock 0e510cc8b6
change example plugins to match current standards 2020-04-28 13:10:17 -07:00
Mallory Hancock 1c4fdb8d9e update readme to use updated decorators 2020-01-31 12:03:46 -08:00
9 changed files with 95 additions and 55 deletions

4
.github/FUNDING.yml vendored 100644
View File

@ -0,0 +1,4 @@
github: archangelic
ko_fi: archangelic
liberapay: archangelic
custom: ["https://paypal.me/archangelic", "https://cash.app/$archangelic"]

View 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

View File

@ -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!')
``` ```

View File

@ -1 +1 @@
theme: jekyll-theme-midnight theme: jekyll-theme-minimal

View File

@ -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:

View File

@ -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:

View File

@ -1 +1 @@
__version__ = '1.9.2' __version__ = '1.9.7'

View File

@ -14,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', [])
@ -36,21 +50,6 @@ class Bot(irc.bot.SingleServerIRCBot):
self.chanlist = channels self.chanlist = channels
self.bot_nick = nickname self.bot_nick = nickname
self.start_logging() self.start_logging()
self.output_message = plugin.message
self.output_action = plugin.action
self.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'
}
self.internal_commands = {self.cmd_prefix + k: v for k,v in self.internal_commands.items()} 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) plugin.load_plugins(self.plugin_dir, use_prefix=self.use_prefix_for_plugins, cmd_prefix=self.cmd_prefix)
@ -72,9 +71,9 @@ class Bot(irc.bot.SingleServerIRCBot):
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): def start_logging(self):
@ -140,7 +139,7 @@ class Bot(irc.bot.SingleServerIRCBot):
try: 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]))
except: except:
self.logger.exception('issue with join command: {}join #channel <channel key>'.format(self.cmd_prefix)) self.logger.exception('issue with join command: {}join #channel <channel key>'.format(self.cmd_prefix))
elif cmd == 'quit' and op: elif cmd == 'quit' and op:
@ -154,43 +153,43 @@ class Bot(irc.bot.SingleServerIRCBot):
elif cmd == '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))
plugin.load_plugins(self.plugin_dir, use_prefix=self.use_prefix_for_plugins, cmd_prefix=self.cmd_prefix) 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: elif cmd == 'enable' and op:
if arg in plugin.plugins: if arg in plugin.plugins:
if plugin.plugins[arg].enabled: if plugin.plugins[arg].enabled:
output = self.output_message("{}: '{}' already enabled".format(nick, arg)) output = plugin.message("{}: '{}' already enabled".format(nick, arg))
else: else:
plugin.plugins[arg].enable() plugin.plugins[arg].enable()
output = self.output_message("{}: '{}' enabled!".format(nick, arg)) output = plugin.message("{}: '{}' enabled!".format(nick, arg))
else: else:
output = self.output_message("{}: '{}' not found".format(nick, arg)) output = plugin.message("{}: '{}' not found".format(nick, arg))
elif cmd == 'disable' and op: elif cmd == 'disable' and op:
if arg in plugin.plugins: if arg in plugin.plugins:
if not plugin.plugins[arg].enabled: if not plugin.plugins[arg].enabled:
output = self.output_message("{}: '{}' already disabled".format(nick, arg)) output = plugin.message("{}: '{}' already disabled".format(nick, arg))
else: else:
plugin.plugins[arg].disable() plugin.plugins[arg].disable()
output = self.output_message("{}: '{}' disabled!".format(nick, arg)) output = plugin.message("{}: '{}' disabled!".format(nick, arg))
elif cmd == 'op' and op: elif cmd == 'op' and op:
for o in arg.split(' '): for o in arg.split(' '):
self.ops.append(o) self.ops.append(o)
output = self.output_message('{}: {} added as op'.format(nick, arg)) output = plugin.message('{}: {} added as op'.format(nick, arg))
elif cmd == 'deop' and op: elif cmd == 'deop' and op:
for o in arg.split(' '): for o in arg.split(' '):
self.ops = [i for i in self.ops if i != o] self.ops = [i for i in self.ops if i != o]
output = self.output_message('{}: {} removed as op'.format(nick, arg)) output = plugin.message('{}: {} removed as op'.format(nick, arg))
elif cmd == 'ops' and op: elif cmd == 'ops' and op:
output = self.output_message('current ops: {}'.format(', '.join(self.ops))) output = plugin.message('current ops: {}'.format(', '.join(self.ops)))
elif cmd == 'ban' and op: elif cmd == 'ban' and op:
for o in arg.split(' '): for o in arg.split(' '):
self.banned_users.append(o) self.banned_users.append(o)
output = self.output_message('{}: banned {}'.format(nick, arg)) output = plugin.message('{}: banned {}'.format(nick, arg))
elif cmd == 'unban' and op: elif cmd == 'unban' and op:
for o in arg.split(' '): for o in arg.split(' '):
self.banned_users = [i for i in self.banned_users if i != o] self.banned_users = [i for i in self.banned_users if i != o]
output = self.output_message('{}: removed ban for {}'.format(nick, arg)) output = plugin.message('{}: removed ban for {}'.format(nick, arg))
elif cmd == 'banlist': elif cmd == 'banlist':
output = self.output_message('currently banned: {}'.format(', '.join(self.banned_users))) 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, msg_type): def call_plugins(self, privmsg, action, notice, chan, cmd, text, nick_list, nick, arg, msg_type):
@ -199,7 +198,7 @@ class Bot(irc.bot.SingleServerIRCBot):
try: try:
if plugin.cmds[cmd].ops and nick not in self.ops: if plugin.cmds[cmd].ops and nick not in self.ops:
if plugin.cmds[cmd].ops_msg: if plugin.cmds[cmd].ops_msg:
output = self.output_message(plugin.cmds[cmd].ops_msg) output = plugin.message(plugin.cmds[cmd].ops_msg)
elif plugin.cmds[cmd].enabled: elif plugin.cmds[cmd].enabled:
self.logger.debug('executing {}'.format(cmd)) self.logger.debug('executing {}'.format(cmd))
output = plugin.cmds[cmd].run(self.Message( output = plugin.cmds[cmd].run(self.Message(
@ -248,7 +247,12 @@ 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
text = e.arguments[0] if nick == self.bot_nick:
pass
if e.arguments:
text = e.arguments[0]
else:
text = ''
if e.type == 'privmsg' or e.type == 'pubmsg': if e.type == 'privmsg' or e.type == 'pubmsg':
msg_type = 'message' msg_type = 'message'
else: else:
@ -313,18 +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.log_level = log_level
self.start_logging() 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)
plugin.load_plugins(self.plugin_dir) 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')
@ -333,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)

View File

@ -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)