johnmaguire / cardinal Goto Github PK
View Code? Open in Web Editor NEWA Python IRC bot, designed to make adding functionality quick and simple. (est. 2013)
License: MIT License
A Python IRC bot, designed to make adding functionality quick and simple. (est. 2013)
License: MIT License
I would like to add support for, for example, grabbing the amount of time, title, and uploader of YouTube videos, the time and track artist on SoundCloud links, etc.
For example, UserA messages "Cardinal: tell UserB check your email". When UserB joins a channel Cardinal is in, Cardinal will send a private message to UserA containing "UserB wanted me to tell you: check your email".
The notes
plugin currently supports adding notes and reading notes, but not deleting them.
This'll make Cardinal just a little more Pythonic.
As an example of the protocol system, create a new Slack protocol driver.
I have to ideas for syntax... either:
Cardinal: Android is a mobile operating system developed by Google.
Cardinal: what is android?
whoami: Android is a mobile operating system developed by Google.
Alternatively...
.dadd android a mobile operating system developed by Google
.define android
whoami: a mobile operating system developed by Google
With the second syntax, it may be beneficial to also make lookups easier with alternative syntax of:
.android
whoami: a mobile operating system developed by Google
If a command exists by the definition name, it would default to the command rather than the definition (with .define
serving as the only way to read the definition.)
List up to five artists that are shared between two users when using the Last.fm plugin's compare command
e.g. Number of times reloaded, bot uptime, connected time, latest git commit (for version).
The goal is to move the interface that plugins are provided (e.g. through the cardinal
param) outside of the CardinalBot
class. Separating this out from the protocol-specific logic will make the ability to write different protocol drivers simpler.
Ideally, I would like to see the following features implemented:
This is useful for things like disabling the URL plugin in channels where another bot has already taken care of the service. Things to take into account:
Currently, if a plugin that defined an event is reloaded, it won't be able to re-define its event, since the EventManager will believe it still exists. Similarly, if an event that has registered a callback is reloaded, when EventManager tries to call it, Python will cry.
This blocks milestone Crimson (version 2.0).
e.g. .addnote example=< example> This is an example message.
Then when the user example
joins the channel Cardinal sends < example> This is an example message.
Happened on first connect. Probably because a MODE gets sent to the client with the user being the name of the network (e.g. irc.darchoods.net), which can't be split with *!*@*
.
Unhandled Error
Traceback (most recent call last):
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/protocols/policies.py", line 120, in dataReceived
self.wrappedProtocol.dataReceived(data)
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 2430, in dataReceived
basic.LineReceiver.dataReceived(self, data.replace('\r', ''))
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/protocols/basic.py", line 571, in dataReceived
why = self.lineReceived(line)
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 2438, in lineReceived
self.handleCommand(command, prefix, params)
--- <exception caught here> ---
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 2482, in handleCommand
method(prefix, params)
File "/home/john/files/Cardinal-DH/cardinal/bot.py", line 202, in irc_MODE
(user.group(1), user.group(2), user.group(3), channel, mode)
exceptions.AttributeError: 'NoneType' object has no attribute 'group'
Looks like the Plugin Manager didn't get instantiated correctly, as well as the irc.invite
method never got de-registered when Cardinal disconnected. (Basic guess, would have to look deeper in the code to verify.)
2015-03-06 02:25:51,214 - cardinal.bot - INFO - Could not connect ([Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionRefusedError'>: Connection was refused by
other side: 111: Connection refused.
]), retrying in 300 seconds
2015-03-06 02:30:52,594 - cardinal.bot - INFO - Signed on as Cardinal
2015-03-06 02:30:52,595 - cardinal.bot - INFO - Attempting to identify with NickServ
Unhandled Error
Traceback (most recent call last):
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/protocols/policies.py", line 120, in dataReceived
self.wrappedProtocol.dataReceived(data)
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 2430, in dataReceived
basic.LineReceiver.dataReceived(self, data.replace('\r', ''))
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/protocols/basic.py", line 571, in dataReceived
why = self.lineReceived(line)
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 2438, in lineReceived
self.handleCommand(command, prefix, params)
--- <exception caught here> ---
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 2482, in handleCommand
method(prefix, params)
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 1846, in irc_RPL_WELCOME
self.signedOn()
File "/home/john/files/Cardinal-DH/cardinal/bot.py", line 85, in signedOn
self.event_manager.register("irc.invite", 2)
File "/home/john/files/Cardinal-DH/cardinal/plugins.py", line 674, in register
raise EventAlreadyExistsError("Event already exists: %s" % name)
cardinal.exceptions.EventAlreadyExistsError: Event already exists: irc.invite
2015-03-06 02:30:52,608 - cardinal.plugins - INFO - Calling callbacks for event: irc.mode
2015-03-06 02:30:52,680 - cardinal.plugins - INFO - Calling callbacks for event: irc.notice
2015-03-06 02:30:52,774 - cardinal.plugins - INFO - Calling callbacks for event: irc.notice
2015-03-06 02:30:52,851 - cardinal.plugins - INFO - Calling callbacks for event: irc.privmsg
Unhandled Error
Traceback (most recent call last):
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/protocols/policies.py", line 120, in dataReceived
self.wrappedProtocol.dataReceived(data)
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 2430, in dataReceived
basic.LineReceiver.dataReceived(self, data.replace('\r', ''))
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/protocols/basic.py", line 571, in dataReceived
why = self.lineReceived(line)
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 2438, in lineReceived
self.handleCommand(command, prefix, params)
--- <exception caught here> ---
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 2482, in handleCommand
method(prefix, params)
File "/home/john/files/Cardinal-DH/cardinal/bot.py", line 139, in irc_PRIVMSG
self.plugin_manager.call_command(user, channel, message)
exceptions.AttributeError: 'NoneType' object has no attribute 'call_command'
2015-03-07 15:06:26,477 - cardinal.plugins - INFO - Calling callbacks for event: irc.notice
2015-03-12 20:13:51,596 - cardinal.bot - INFO - Connection lost ([Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionDone'>: Connection was closed cleanly.
]), reconnecting in 10 seconds.
2015-03-12 20:14:08,986 - cardinal.bot - INFO - Signed on as Cardinal
2015-03-12 20:14:08,986 - cardinal.bot - INFO - Attempting to identify with NickServ
Unhandled Error
Traceback (most recent call last):
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/protocols/policies.py", line 120, in dataReceived
self.wrappedProtocol.dataReceived(data)
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 2430, in dataReceived
basic.LineReceiver.dataReceived(self, data.replace('\r', ''))
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/protocols/basic.py", line 571, in dataReceived
why = self.lineReceived(line)
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 2438, in lineReceived
self.handleCommand(command, prefix, params)
--- <exception caught here> ---
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 2482, in handleCommand
method(prefix, params)
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 1846, in irc_RPL_WELCOME
self.signedOn()
File "/home/john/files/Cardinal-DH/cardinal/bot.py", line 85, in signedOn
self.event_manager.register("irc.invite", 2)
File "/home/john/files/Cardinal-DH/cardinal/plugins.py", line 674, in register
raise EventAlreadyExistsError("Event already exists: %s" % name)
cardinal.exceptions.EventAlreadyExistsError: Event already exists: irc.invite
2015-03-12 20:14:08,992 - cardinal.plugins - INFO - Calling callbacks for event: irc.mode
2015-03-12 20:14:46,450 - cardinal.plugins - INFO - Calling callbacks for event: irc.notice
I executed $python cardinal.py
Its giving me
from twisted.words.protocols import irc
ImportError: No module named words.protocols
The point of the module would be to allow the user to check what's the current time in some timezone. For example, if one would enter the following:
.time +1
it would return the current time of the GMT+1 timezone.
.time
without any arguments defaults to GMT +/- 0
.time -5
would return the time of GMT-5.
Documentation needs to be updated to describe the new plugin system.
May involve adding to the core of the bot for ignoring commands by users, but the functionality should be in the plugin so as to allow easy editing, whitelists, etc.
2015-04-27 00:26:25,770 - cardinal.plugins - INFO - Calling callbacks for event: irc.privmsg
Unhandled Error
Traceback (most recent call last):
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/protocols/policies.py", line 120, in dataReceived
self.wrappedProtocol.dataReceived(data)
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 2430, in dataReceived
basic.LineReceiver.dataReceived(self, data.replace('\r', ''))
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/protocols/basic.py", line 571, in dataReceived
why = self.lineReceived(line)
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 2438, in lineReceived
self.handleCommand(command, prefix, params)
--- <exception caught here> ---
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 2482, in handleCommand
method(prefix, params)
File "/home/john/files/Cardinal-DH/cardinal/bot.py", line 144, in irc_PRIVMSG
self.plugin_manager.call_command(user, channel, message)
File "/home/john/files/Cardinal-DH/cardinal/plugins.py", line 630, in call_command
command(self.cardinal, user, channel, message)
File "/home/john/files/Cardinal-DH/plugins/notes/plugin.py", line 118, in get_note
content = self._get_note_from_db(title)
File "/home/john/files/Cardinal-DH/plugins/notes/plugin.py", line 134, in _get_note_from_db
if not result[0]:
exceptions.TypeError: 'NoneType' object has no attribute '__getitem__'
Happened when attempting .help setlastfm
Unhandled Error
Traceback (most recent call last):
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/protocols/policies.py", line 120, in dataReceived
self.wrappedProtocol.dataReceived(data)
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 2430, in dataReceived
basic.LineReceiver.dataReceived(self, data.replace('\r', ''))
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/protocols/basic.py", line 571, in dataReceived
why = self.lineReceived(line)
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 2438, in lineReceived
self.handleCommand(command, prefix, params)
--- <exception caught here> ---
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 2482, in handleCommand
method(prefix, params)
File "/home/john/files/Cardinal-DH/cardinal/bot.py", line 139, in irc_PRIVMSG
self.plugin_manager.call_command(user, channel, message)
File "/home/john/files/Cardinal-DH/cardinal/plugins.py", line 613, in call_command
command(self.cardinal, user, channel, message)
File "/home/john/files/Cardinal-DH/plugins/help/plugin.py", line 89, in help
help = self._get_command_help(cardinal, command)
File "/home/john/files/Cardinal-DH/plugins/help/plugin.py", line 39, in _get_command_help
for name, module in cardinal.loaded_plugins.items():
exceptions.AttributeError: CardinalBot instance has no attribute 'loaded_plugins'
Here's how I'd like the plugin system to work:
Each plugin is loaded up and its constructor is called. The constructor will register commands based on what type of action is called (for example an ACTION, PRIVMSG, NICK, etc.), a regex or a command (with the command symbol prior to it) and a callback. On each action received by the bot, it will go through the list of callbacks, and hit any that are associated.
Plugins will have three possible components: plugin
, which is necessary, api
, which exposes some features to other plugins through a cardinal.api.<plugin_name>
object, and a config
which is where configuration data goes. All other plugins will have access to the APIs of currently enabled plugins and can even depend on other plugins in order to work.
Plugins will be also able to register with the bot hooks for certain functionality. For example, the urls
plugin, which looks up URLs and returns the title of the page would allow other plugins to hook in, and override the default functionality.
For example, in the constructor, the urls
plugin would call something like cardinal.register_hook('urls')
. This would let Cardinal know that there is a hook other plugins can register to named urls
. From the constructors of other plugins, you could call cardinal.register_hook_callback('urls', '/http:\/\/youtube.com\/watch?v=(.+)/', self.urls_callback)
. When the urls
plugin calls cardinal.handle_callbacks('urls', url)
, Cardinal will check if any of the callbacks have regexes matching the url
variable and if so will return true, letting the urls
plugin know that some other functionality was called (allowing urls
to no-op). If none were found matching, it will return false.
Lastly, there should be a plugin providing configuration support. This would allow plugins to have some on-the-fly configuration (such as set urls.detection off
to turn detection of URLs off.)
Not sure if this happens 100% of times, but if the connection to the IRC network is killed, Cardinal seems to hang, rather than reconnecting in 15 seconds as it should. C-c also will not kill it, and it has to be killed with a "kill -9" command.
It's also worth taking a look at the PluginManager
and seeing whether weak references may be useful there as well. In the case of the EventManager
, callbacks to events should be registered as weak references, so that if the plugin that registered the callback doesn't remove its callback for some reason, it doesn't prevent the garbage collector from handling the plugin (and this would help to prevent accidental duplicate callbacks for reloaded plugins.)
It would be cool to make a plugin that can accept snaps and send them to a configured IRC channel upon receiving them.
I think, at least initially, this will be pretty simple, logic similar to:
if link_to_parse == last_link_parsed and last_link_parsed_time < 5 minutes ago:
return
This would make talking about sites like goo.gl and Last.fm a lot less annoying.
del() is unreliable and can't be trusted to be run by Python
CardinalBot.py contains all the main bot logic.
It would be much easier and nicer to read if it was split into pieces. I would like to start using proper logging, with the Python logging
library, and a separate class for plugin logic at the very least.
This would also be a good time to start making things PEP-8 compatible.
logging
for console outputProbably the same fix as 8141a89.
2015-02-27 15:00:47,614 - cardinal.plugins - INFO - Calling callbacks for event: irc.privmsg
Unhandled Error
Traceback (most recent call last):
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/protocols/policies.py", line 120, in dataReceived
self.wrappedProtocol.dataReceived(data)
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 2430, in dataReceived
basic.LineReceiver.dataReceived(self, data.replace('\r', ''))
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/protocols/basic.py", line 571, in dataReceived
why = self.lineReceived(line)
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 2438, in lineReceived
self.handleCommand(command, prefix, params)
--- <exception caught here> ---
File "/home/john/files/Cardinal-DH/local/lib/python2.7/site-packages/twisted/words/protocols/irc.py", line 2482, in handleCommand
method(prefix, params)
File "/home/john/files/Cardinal-DH/cardinal/bot.py", line 139, in irc_PRIVMSG
self.plugin_manager.call_command(user, channel, message)
File "/home/john/files/Cardinal-DH/cardinal/plugins.py", line 611, in call_command
command(self.cardinal, user, channel, message)
File "/home/john/files/Cardinal-DH/plugins/urls/plugin.py", line 91, in get_title
title = str(h.unescape(title))
exceptions.UnicodeEncodeError: 'ascii' codec can't encode character u'\xb7' in position 12: ordinal not in range(128)
Currently, if the server itself has a password (not NickServ), there's no way of connecting.
.ud what's up
should return the first definition and a permalink to the definitions page.
Instead, CardinalBot
will trigger a new instance of TwistedIRCProtocol
, which will hook into CardinalBot
. The instance of TwistedIRCProtocol
will handle connection to the protocol as well as interfacing with it.
Lots of class members should probably be defined as instance members instead (this really bit me in the butt with #44).
Learning a language by writing an IRC bot means falling prone to the typical gotchas. :(
iGoogle was recently shutdown, taking the unofficial Google Calculator API with it. Might switch to Wolfram Alpha. Alternatively I could use a library to add calculator support. Need to look into it.
Very simple API is available from https://www.google.com/ig/calculator?hl=en&q={QUERY}
As a part of #15, proper logging support as well as a proper config setup is being added to Cardinal. As such, you should be able to specify these properties during startup.
For example, URL detection should be able to be turned off with a switch like "Cardinal: set urls.detection false".
simply return the first result for a Google search
11:22:40 whoami | .info
11:22:41 +Cardinal | I am a Python-based Cardinal IRC bot. My owners are: whoami. You can find out more about me on my Github page: http://johnmaguire.github.io/Cardinal (Try
| .help for commands.)
11:22:41 +Cardinal | I have been online without downtime for 3 days 00:42:18, and was initially brought online 3 days 00:35:04 ago. I've been reloaded (or partially reloaded)
| 0 times since then.
11:24:43 <-- | Cardinal ([email protected]) has quit (Killed (NickServ (GHOST command used by [email protected])))
11:24:55 --> | Cardinal ([email protected]) has joined #darchoods
11:24:55 -- | Mode #darchoods [+v Cardinal] by ChanServ
11:24:57 whoami | .info
11:24:59 +Cardinal | I am a Python-based Cardinal IRC bot. My owners are: whoami. You can find out more about me on my Github page: http://johnmaguire.github.io/Cardinal (Try
| .help for commands.)
11:25:00 +Cardinal | I have been online without downtime for 00:00:02, and was initially brought online 00:37:20 ago. I've been reloaded (or partially reloaded) 0 times since
| then.
URLs plugin should fire an event when it detects a URL so other plug-ins can hook in and provide additional info.
Some ideas for handlers:
imgur
reddit
YouTube
Wikipedia
Urban Dictionary
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.