GithubHelp home page GithubHelp logo

archangelic / pinhook Goto Github PK

View Code? Open in Web Editor NEW
31.0 31.0 4.0 122 KB

the pluggable python framework for IRC bots and Twitch bots

Home Page: https://archangelic.github.io/pinhook/

License: MIT License

Python 100.00%

pinhook's Introduction

Looking for Work

archangelic

making bot frameworks in my spare time

interested in irc, go, python

pinhook's People

Contributors

archangelic avatar importantchoice avatar juliaria08 avatar larsks avatar lucidiot avatar russellchamp avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

pinhook's Issues

roll ops decorator into the command decorator

Currently adding an ops command requires 2 decorators, but this option could be added using a options in the command decorator now that plugins are objects.

@pinhook.plugin.command('!command', ops=True, ops_msg='this is an ops message')

Use Enum for Output types

Instead of hardcoded 'action' and 'message' strings here, use a Python Enum. The enum34 can provide backporting to Python versions before 3.4.

This a really easy issue, feel free to assign it to me if you want. :p

Class-based plugins

Implement class-based plugins for all features going forward.

Spec for class-based plugins:

  • ability to give help info back to bot
  • abstract way to send message to calling channel or other channel without returning
  • multiple command and listener support in single class (via method decorators)
  • on_mention decorator
  • cmd and listener plugins should meet these specs
  • ability to inform bot of aliases for commands
  • editable settings (possible work on bot to get this working)

Add logging

The bot should be logging events based on a level when the bot is initialized. Plugins should also be able to add arbitrary information to the log as well.

Allow disabling or overriding the !help command

By default, a '!help' command is registered for all bots that enumerate all the plugins that they respond to. I would like a way to either disable or override the default '!help' command.
This is useful for the cases where I do not want all my plugin functionality made public or do not want my bot to respond to a !help

XMPP support

tilde.town now has profanity installed, and that made me think about having yet another backend on pinhook!

implement verbose help output

!help should bring up help info for a command, if any is provided

this would need to be implemented on both the plugin side and on the bot side

  • create option to add verbose help text in plugin decorator
  • store help info in bot class alongside plugin
  • create method to output verbose help text to user over notify rather than message (or possibly over privmsg)

plugins vulnerable to arbitrary command injection

When plugins handle text and send it to pinhook.plugin.message it is vulnerable to command injection.

for instance text i want\r\nQUIT :injected quit message

The bot runs this without validation, which is a security concern when dealing with untrusted text.

Add a decorator for "pipe"able commands

One thing I've seen some bot creators do is create commands that can "chain" or "pipe" into each other. This would use the output of one command as the input for the next one.
As an example, a bot may support the commands "!rainbow" and "!superhero". Chaining the two commands together (like "!superhero | !rainbow") would result in a random super hero printed in a rainbow color; the output from "!superhero" would be used to call "!rainbow ".
The decorator would go on any plugin that is allowed to be "piped" into another. In this case, it would be on "!superhero" and the decorator would mean that only the user input up to the first pipe '|' would be sent to that command.
I'd be interested in doing this at some point in the future!

MSN support

MSN is not dead! An MSN server called Escargot is being developed on GitLab (official website here). It supports Windows Messenger 1.0 up to WLM 2009 and Yahoo! Messenger 5.0 to 5.5 currently, and plans are made for bridges to XMPP and Matrix. I am currently submitting this issue while logged on on WLM 8.5.

There are some MSNP implementations in Python, but obviously we're talking about obsolete code: msnp.py was last updated in 2004, and I found a bot implementation made in 2010 that uses a module last updated in 2001. But it does not look that hard to start maintaining that code again, and I might just get started on that if I ever have the time, and Escargot is designed to work with any version of the protocol, so that helps.

Use argparse to parse !command arguments?

