File botly/bot.py renamed from botly/botly.py (similarity 78%) (mode: 100644) (index 0f835d9..8e6311b) |
... |
... |
import discord |
8 |
8 |
from botly.settings import Settings |
from botly.settings import Settings |
9 |
9 |
from botly.knowledge import Knowledge |
from botly.knowledge import Knowledge |
10 |
10 |
from botly.comm import Comm |
from botly.comm import Comm |
|
11 |
|
from botly.task import load_tasks |
11 |
12 |
from botly.reaction import load_reactions |
from botly.reaction import load_reactions |
12 |
13 |
from botly.behaviour import Behaviour |
from botly.behaviour import Behaviour |
13 |
14 |
|
|
14 |
15 |
|
|
15 |
|
class Botly: |
|
|
16 |
|
class Bot: |
16 |
17 |
"""Main class that represents the bot. Will load various dependencies.""" |
"""Main class that represents the bot. Will load various dependencies.""" |
17 |
18 |
|
|
18 |
19 |
def __init__(self, rootModule, dbPath): |
def __init__(self, rootModule, dbPath): |
19 |
20 |
"""Initialization loads database, reactions and tasks from drive.""" |
"""Initialization loads database, reactions and tasks from drive.""" |
20 |
21 |
self.me = False |
self.me = False |
21 |
|
self.comm = False |
|
22 |
22 |
|
|
23 |
23 |
self.settings = Settings() |
self.settings = Settings() |
24 |
24 |
self.settings.load(dbPath) |
self.settings.load(dbPath) |
25 |
25 |
self.knowledge = Knowledge() |
self.knowledge = Knowledge() |
26 |
26 |
|
|
|
27 |
|
# Create discord client object for communication and event reception |
|
28 |
|
self.comm = Comm(self) |
|
29 |
|
|
|
30 |
|
# Load the bot background tasks |
|
31 |
|
for task in load_tasks(self, rootModule + '.tasks'): |
|
32 |
|
self.comm.loop.create_task(task.run()) |
|
33 |
|
|
|
34 |
|
# Load the bot behaviour (reactions to events) |
27 |
35 |
self.behaviour = Behaviour(load_reactions(self, |
self.behaviour = Behaviour(load_reactions(self, |
28 |
36 |
rootModule + '.reactions')) |
rootModule + '.reactions')) |
29 |
37 |
|
|
30 |
38 |
def live(self): |
def live(self): |
31 |
39 |
"""Runs the bot until it is exited. """ |
"""Runs the bot until it is exited. """ |
32 |
|
self.comm = Comm(self) |
|
33 |
40 |
# Listen to Discord's events (blocking call) |
# Listen to Discord's events (blocking call) |
34 |
41 |
print("Connecting to Discord...") |
print("Connecting to Discord...") |
35 |
42 |
self.comm.run() |
self.comm.run() |
36 |
43 |
print("\nBotly exited.") |
print("\nBotly exited.") |
37 |
44 |
|
|
|
45 |
|
async def leave(self): |
|
46 |
|
"""Coroutine that disconnects the robot from Discord.""" |
|
47 |
|
await self.comm.logout() |
|
48 |
|
|
|
49 |
|
async def say(self, channel, message): |
|
50 |
|
"""Coroutine that send a message to discord.""" |
|
51 |
|
await self.comm.send_message(channel, message) |
|
52 |
|
|
38 |
53 |
def set_whoami(self, user): |
def set_whoami(self, user): |
39 |
54 |
"""This will be called once the bot is connected to Discord.""" |
"""This will be called once the bot is connected to Discord.""" |
40 |
55 |
self.me = user |
self.me = user |
|
... |
... |
class Botly: |
50 |
65 |
reaction.prepare_react(**eventInfo) |
reaction.prepare_react(**eventInfo) |
51 |
66 |
await reaction.react() |
await reaction.react() |
52 |
67 |
|
|
53 |
|
async def leave(self): |
|
54 |
|
"""Coroutine that disconnects the robot from Discord.""" |
|
55 |
|
await self.comm.logout() |
|
56 |
68 |
|
|
57 |
69 |
|
|
File botly/comm.py changed (mode: 100644) (index f2576da..66f2085) |
... |
... |
import discord |
9 |
9 |
class Comm(discord.Client): |
class Comm(discord.Client): |
10 |
10 |
"""Inherits from discord.client. Used for communication with server.""" |
"""Inherits from discord.client. Used for communication with server.""" |
11 |
11 |
|
|
12 |
|
def __init__(self, botly): |
|
13 |
|
self.botly = botly |
|
14 |
|
super(Comm, self).__init__(max_messages=botly.settings.get_int( |
|
|
12 |
|
def __init__(self, bot): |
|
13 |
|
self.bot = bot |
|
14 |
|
super(Comm, self).__init__(max_messages=bot.settings.get_int( |
15 |
15 |
'CacheMaxMessages')) |
'CacheMaxMessages')) |
16 |
16 |
|
|
17 |
17 |
async def on_ready(self): |
async def on_ready(self): |
18 |
18 |
print('Bot ready. Press Ctrl+C to shutdown.') |
print('Bot ready. Press Ctrl+C to shutdown.') |
19 |
|
self.botly.set_whoami(self.user) |
|
20 |
|
await self.botly.react_to('on_ready') |
|
|
19 |
|
self.bot.set_whoami(self.user) |
|
20 |
|
await self.bot.react_to('on_ready') |
21 |
21 |
|
|
22 |
22 |
async def on_typing(self, channel, user, when): |
async def on_typing(self, channel, user, when): |
23 |
23 |
if user != self.user: |
if user != self.user: |
24 |
|
await self.botly.react_to('on_typing', channel=channel, user=user, |
|
|
24 |
|
await self.bot.react_to('on_typing', channel=channel, user=user, |
25 |
25 |
when=when) |
when=when) |
26 |
26 |
|
|
27 |
27 |
async def on_message_edit(self, before, after): |
async def on_message_edit(self, before, after): |
28 |
28 |
if before.author != self.user: |
if before.author != self.user: |
29 |
|
await self.botly.react_to('on_message_edit', message=before, |
|
|
29 |
|
await self.bot.react_to('on_message_edit', message=before, |
30 |
30 |
after=after) |
after=after) |
31 |
31 |
|
|
32 |
32 |
async def on_message_delete(self, message): |
async def on_message_delete(self, message): |
33 |
33 |
if message.author != self.user: |
if message.author != self.user: |
34 |
|
await self.botly.react_to('on_message_delete', message=message) |
|
|
34 |
|
await self.bot.react_to('on_message_delete', message=message) |
35 |
35 |
|
|
36 |
36 |
async def on_message(self, message): |
async def on_message(self, message): |
37 |
37 |
if message.author != self.user: |
if message.author != self.user: |
38 |
|
await self.botly.react_to('on_message', message=message) |
|
|
38 |
|
await self.bot.react_to('on_message', message=message) |
39 |
39 |
|
|
40 |
40 |
async def on_member_update(self, before, after): |
async def on_member_update(self, before, after): |
41 |
|
await self.botly.react_to('on_member_update', before=before, |
|
|
41 |
|
await self.bot.react_to('on_member_update', before=before, |
42 |
42 |
after=after) |
after=after) |
43 |
43 |
|
|
44 |
44 |
def run(self): |
def run(self): |
45 |
|
super(Comm, self).run(self.botly.settings.get_string('DiscordToken')) |
|
|
45 |
|
super(Comm, self).run(self.bot.settings.get_string('DiscordToken')) |
46 |
46 |
|
|
47 |
47 |
|
|
File botly/reaction.py changed (mode: 100644) (index 0520ccf..cdd185c) |
... |
... |
class ReactionBase: |
27 |
27 |
def __init__(self, eventName): |
def __init__(self, eventName): |
28 |
28 |
"""When subclassed, pass the event name to the mother class.""" |
"""When subclassed, pass the event name to the mother class.""" |
29 |
29 |
self.eventName = eventName |
self.eventName = eventName |
30 |
|
self.botly = False |
|
|
30 |
|
self.bot = False |
31 |
31 |
self.knowledge = False |
self.knowledge = False |
32 |
32 |
trigger = Trigger(eventName) |
trigger = Trigger(eventName) |
33 |
33 |
# Call the function that should have been subclassed |
# Call the function that should have been subclassed |
|
... |
... |
class ReactionBase: |
53 |
53 |
|
|
54 |
54 |
def is_mentioned(self): |
def is_mentioned(self): |
55 |
55 |
"""Returns whether or not our bot was mentioned in the message.""" |
"""Returns whether or not our bot was mentioned in the message.""" |
56 |
|
assert self.botly, 'Botly instance not passed to this object' |
|
|
56 |
|
assert self.bot, 'Botly instance not passed to this object' |
57 |
57 |
if self.message: |
if self.message: |
58 |
|
return self.botly.me.mentioned_in(self.message) |
|
|
58 |
|
return self.bot.me.mentioned_in(self.message) |
59 |
59 |
|
|
60 |
60 |
async def reply(self, message): |
async def reply(self, message): |
61 |
61 |
"""Coroutine that replies in current chan with given message.""" |
"""Coroutine that replies in current chan with given message.""" |
62 |
62 |
if self.channel: |
if self.channel: |
63 |
|
await self.botly.comm.send_message(self.channel, message) |
|
|
63 |
|
await self.bot.say(self.channel, message) |
64 |
64 |
|
|
65 |
65 |
async def send_to_chan(self, channel, message): |
async def send_to_chan(self, channel, message): |
66 |
66 |
"""Coroutine that send message to given channel.""" |
"""Coroutine that send message to given channel.""" |
67 |
|
await self.botly.comm.send_message(channel, message) |
|
|
67 |
|
await self.bot.say(channel, message) |
68 |
68 |
|
|
69 |
|
def set_botly_instance(self, botly): |
|
|
69 |
|
def set_bot_instance(self, bot): |
70 |
70 |
"""Saves botly instance. Only meant to becalled upon module import.""" |
"""Saves botly instance. Only meant to becalled upon module import.""" |
71 |
|
self.botly = botly |
|
72 |
|
self.knowledge = botly.knowledge |
|
|
71 |
|
self.bot = bot |
|
72 |
|
self.knowledge = bot.knowledge |
73 |
73 |
|
|
74 |
74 |
def set_module_name(self, name): |
def set_module_name(self, name): |
75 |
75 |
"""Saves the name of this Reaction. Called upon module import.""" |
"""Saves the name of this Reaction. Called upon module import.""" |
|
... |
... |
class ReactionBase: |
101 |
101 |
self.after = eventInfo['after'] |
self.after = eventInfo['after'] |
102 |
102 |
|
|
103 |
103 |
|
|
104 |
|
def load_reactions(botly, reactionsParent): |
|
|
104 |
|
def load_reactions(bot, reactionsParent): |
105 |
105 |
"""Function that loads reactions from given parent module directory.""" |
"""Function that loads reactions from given parent module directory.""" |
106 |
106 |
# Convert module path to drive path. |
# Convert module path to drive path. |
107 |
107 |
dpath = './' + reactionsParent.replace('.', '/') |
dpath = './' + reactionsParent.replace('.', '/') |
|
... |
... |
def load_reactions(botly, reactionsParent): |
122 |
122 |
assert len(reaction.eventName), \ |
assert len(reaction.eventName), \ |
123 |
123 |
'Loaded a reaction linked to no event.' |
'Loaded a reaction linked to no event.' |
124 |
124 |
# Inject instance info in reaction object |
# Inject instance info in reaction object |
125 |
|
reaction.set_botly_instance(botly) |
|
|
125 |
|
reaction.set_bot_instance(bot) |
126 |
126 |
reaction.set_module_name(module) |
reaction.set_module_name(module) |
127 |
127 |
print('Loaded ' + str(len(reactions)) + ' reactions from drive.') |
print('Loaded ' + str(len(reactions)) + ' reactions from drive.') |
128 |
128 |
return reactions |
return reactions |
File botly/task.py added (mode: 100644) (index 0000000..cbc32e0) |
|
1 |
|
#!/usr/bin/env python |
|
2 |
|
"""Contains the TaskBase to be inherited for Tasks creation""" |
|
3 |
|
|
|
4 |
|
import asyncio |
|
5 |
|
from importlib import import_module |
|
6 |
|
from os import listdir |
|
7 |
|
from os.path import isfile, join |
|
8 |
|
|
|
9 |
|
import discord |
|
10 |
|
|
|
11 |
|
|
|
12 |
|
class TaskBase: |
|
13 |
|
"""Base class for bot's background tasks""" |
|
14 |
|
|
|
15 |
|
def __init__(self): |
|
16 |
|
self.endTask = False |
|
17 |
|
|
|
18 |
|
def set_instance_info(self, bot): |
|
19 |
|
self.bot = bot |
|
20 |
|
self.knowledge = bot.knowledge |
|
21 |
|
self.settings = bot.settings |
|
22 |
|
self.comm = bot.comm |
|
23 |
|
|
|
24 |
|
self.mainChannel = discord.Object( |
|
25 |
|
id=bot.settings.get_string('DefaultChannelId')) |
|
26 |
|
|
|
27 |
|
async def run(self): |
|
28 |
|
"""Entry point of task. Do not overwrite.""" |
|
29 |
|
|
|
30 |
|
# Wait until the bot is ready before running |
|
31 |
|
await self.comm.wait_until_ready() |
|
32 |
|
|
|
33 |
|
while not self.comm.is_closed or not self.endTask: |
|
34 |
|
await asyncio.sleep(self.get_next_timeout()) |
|
35 |
|
await self.do() |
|
36 |
|
|
|
37 |
|
def get_next_timeout(self): |
|
38 |
|
"""Should return the time, in sec, to wait before each do() call.""" |
|
39 |
|
return 10 |
|
40 |
|
|
|
41 |
|
def do(self): |
|
42 |
|
"""Proceed with the task's action. To be overwritten when inherited""" |
|
43 |
|
pass |
|
44 |
|
|
|
45 |
|
|
|
46 |
|
def load_tasks(bot, tasksParent): |
|
47 |
|
"""Function that loads the tasks from given paren module directory.""" |
|
48 |
|
# Get fs path to module directory |
|
49 |
|
dpath = './' + tasksParent.replace('.', '/') |
|
50 |
|
|
|
51 |
|
# Retreive every python script in the tasks module directory |
|
52 |
|
files = [f for f in listdir(dpath) |
|
53 |
|
if isfile(join(dpath, f)) |
|
54 |
|
and f.endswith('.py')] |
|
55 |
|
|
|
56 |
|
# Build the list of tasks |
|
57 |
|
tasks = [] |
|
58 |
|
for file in files: |
|
59 |
|
# Module name should not end with '.py', so remove that part |
|
60 |
|
module = file[:-3] |
|
61 |
|
_taskModule = import_module(tasksParent + '.' + module) |
|
62 |
|
task = _taskModule.Task() |
|
63 |
|
tasks.append(task) |
|
64 |
|
# Inject instance info in task object |
|
65 |
|
task.set_instance_info(bot) |
|
66 |
|
print('Loaded ' + str(len(tasks)) + ' tasks from drive.') |
|
67 |
|
return tasks |
|
68 |
|
|
|
69 |
|
|
File examplebot/reactions/HELP.py.example changed (mode: 100644) (index 1cb51e7..d6797ba) |
... |
... |
class Reaction(ReactionBase): |
58 |
58 |
# |
# |
59 |
59 |
### |
### |
60 |
60 |
# Available accesses: |
# Available accesses: |
61 |
|
# self.botly Access to the bot object instance |
|
62 |
|
# self.botly.comm Discord communication object instance |
|
|
61 |
|
# self.bot Access to the bot object instance |
|
62 |
|
# self.bot.comm Discord communication object instance |
63 |
63 |
# self.knowledge Access to the bot knowledge database |
# self.knowledge Access to the bot knowledge database |
64 |
64 |
# |
# |
65 |
65 |
### |
### |
|
... |
... |
class Reaction(ReactionBase): |
69 |
69 |
# self.author Discord Member/User object shortcut |
# self.author Discord Member/User object shortcut |
70 |
70 |
# self.channel Discord Channel object where this occured |
# self.channel Discord Channel object where this occured |
71 |
71 |
# 'on_message_edit' event only: |
# 'on_message_edit' event only: |
72 |
|
# self.messageAfter Message post-edition |
|
|
72 |
|
# self.messageAfter Message post-edition |
73 |
73 |
# 'on_typing' event: |
# 'on_typing' event: |
74 |
74 |
# self.channel Discord channel object |
# self.channel Discord channel object |
75 |
75 |
# self.user Discord Member/User object |
# self.user Discord Member/User object |
|
... |
... |
class Reaction(ReactionBase): |
81 |
81 |
### |
### |
82 |
82 |
# |
# |
83 |
83 |
# Exemple response to a message |
# Exemple response to a message |
84 |
|
# await self.botly.comm.send_message(self.message.channel, \ |
|
85 |
|
# self.message.author.mention + ' lol') |
|
|
84 |
|
# await self.reply(self.message.author.mention + ' lol') |
86 |
85 |
|
|