plugins: add base system with horoscope as demo
This commit is contained in:
parent
3d78c1b149
commit
dee6096af2
1
Pipfile
1
Pipfile
|
@ -7,6 +7,7 @@ verify_ssl = true
|
|||
|
||||
[packages]
|
||||
irc = "~=19.0.1"
|
||||
requests = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.7"
|
||||
|
|
40
Pipfile.lock
generated
40
Pipfile.lock
generated
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "489adc12bb1fac949a63215586220274f8f10299d4d171976ac988bc32b4afab"
|
||||
"sha256": "024a36b2b9e61302df7560d7af32ef1c9802327f23381e327cacb345adf56eb7"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
|
@ -16,6 +16,28 @@
|
|||
]
|
||||
},
|
||||
"default": {
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3",
|
||||
"sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"
|
||||
],
|
||||
"version": "==2020.6.20"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
|
||||
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.10"
|
||||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
"sha256:77a540690e24b0305878c37ffd421785a6f7e53c8b5720d211b211de8d0e95da",
|
||||
|
@ -95,6 +117,14 @@
|
|||
],
|
||||
"version": "==2020.1"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
|
||||
"sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.24.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
|
||||
|
@ -111,6 +141,14 @@
|
|||
"markers": "python_version >= '3.6'",
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a",
|
||||
"sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
||||
"version": "==1.25.10"
|
||||
},
|
||||
"zipp": {
|
||||
"hashes": [
|
||||
"sha256:64ad89efee774d1897a58607895d80789c59778ea02185dd846ac38394a8642b",
|
||||
|
|
26
README.md
Normal file
26
README.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
Edm0nd
|
||||
======
|
||||
|
||||
New version of the infamous IRC bot.
|
||||
|
||||
|
||||
|
||||
Missing features
|
||||
----------------
|
||||
|
||||
- [ ] Actions (/me)
|
||||
- [ ] Beers
|
||||
- [ ] Mood
|
||||
- [ ] Random: dice, choice, etc
|
||||
- [ ] Notes
|
||||
- [ ] Handle compliments
|
||||
- [ ] Handle
|
||||
- [ ] Horoscope
|
||||
- [ ] "Journee mondiale"
|
||||
- [ ] Mug
|
||||
- [ ] Music
|
||||
- [ ] Opinions
|
||||
- [ ] Translate
|
||||
- [ ] Wikipedia: find definition, get random page
|
||||
- [ ] Wolframalpha
|
||||
- [ ] Youtube: parsing for title, requests for channel or video
|
|
@ -2,5 +2,13 @@
|
|||
"host": "irc.freenode.net",
|
||||
"port": 6667,
|
||||
"nick": "edm0nd",
|
||||
"channels": ["#idi0crates"]
|
||||
"alternative_nicks": ["edmon", "edmond"],
|
||||
"channels": ["#idi0crates"],
|
||||
"speak_delay": 0.5,
|
||||
"plugins": {
|
||||
"horoscope": {
|
||||
"url": "http://zwergf.elynx.fr/bots/horobot/",
|
||||
"delay": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
import importlib
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import irc.client
|
||||
from irc.client import NickMask
|
||||
|
||||
|
@ -10,11 +15,16 @@ class Bot(irc.client.SimpleIRCClient, Logger):
|
|||
super().__init__()
|
||||
self.config = config
|
||||
self.logger = logger
|
||||
self.plugins = []
|
||||
|
||||
@property
|
||||
def nick(self):
|
||||
return self.config["nick"]
|
||||
|
||||
@property
|
||||
def names(self):
|
||||
return (self.config["nick"], *self.config["alternative_nicks"])
|
||||
|
||||
def on_welcome(self, connection, event):
|
||||
self.log_i(f"Connected to server {event.source}.")
|
||||
for channel in self.config["channels"]:
|
||||
|
@ -22,26 +32,60 @@ class Bot(irc.client.SimpleIRCClient, Logger):
|
|||
|
||||
def on_join(self, connection, event):
|
||||
self.log_i(f"Joined {event.target}.")
|
||||
self.run_plugin_callbacks(event)
|
||||
|
||||
def on_part(self, connection, event):
|
||||
self.log_i(f"Left {event.target} (args: {event.arguments[0]}).")
|
||||
self.run_plugin_callbacks(event)
|
||||
|
||||
def on_pubmsg(self, connection, event):
|
||||
channel = event.target
|
||||
nick = NickMask(event.source).nick
|
||||
message = event.arguments[0]
|
||||
self.log_d(f"Message in {channel} from {nick}: {message}")
|
||||
self.run_plugin_callbacks(event)
|
||||
|
||||
def on_privmsg(self, connection, event):
|
||||
nick = NickMask(event.source).nick
|
||||
target = event.target
|
||||
message = event.arguments[0]
|
||||
self.log_d(f"Private message from {nick} to {target}: {message}")
|
||||
self.run_plugin_callbacks(event)
|
||||
|
||||
def run(self):
|
||||
"""Connect the bot to server, join channels and start responding."""
|
||||
self.log_i("Starting Edmond.")
|
||||
self.load_plugins()
|
||||
self.connect(self.config["host"], self.config["port"], self.nick)
|
||||
try:
|
||||
self.start()
|
||||
except KeyboardInterrupt:
|
||||
self.log_i("Stopping Edmond.")
|
||||
|
||||
def load_plugins(self):
|
||||
"""Load all installed plugins."""
|
||||
plugin_files = os.listdir(Path(__file__).parent / "plugins")
|
||||
plugin_names = map(
|
||||
lambda f: os.path.splitext(f)[0],
|
||||
filter(lambda f: f.endswith(".py"), plugin_files)
|
||||
)
|
||||
for plugin_name in plugin_names:
|
||||
module = importlib.import_module(f"edmond.plugins.{plugin_name}")
|
||||
class_name = plugin_name.capitalize() + "Plugin"
|
||||
plugin_class = getattr(module, class_name)
|
||||
self.plugins.append(plugin_class(self))
|
||||
self.log_d(f"Loaded {class_name}.")
|
||||
|
||||
def say(self, target, message):
|
||||
"""Send message to target after a slight delay."""
|
||||
time.sleep(self.config["speak_delay"])
|
||||
self.log_d(f"Sending to {target}: {message}")
|
||||
self.connection.privmsg(target, message)
|
||||
|
||||
def run_plugin_callbacks(self, event):
|
||||
etype = event.type
|
||||
for plugin in self.plugins:
|
||||
callbacks = plugin.callbacks
|
||||
if etype not in callbacks:
|
||||
continue
|
||||
callbacks[etype](event)
|
||||
|
|
45
edmond/plugin.py
Normal file
45
edmond/plugin.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
|
||||
class Plugin:
|
||||
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.config = self.get_config()
|
||||
|
||||
@property
|
||||
def callbacks(self):
|
||||
"""List of callback types available for this plugin."""
|
||||
return {
|
||||
cb[3:]: getattr(self, cb)
|
||||
for cb in dir(self)
|
||||
if cb.startswith("on_") and callable(getattr(self, cb))
|
||||
}
|
||||
|
||||
def get_config(self):
|
||||
"""Return the plugin section from the bot config."""
|
||||
name = self.__class__.__name__.lower()[:-6] # Remove Plugin suffix.
|
||||
plugins_configs = self.bot.config["plugins"]
|
||||
return plugins_configs.get(name, {})
|
||||
|
||||
def should_handle_command(self, message):
|
||||
"""Store Command in object and return True if it should handle it. """
|
||||
command = self.parse_command(message)
|
||||
if command and any(c == command.ident for c in self.COMMANDS):
|
||||
self.command = command
|
||||
return True
|
||||
return False
|
||||
|
||||
def parse_command(self, message):
|
||||
"""Return a command ID if this message is a command."""
|
||||
words = message.split()
|
||||
if words[0].lower() in self.bot.names and words[-1] == "please":
|
||||
ident = words[1]
|
||||
content = " ".join(words[2:-1])
|
||||
return Command(ident, content)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Command:
|
||||
ident: str
|
||||
content: str
|
21
edmond/plugins/horoscope.py
Normal file
21
edmond/plugins/horoscope.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
import time
|
||||
|
||||
from edmond.plugin import Plugin
|
||||
from edmond.utils import http_get
|
||||
|
||||
|
||||
class HoroscopePlugin(Plugin):
|
||||
|
||||
COMMANDS = ["horoscope"]
|
||||
|
||||
def __init__(self, bot):
|
||||
super().__init__(bot)
|
||||
|
||||
def on_pubmsg(self, event):
|
||||
if not self.should_handle_command(event.arguments[0]):
|
||||
return False
|
||||
self.bot.say(event.target, "/me looks at the stars")
|
||||
time.sleep(self.config["delay"])
|
||||
text = http_get(self.config["url"])
|
||||
if text:
|
||||
self.bot.say(event.target, text)
|
8
edmond/plugins/opinion.py
Normal file
8
edmond/plugins/opinion.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from edmond.plugin import Plugin
|
||||
|
||||
|
||||
class OpinionPlugin(Plugin):
|
||||
|
||||
def __init__(self, bot):
|
||||
super().__init__(bot)
|
||||
|
7
edmond/utils.py
Normal file
7
edmond/utils.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
import requests
|
||||
|
||||
|
||||
def http_get(url):
|
||||
response = requests.get(url)
|
||||
if response.status_code == 200:
|
||||
return response.text
|
Loading…
Reference in a new issue