When creating command-line scripts, the cool way to parse command-line arguments is to use the argparse module. That could automatically generate help text (like for #19) with details about arguments. This should be easy to do with class-based plugins (#27) by using a way similar to how Django commands are designed:

import pinhook.plugin

class HelloPlugin(pinhook.plugin.BasePlugin):
    help = 'Say hello to someone!'

    def add_arguments(self, parser):
        parser.add_argument(
            'nick',
            nargs='?',
            default=None,
            help='Nickname of the user to say hello to',
        )

    def handle(self, msg, **options):
        message = 'Hello {}!'.format(options.get('nick', msg.nick))
        return pinhook.plugin.message(message)

!reload does not reload plugin dependencies

I treat my plugin files as thin shims that just import util methods. Running a !reload against bot commands will reload the plugin file but not plugin dependencies.
Eg. I might have "./plugins/foo.py" that contains from util import doFoo. Running a !reload will reload "foo.py" but not my "doFoo" module. In order to reload the "doFoo" module, I need to do a full bot restart.

Cheers!

Write better documentation

So far, all I have is a poorly made tutorial. This should be expanded with more examples and possibly technical details of how plugins and bot initialization work.

remove removed plugins on reload

If I remove a plugin (or the plugin can't be imported after a change) and !reload all plugins, the old version of the plugin is still available.

So, I guess, the easiest fix would be to clear the whole cmds list and refilled it again on reload.

events

events. like on join, on kick, on leave, on ban, op, on deop etc etc

Move plugin loading

Plugin loading should be the domain of pinhook.plugin. This should be a function to call in that module, further removing the need to have the plugins and bot be linked too heavily.

Should also come with implementing #6

Clarify Bot Extension Strategies

Pinhook is designed to be extended by using command and listener plugins. Sometimes, however, it is natural to extend the Bot class in other ways. Currently I am struggling with appending a natural language processing model to the Bot class.

My current attempt involves creating a new class that instantiates a Pinhook Bot and a Model instance during init; it looks something like this:

from pinhook.bot import Bot

from mybot.nlp.model import Model


class Mybot:

    def __init__(self, **config):

        self.config = config
        self.model = Model()
        self.bot = Bot(
            use_prefix_for_plugins=True,
            **self.config
        )
        self.bot.start()

But now I want to create a listener which has access to the model attribute, and it is not clear how. I considered trying instead to extend the Bot class directly, but plugins do not have access to self anyway so this doesn't help me.

Any ideas on how I could work around this sort of issue?

Add rate limiting of message to avoid channel flooding

Many channels have anti flooding settings that will cause an overly-chatty bot to get kicked. Eg, 5 lines within 10 seconds.
Add either an optional delay timer or max msg lines or rate limit or something to avoid getting kicked for flooding.
Eg Setting flood_limit 5 and flood_time 10 will cause the bot to rate limit itself whenever pinhook.plugin.message receives more than 5 lines in the array. It could print the first 5 lines then wait 10 seconds or only print a message every 2 seconds.

Add ops annotation to plugin

Allow a plugin to be registered as only runnable by the bot's op. it would add an automatic check of if msg.nick not in msg.ops; return before calling the plugin. maybe something like @pinhook.plugin.ops($message_what_gets_printed_if_a_non_op_tries = "") where None will result in no message.
These op commands should probably also not be included in !help (maybe unless called by an op?)

AttributeError: 'TwitchBot' object has no attribute 'log_file'

Using the following code:

bot = TwitchBot(
    nickname="pyllybot",
    channel="#siinacutie",
    token=secret
).start()

I receive the error:

Traceback (most recent call last):
  File "./pyllybot.py", line 18, in <module>
    bot = TwitchBot(
  File "/opt/virtual_env/pyllybot/lib/python3.8/site-packages/pinhook/bot.py", line 319, in __init__
    self.start_logging()
  File "/opt/virtual_env/pyllybot/lib/python3.8/site-packages/pinhook/bot.py", line 82, in start_logging
    if self.log_file:
AttributeError: 'TwitchBot' object has no attribute 'log_file'

Namespaced editable settings

Create way for settings for plugins to be changed from within IRC
for example we would use a reserved command !set to change the setting for the dice plugin

<archangelic> !set dice.max_size 100
<pinhook> archangelic: setting dice.max_size changed to 100

Possible ways to implement this:

  • class based plugin feature
  • adding plugin naming to claim namespaces in decorator method

Command Prefix Parameter

If multiple pinhook bots sharing ops are in the same room, conflicts could arise if commands are shared. While plugin command prefixes are controlled by the user, the prefixes for the builtins !join, !help, !reload and most critically !quit are hardcoded.

Doing something like:

class Bot(irc.bot.SingleServerIRCBot):
    def __init__(self, channels, nickname, server, cmd_prefix='!', **kwargs):

        ...    

       self.cmd_prefix = cmd_prefix

        ...

    def call_internal_commands(self, channel, nick, cmd, text, arg, c):
        output = None
        if nick in self.ops:
            op = True
        else:
            op = False
        if cmd == self.cmd_prefix + 'join' and op:
            c.join(*arg.split())
            self.logger.info('joining {} per request of {}'.format(arg, nick))
            output = self.output_message('{}: joined {}'.format(nick, arg.split()[0]))
        elif cmd == self.cmd_prefix + 'quit' and op:
            self.logger.info('quitting per request of {}'.format(nick))
            c.quit("See y'all later!")
            quit()
        elif cmd == self.cmd_prefix + 'help':
            output = self.call_help()
        elif cmd == self.cmd_prefix + 'reload' and op:
            self.logger.info('reloading plugins per request of {}'.format(nick))
            self.load_plugins()
            output = self.output_message('Plugins reloaded')
        return output

This would resolve this at least for the builtins, but I am not sure how you would like to handle plugin commands. Ideally, this prefix would be applied to them as well, but of course this change is not backwards compatible with existing plugins (which is why I didn't submit a PR).

Add badges for PyPI package

Add Shields.io badges to indicate the package is on PyPI and supports specific versions. This is very helpful to anyone looking for a bot framework, finding pinhook and not knowing anything about it.

[![Supported Python versions](https://img.shields.io/pypi/pyversions/pinhook.svg)](https://pypi.org/project/pinhook) [![Package License](https://img.shields.io/pypi/l/pinhook.svg)](https://github.com/archangelic/pinhook/blob/master/LICENSE) [![PyPI package format](https://img.shields.io/pypi/format/pinhook.svg)](https://pypi.org/project/pinhook) [![Package development status](https://img.shields.io/pypi/status/pinhook.svg)](https://pypi.org/project/pinhook) [![With love from tilde.town](https://img.shields.io/badge/with%20love%20from-tilde%20town-e0b0ff.svg)](https://tilde.town)

Supported Python versions Package License PyPI package format Package development status With love from tilde.town

The Status badge will need a development status classifier in setup.py, one of these:

Development Status :: 1 - Planning
Development Status :: 2 - Pre-Alpha
Development Status :: 3 - Alpha
Development Status :: 4 - Beta
Development Status :: 5 - Production/Stable
Development Status :: 6 - Mature
Development Status :: 7 - Inactive

Add tests

I feel like, in order for this to be a solid project, it will need some tests. I am way out of my depth with writing tests. So any help would be greatly appreciated!

Ban users by nick

There should be an internal command that ops can add a user by nick that cannot interact with a bot (commands and messages to listeners should be ignored)

Should be paired with an unban command.

Abstraction for multiple backends

Instead of having IRCBot and TwitchBot classes, have a single Bot class that takes a backend as a parameter. That could allow to split pinhook into multiple packages, like pinhook-irc, pinhook-twitch, etc.

The backend could be a class or a string pointing to a class (to help with import troubles). A backend would be initialized with the Bot instance as a parameter, and started as soon as the bot is ready to accept events; then, the backend deals with everything and fires on_* events on the bot.

It could be possible to use Abstract Base Classes to enforce implementing specific methods if needed.

Messages would probably need to become even more abstract (or have more possibilities than just action and message) to provide support for backends from different paradigms (like toots or facebook privacy settings).

Messages silently fail to send if over 512 bytes

Sometimes a plugin will return a message over 512 bytes. Need to find a good solution to figure out length of bytestring and split it if necessary into multiple blocks to send back to the message sending function.

What I need to implement:

  • Check for size of message converted to bytes
  • log failures due to byte size

This is all the framework should handle. Plugins should be responsible for cleaning up messages sent to irc.

Create github page

The project should eventually have a website, and github pages should be totally fine for this.

Dynamic enable/disable plugins

Currently there is no way to disable or enable specific plugins. The bot loads literally every plugin in the folder.

So, in order to do this, plugins might need to declare names and we will need to set an op-only command for enabling and disabling. As well as some checks to make sure code is not executed when a plugin is disabled.

TwitchBot does not ignore own nick if used by another bot

I have two bots using the same bot account on Twitch, with different OAuth tokens.

When pinhook sends messages, it does not trigger its own listener, which is good. If the other bot sends a message using the same nick, it causes pinhook to trigger listeners and commands even when pinhook's bot name is added to banned_users:

I can work around this by adding the following code, but it is tedious to do it for every plugin:

if msg.nick != "pyllybot":
    # plugin code here

Ideally, the bot should always be ignoring its own nick.

Note: pinhook has the command !ping and the other bot has !ding

Here is a log generate for this issue:

2020-05-22 15:36:40,393 - INFO - bot - Logging started!
2020-05-22 15:36:40,393 - INFO - bot - Joining Twitch Server
2020-05-22 15:36:40,393 - DEBUG - plugin - []
2020-05-22 15:36:40,393 - INFO - plugin - clearing plugin cache
2020-05-22 15:36:40,393 - INFO - plugin - checking plugin directory
2020-05-22 15:36:40,393 - INFO - plugin - loading plugin test_plugin
2020-05-22 15:36:40,393 - DEBUG - plugin - adding command !ping
2020-05-22 15:36:40,393 - DEBUG - plugin - adding listener echo
2020-05-22 15:36:40,393 - DEBUG - pyllybot - Banned users: pyllybot, buttsbot
2020-05-22 15:36:41,034 - INFO - bot - requesting permissions
2020-05-22 15:36:41,034 - INFO - bot - Joining channel #siinacutie
2020-05-22 15:36:45,228 - DEBUG - bot - Message info: channel: #siinacutie, nick: siinacutie, cmd: !ping, text: !ping
2020-05-22 15:36:45,228 - DEBUG - bot - executing !ping
2020-05-22 15:36:45,228 - DEBUG - bot - returning output: ['is pinged!']
2020-05-22 15:36:45,228 - DEBUG - bot - sending output: ['is pinged!']
2020-05-22 15:36:45,228 - DEBUG - bot - output action: is pinged!
2020-05-22 15:36:49,415 - DEBUG - bot - Message info: channel: #siinacutie, nick: siinacutie, cmd: !ding, text: !ding
2020-05-22 15:36:49,415 - DEBUG - bot - whispering to listener: echo
2020-05-22 15:36:49,614 - DEBUG - bot - Message info: channel: #siinacutie, nick: pyllybot, cmd: Dong!, text: Dong!
2020-05-22 15:36:49,615 - DEBUG - bot - whispering to listener: echo
2020-05-22 15:36:49,615 - INFO - test_plugin - Echoing user: pyllybot
2020-05-22 15:36:49,615 - DEBUG - bot - returning output: ['Dong!']
2020-05-22 15:36:49,615 - DEBUG - bot - sending output: ['Dong!']
2020-05-22 15:36:49,615 - DEBUG - bot - output message: Dong!

Add persistent configuration into Bot

The bot needs persistent storage, but not enough to justify a database (see #51). I believe the solution is to have a config file passed to the Bot object that it can read and write to. This can also help in doing config-only init (#39)

So the only decision i need to make is to standardize the format for config:

  • ini - a little obtuse, don't really care for this option
  • yaml
  • toml
  • json - hard to hand edit

Any thoughts?

Cannot connect using server password

Actually a lot of authentication stuff is probably not well implemented, but I really want to make a guide to using this for twitch bots and I can't without this functionality.

make message object more generic

In order to implement #31, the Message class sent to plugins needs to be more generic. Currently, the properties are very irc-centric.

What properties are necessary and what should they be named?

First thoughts:
msg.nick -> msg.who

Allow bot to identify actions/events

Pass an 'event' value to Message handlers to allow listeners to identify when a user has performed a /me action.
I've already played with this locally so you can bop the issue back to me if you like the idea.

Remove dependencies for irc connection

Currently this relies on the irc library, which is very nice, but a bit much for what we need in a simple bot framework. Eventually, i would like to replace this dependency with the bare minimum necessary to get a bot connected to irc.

Pinhook fails to start without plugin directory

When I use the following:

import pinhook.bot
bot = pinhook.bot.Bot(
    channels=['#channel'],
    nickname='pin-test',
    server='my.server',
    port=6667,
    ops='SinaCutie',
)
bot.start()

I receive:

Traceback (most recent call last):
  File "bot.py", line 9, in <module>
    ops='SinaCutie',
  File "/home/sina/.virtualenvs/pinhook/lib/python3.5/site-packages/pinhook-1.1.0-py3.5.egg/pinhook/bot.py", line 29, in __init__
  File "/home/sina/.virtualenvs/pinhook/lib/python3.5/site-packages/pinhook-1.1.0-py3.5.egg/pinhook/bot.py", line 50, in load_plugins
FileNotFoundError: [Errno 2] No such file or directory: 'plugins'

Running mkdir plugins alleviates the problem.

Should pinhook be creating the directory or gracefully exiting with an explanation that plugins are needed?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.