Initial commit
This commit is contained in:
commit
20a3aa0ce3
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
**/*~
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
venv/
|
||||||
|
**/__pycache__/
|
||||||
|
*.egg-info
|
||||||
|
*.toml
|
0
mira/__init__.py
Normal file
0
mira/__init__.py
Normal file
97
mira/base.py
Normal file
97
mira/base.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import importlib
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
import aioxmpp
|
||||||
|
import toml
|
||||||
|
|
||||||
|
from mira.subscription import SubscriptionManager
|
||||||
|
|
||||||
|
def message_wrapper(to, body):
|
||||||
|
msg = aioxmpp.Message(
|
||||||
|
type_=aioxmpp.MessageType.CHAT,
|
||||||
|
to=to)
|
||||||
|
msg.body[None] = body
|
||||||
|
return msg
|
||||||
|
|
||||||
|
class MiraBot:
|
||||||
|
def __init__(self):
|
||||||
|
# Bot specific settings
|
||||||
|
self._jid = ""
|
||||||
|
self._password = ""
|
||||||
|
self._client = None
|
||||||
|
self._avatar = None
|
||||||
|
|
||||||
|
self._modules = {} # Module name -> module
|
||||||
|
self._subscription_manager = SubscriptionManager()
|
||||||
|
|
||||||
|
def load_config(self, path):
|
||||||
|
data = toml.load(path)
|
||||||
|
|
||||||
|
self._jid = aioxmpp.JID.fromstr(data['jid'])
|
||||||
|
self._password = data['password']
|
||||||
|
self._avatar = data.get('avatar', None)
|
||||||
|
|
||||||
|
for module in data['modules']:
|
||||||
|
mod = importlib.import_module(module['name'])
|
||||||
|
self._modules[mod.NAME] = mod.get_instance(self, module)
|
||||||
|
self._modules[mod.NAME].set_name(mod.NAME)
|
||||||
|
|
||||||
|
async def connect(self):
|
||||||
|
self._client = aioxmpp.PresenceManagedClient(
|
||||||
|
self._jid,
|
||||||
|
aioxmpp.make_security_layer(self._password))
|
||||||
|
async with self._client.connected():
|
||||||
|
self._client.stream.register_message_callback(
|
||||||
|
aioxmpp.MessageType.CHAT,
|
||||||
|
None,
|
||||||
|
self._on_message)
|
||||||
|
|
||||||
|
if self._avatar:
|
||||||
|
with open(self._avatar, 'rb') as avatar_file:
|
||||||
|
data = avatar_file.read()
|
||||||
|
avatar_set = aioxmpp.avatar.AvatarSet()
|
||||||
|
# TODO: Detect MIME type
|
||||||
|
avatar_set.add_avatar_image('image/png', image_bytes=data)
|
||||||
|
avatar = self._client.summon(aioxmpp.avatar.AvatarService)
|
||||||
|
await avatar.publish_avatar_set(avatar_set)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
def _on_message(self, message):
|
||||||
|
# Automatically handles sending a message receipt and dealing
|
||||||
|
# with unwanted messages
|
||||||
|
if (message.type_ != aioxmpp.MessageType.CHAT or
|
||||||
|
not message.body):
|
||||||
|
return
|
||||||
|
|
||||||
|
cmd = str(message.body.any()).split(' ')
|
||||||
|
|
||||||
|
receipt = aioxmpp.mdr.compose_receipt(message)
|
||||||
|
self._client.enqueue(receipt)
|
||||||
|
|
||||||
|
if not cmd[0] in self._modules:
|
||||||
|
self._client.enqueue(message_wrapper(message.from_, "Unbekannter Befehl"))
|
||||||
|
return
|
||||||
|
|
||||||
|
self._modules[cmd[0]]._on_command(cmd[1:], message)
|
||||||
|
|
||||||
|
# Module Function: Send message
|
||||||
|
def send_message(self, message):
|
||||||
|
self._client.enqueue(message)
|
||||||
|
|
||||||
|
# Module Function: Send a message to @to with @body
|
||||||
|
def send_message_wrapper(self, to, body):
|
||||||
|
self.send_message(message_wrapper(to, body))
|
||||||
|
|
||||||
|
# Module Function
|
||||||
|
def get_subscription_manager(self):
|
||||||
|
return self._subscription_manager
|
||||||
|
|
||||||
|
def main():
|
||||||
|
bot = MiraBot()
|
||||||
|
bot.load_config("./config.toml")
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.run_until_complete(bot.connect())
|
||||||
|
loop.close()
|
51
mira/module.py
Normal file
51
mira/module.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
class BaseModule:
|
||||||
|
def __init__(self, base, config={}, subcommand_table={}):
|
||||||
|
self._name = ''
|
||||||
|
self._base = base
|
||||||
|
self._config = config
|
||||||
|
self._subcommand_table = subcommand_table
|
||||||
|
|
||||||
|
def set_name(self, name):
|
||||||
|
if self._name:
|
||||||
|
raise Exception('Name change of module attempted!')
|
||||||
|
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
def get_option(self, key, default=None):
|
||||||
|
return self._config.get(key, default)
|
||||||
|
|
||||||
|
# Used for access control
|
||||||
|
# Returns True if @jid is allowed to access the command. False otherwise.
|
||||||
|
# This is configured by either restrict_local or allowed_domains. restrict_local
|
||||||
|
# takes precedence over allowed_domains
|
||||||
|
def is_jid_allowed(self, jid):
|
||||||
|
only_local = self.get_option('restrict_local')
|
||||||
|
if only_local:
|
||||||
|
return only_local
|
||||||
|
|
||||||
|
domains = self.get_option('allowed_domains')
|
||||||
|
if not domains:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return jid.domain in domains
|
||||||
|
|
||||||
|
def get_subscriptions_for(self, jid):
|
||||||
|
return self._base.get_subscription_manager.get_subscriptions_for(self._name, jid)
|
||||||
|
|
||||||
|
def add_subscription_for(self, jid, keyword):
|
||||||
|
return self._base.get_subscription_manager.add_subscription_for(self._name, jid, keyword)
|
||||||
|
|
||||||
|
def remove_subscription_for(self, jid, keyword):
|
||||||
|
return self._base.get_subscription_manager.remove_subscription_for(self._name, jid, keyword)
|
||||||
|
|
||||||
|
def is_subscribed_to(self, jid, keyword):
|
||||||
|
return self._base.get_subscription_manager.is_subscribed_to(self._name, jid, keyword)
|
||||||
|
|
||||||
|
def _on_command(self, cmd, msg):
|
||||||
|
if not self._subcommand_table:
|
||||||
|
self.on_command(cmd, msg)
|
||||||
|
elif cmd and cmd[0] in self._subcommand_table:
|
||||||
|
self._subcommand_table[cmd[0]](cmd[1:], msg)
|
||||||
|
else:
|
||||||
|
if '*' in self._subcommand_table:
|
||||||
|
self._subcommand_table['*'](cmd, msg)
|
27
mira/modules/test.py
Normal file
27
mira/modules/test.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from mira.module import BaseModule
|
||||||
|
|
||||||
|
NAME = 'test'
|
||||||
|
|
||||||
|
class TestModule(BaseModule):
|
||||||
|
__instance = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_instance(base, config):
|
||||||
|
if TestModule.__instance == None:
|
||||||
|
TestModule(base, config)
|
||||||
|
|
||||||
|
return TestModule.__instance
|
||||||
|
|
||||||
|
def __init__(self, base, config):
|
||||||
|
if TestModule.__instance != None:
|
||||||
|
raise Exception('Trying to init singleton twice')
|
||||||
|
|
||||||
|
super().__init__(base, config)
|
||||||
|
TestModule.__instance = self
|
||||||
|
|
||||||
|
def on_command(self, cmd, msg):
|
||||||
|
greeting = self.get_option('greeting', 'OwO, %%user%%!').replace('%%user%%', str(msg.from_.bare()))
|
||||||
|
self._base.send_message_wrapper(msg.from_, greeting)
|
||||||
|
|
||||||
|
def get_instance(base, config={}):
|
||||||
|
return TestModule.get_instance(base, config)
|
39
mira/subscription.py
Normal file
39
mira/subscription.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#from collections import namedtuple
|
||||||
|
# TODO: Allow storing data along with the keyword
|
||||||
|
|
||||||
|
def append_or_insert(dict_, key, value):
|
||||||
|
if key in dict_ or dict_[key]:
|
||||||
|
dict_[key].append(value)
|
||||||
|
else:
|
||||||
|
dict_[key] = [value]
|
||||||
|
|
||||||
|
def remove_or_delete(dict_, key, value):
|
||||||
|
if value in dict_[key] and len(dict_[key]) == 1:
|
||||||
|
del dict_[key]
|
||||||
|
else:
|
||||||
|
dict_[key].remove(value)
|
||||||
|
|
||||||
|
class SubscriptionManager:
|
||||||
|
def __init__(self):
|
||||||
|
self._subscriptions = {} # Module -> JID -> Keywords
|
||||||
|
|
||||||
|
def get_subscriptions_for(self, module, jid):
|
||||||
|
if not module in self._subscriptions:
|
||||||
|
return None
|
||||||
|
if not jid in self._subscriptions[module]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return self._subscriptions[module][jid]
|
||||||
|
|
||||||
|
def is_subscribed_to(self, module, jid, keyword):
|
||||||
|
return keyword in self.get_subscriptions_for(module, jid)
|
||||||
|
|
||||||
|
def __flush(self):
|
||||||
|
# TODO
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_subscription_for(self, module, jid, keyword):
|
||||||
|
append_or_insert(self._subscriptions[module], jid, keyword)
|
||||||
|
|
||||||
|
def remove_subscription_for(self, module, jid, keyword):
|
||||||
|
remove_or_delete(self._subscriptions[module], jid, keyword)
|
22
setup.py
Normal file
22
setup.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name = 'mira',
|
||||||
|
version = '0.1.0',
|
||||||
|
description = 'An XMPP bot framework',
|
||||||
|
url = 'https://git.polynom.me/PapaTutuWawa/mira',
|
||||||
|
author = 'Alexander "PapaTutuWawa"',
|
||||||
|
author_email = 'papatutuwawa@polynom.me',
|
||||||
|
license = 'GPLv3',
|
||||||
|
packages = find_packages(),
|
||||||
|
install_requires = [
|
||||||
|
'aioxmpp>=0.11.0',
|
||||||
|
'toml>=0.10.2'
|
||||||
|
],
|
||||||
|
zip_safe=True,
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': [
|
||||||
|
'mira = mira.mira:main'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user