File botly/behaviour.py changed (mode: 100644) (index 102836f..d5908fd) |
|
1 |
|
#!/usr/bin/env python |
|
2 |
|
"""This class groups by name of events the reactions of the Bot""" |
|
3 |
|
|
1 |
4 |
|
|
2 |
5 |
class Behaviour: |
class Behaviour: |
3 |
|
|
|
|
6 |
|
"""Behaviour class holds the reaction sorted by event name.""" |
|
7 |
|
|
4 |
8 |
def __init__(self, reactions): |
def __init__(self, reactions): |
5 |
9 |
self.reactions = {} |
self.reactions = {} |
6 |
10 |
# We will group reactions based on the event |
# We will group reactions based on the event |
|
... |
... |
class Behaviour: |
8 |
12 |
print('Loading reactions into behaviour...') |
print('Loading reactions into behaviour...') |
9 |
13 |
for reaction in reactions: |
for reaction in reactions: |
10 |
14 |
ename = reaction.get_event_name() |
ename = reaction.get_event_name() |
11 |
|
print("Loading reaction '" + \ |
|
12 |
|
reaction.get_module_name() + \ |
|
13 |
|
"' into event table '" + \ |
|
14 |
|
ename + "'.") |
|
|
15 |
|
print("Loading reaction '" |
|
16 |
|
+ reaction.get_module_name() |
|
17 |
|
+ "' into event table '" |
|
18 |
|
+ ename + "'.") |
15 |
19 |
if not ename in self.reactions: |
if not ename in self.reactions: |
16 |
20 |
self.reactions[ename] = [] |
self.reactions[ename] = [] |
17 |
21 |
self.reactions[ename].append(reaction) |
self.reactions[ename].append(reaction) |
18 |
22 |
|
|
19 |
23 |
def get_reactions(self, eventName): |
def get_reactions(self, eventName): |
|
24 |
|
"""Returns reaction by event name given, or False it none exist.""" |
20 |
25 |
if eventName in self.reactions: |
if eventName in self.reactions: |
21 |
26 |
return self.reactions[eventName] |
return self.reactions[eventName] |
22 |
27 |
return False |
return False |
|
28 |
|
|
|
29 |
|
|
File botly/botly.py changed (mode: 100644) (index bc6e6cf..0f835d9) |
1 |
|
import discord |
|
|
1 |
|
#!/usr/bin/env python |
|
2 |
|
"""Main class that will load the various parts of the bot.""" |
|
3 |
|
|
2 |
4 |
import asyncio |
import asyncio |
3 |
|
import sys |
|
4 |
|
import time |
|
5 |
5 |
|
|
6 |
|
from .settings import Settings |
|
7 |
|
from .knowledge import Knowledge |
|
8 |
|
from .comm import Comm |
|
9 |
|
from .reaction import load_reactions |
|
10 |
|
from .behaviour import Behaviour |
|
|
6 |
|
import discord |
|
7 |
|
|
|
8 |
|
from botly.settings import Settings |
|
9 |
|
from botly.knowledge import Knowledge |
|
10 |
|
from botly.comm import Comm |
|
11 |
|
from botly.reaction import load_reactions |
|
12 |
|
from botly.behaviour import Behaviour |
|
13 |
|
|
11 |
14 |
|
|
12 |
15 |
class Botly: |
class Botly: |
|
16 |
|
"""Main class that represents the bot. Will load various dependencies.""" |
13 |
17 |
|
|
14 |
18 |
def __init__(self, rootModule, dbPath): |
def __init__(self, rootModule, dbPath): |
|
19 |
|
"""Initialization loads database, reactions and tasks from drive.""" |
15 |
20 |
self.me = False |
self.me = False |
16 |
21 |
self.comm = False |
self.comm = False |
17 |
22 |
|
|
|
... |
... |
class Botly: |
19 |
24 |
self.settings.load(dbPath) |
self.settings.load(dbPath) |
20 |
25 |
self.knowledge = Knowledge() |
self.knowledge = Knowledge() |
21 |
26 |
|
|
22 |
|
self.behaviour = Behaviour(load_reactions(self, \ |
|
23 |
|
rootModule + '.reactions')) |
|
|
27 |
|
self.behaviour = Behaviour(load_reactions(self, |
|
28 |
|
rootModule + '.reactions')) |
24 |
29 |
|
|
25 |
30 |
def live(self): |
def live(self): |
|
31 |
|
"""Runs the bot until it is exited. """ |
26 |
32 |
self.comm = Comm(self) |
self.comm = Comm(self) |
27 |
33 |
# Listen to Discord's events (blocking call) |
# Listen to Discord's events (blocking call) |
|
34 |
|
print("Connecting to Discord...") |
28 |
35 |
self.comm.run() |
self.comm.run() |
29 |
36 |
print("\nBotly exited.") |
print("\nBotly exited.") |
30 |
37 |
|
|
31 |
38 |
def set_whoami(self, user): |
def set_whoami(self, user): |
32 |
|
""" |
|
33 |
|
This will be called by Comm when Discord |
|
34 |
|
has been connected. |
|
35 |
|
""" |
|
|
39 |
|
"""This will be called once the bot is connected to Discord.""" |
36 |
40 |
self.me = user |
self.me = user |
37 |
41 |
|
|
38 |
42 |
async def react_to(self, eventName, **eventInfo): |
async def react_to(self, eventName, **eventInfo): |
|
43 |
|
"""Coroutine that will be called upon events. Checks for triggers.""" |
39 |
44 |
reactions = self.behaviour.get_reactions(eventName) |
reactions = self.behaviour.get_reactions(eventName) |
40 |
45 |
if reactions: |
if reactions: |
41 |
46 |
for reaction in reactions: |
for reaction in reactions: |
42 |
|
if reaction.get_trigger().is_triggered(self, **eventInfo): |
|
43 |
|
print('('+eventName+') -> ' + reaction.get_module_name()) |
|
|
47 |
|
if reaction.trigger.is_triggered(self, **eventInfo): |
|
48 |
|
print("Reaction '" + reaction.moduleName |
|
49 |
|
+ "' triggered by event '" + eventName + "'.") |
44 |
50 |
reaction.prepare_react(**eventInfo) |
reaction.prepare_react(**eventInfo) |
45 |
51 |
await reaction.react() |
await reaction.react() |
46 |
52 |
|
|
47 |
53 |
async def leave(self): |
async def leave(self): |
|
54 |
|
"""Coroutine that disconnects the robot from Discord.""" |
48 |
55 |
await self.comm.logout() |
await self.comm.logout() |
49 |
56 |
|
|
|
57 |
|
|
File botly/comm.py changed (mode: 100644) (index 9e61871..6fd9842) |
1 |
|
import discord |
|
|
1 |
|
#!/usr/bin/env python |
|
2 |
|
"""Inherits from Discord client. Handles events and pass them to Botly.""" |
|
3 |
|
|
2 |
4 |
import asyncio |
import asyncio |
3 |
|
import sys |
|
4 |
|
import time |
|
|
5 |
|
|
|
6 |
|
import discord |
|
7 |
|
|
5 |
8 |
|
|
6 |
9 |
class Comm(discord.Client): |
class Comm(discord.Client): |
7 |
|
|
|
|
10 |
|
"""Inherits from discord.client. Used for communication with server.""" |
|
11 |
|
|
8 |
12 |
def __init__(self, botly): |
def __init__(self, botly): |
9 |
13 |
self.botly = botly |
self.botly = botly |
10 |
|
self.updatedBot = False |
|
11 |
|
super(Comm, self).__init__(max_messages=botly.settings.get_int( \ |
|
12 |
|
'CacheMaxMessages')) |
|
|
14 |
|
super(Comm, self).__init__(max_messages=botly.settings.get_int( |
|
15 |
|
'CacheMaxMessages')) |
13 |
16 |
|
|
14 |
17 |
async def on_ready(self): |
async def on_ready(self): |
15 |
|
print('Bot ready') |
|
16 |
|
if not self.updatedBot: |
|
17 |
|
self.botly.set_whoami(self.user) |
|
18 |
|
self.updatedBot = True |
|
|
18 |
|
print('Bot ready. Press Ctrl+C to shutdown.') |
|
19 |
|
self.botly.set_whoami(self.user) |
19 |
20 |
await self.botly.react_to('on_ready') |
await self.botly.react_to('on_ready') |
20 |
21 |
|
|
21 |
22 |
async def on_typing(self, channel, user, when): |
async def on_typing(self, channel, user, when): |
22 |
23 |
if user != self.user: |
if user != self.user: |
23 |
24 |
await self.botly.react_to('on_typing', channel=channel, user=user, |
await self.botly.react_to('on_typing', channel=channel, user=user, |
24 |
|
when=when) |
|
|
25 |
|
when=when) |
25 |
26 |
|
|
26 |
27 |
async def on_message_edit(self, before, after): |
async def on_message_edit(self, before, after): |
27 |
28 |
if before.author != self.user: |
if before.author != self.user: |
28 |
|
await self.botly.react_to('on_message_edit', message=before, \ |
|
29 |
|
after=after) |
|
|
29 |
|
await self.botly.react_to('on_message_edit', message=before, |
|
30 |
|
after=after) |
30 |
31 |
|
|
31 |
32 |
async def on_message_delete(self, message): |
async def on_message_delete(self, message): |
32 |
33 |
if message.author != self.user: |
if message.author != self.user: |
|
... |
... |
class Comm(discord.Client): |
37 |
38 |
await self.botly.react_to('on_message', message=message) |
await self.botly.react_to('on_message', message=message) |
38 |
39 |
|
|
39 |
40 |
async def on_member_update(self, before, after): |
async def on_member_update(self, before, after): |
40 |
|
await self.botly.react_to('on_member_update', before=before, \ |
|
41 |
|
after=after) |
|
|
41 |
|
await self.botly.react_to('on_member_update', before=before, |
|
42 |
|
after=after) |
42 |
43 |
|
|
43 |
44 |
def run(self): |
def run(self): |
44 |
45 |
super(Comm, self).run(self.botly.settings.get_string('DiscordToken')) |
super(Comm, self).run(self.botly.settings.get_string('DiscordToken')) |
45 |
|
|
|
|
46 |
|
|
|
47 |
|
|
File botly/knowledge.py changed (mode: 100644) (index bce2dea..cb5b41d) |
1 |
|
from .db import Db |
|
|
1 |
|
#!/usr/bin/env python |
|
2 |
|
"""Class Knowledge represents what bot knows, such as users and their info.""" |
|
3 |
|
|
|
4 |
|
from botly.db import Db |
|
5 |
|
|
2 |
6 |
|
|
3 |
7 |
class Knowledge(Db): |
class Knowledge(Db): |
4 |
|
|
|
|
8 |
|
"""Represents what bot knows. Use accessors to access data.""" |
|
9 |
|
|
5 |
10 |
def __init__(self): |
def __init__(self): |
6 |
11 |
self.users = [] |
self.users = [] |
7 |
12 |
|
|
8 |
|
def _load_users(self): |
|
9 |
|
assert self.is_loaded(), 'Xml document is not loaded' |
|
10 |
|
self.users = [] |
|
11 |
|
for userxml in Db.dbxml.xpath('/BotDb/Knowledge/Users/User'): |
|
12 |
|
user = {} |
|
13 |
|
user['Id'] = self.get_value(userxml, 'Id') |
|
14 |
|
user['Alias'] = self.get_value(userxml, 'Alias') |
|
15 |
|
user['BotAffinity'] = self.get_value(userxml, 'BotAffinity') |
|
16 |
|
user['IsMaster'] = self.get_value(userxml, 'IsMaster') |
|
17 |
|
self.users.append(user) |
|
18 |
|
|
|
19 |
13 |
def get_users(self): |
def get_users(self): |
|
14 |
|
"""Returns the whole users table. Loads it from XML if not done yet.""" |
20 |
15 |
if len(self.users) == 0: |
if len(self.users) == 0: |
21 |
16 |
self._load_users() |
self._load_users() |
22 |
17 |
return self.users |
return self.users |
23 |
18 |
|
|
|
19 |
|
def user_exists(self, id): |
|
20 |
|
"""Returns whether or not we have this user in our knowledge db.""" |
|
21 |
|
return True if self.get_user(id) else False |
|
22 |
|
|
24 |
23 |
def get_user(self, id): |
def get_user(self, id): |
|
24 |
|
"""Return all botly values for given user id.""" |
25 |
25 |
for user in self.get_users(): |
for user in self.get_users(): |
26 |
26 |
if user['Id'] == id: |
if user['Id'] == id: |
27 |
27 |
return user |
return user |
28 |
28 |
return False |
return False |
29 |
29 |
|
|
30 |
30 |
def get_user_affinity(self, id): |
def get_user_affinity(self, id): |
|
31 |
|
"""Returns the affinity of the user designated by given id.""" |
31 |
32 |
user = self.get_user(id) |
user = self.get_user(id) |
32 |
33 |
if user == False: |
if user == False: |
33 |
34 |
# We have an affinity of 0 (neutral) for unknown users |
# We have an affinity of 0 (neutral) for unknown users |
|
... |
... |
class Knowledge(Db): |
35 |
36 |
return int(user['BotAffinity']) |
return int(user['BotAffinity']) |
36 |
37 |
|
|
37 |
38 |
def get_user_alias(self, id): |
def get_user_alias(self, id): |
|
39 |
|
"""Returns the alias of the user designated by given id.""" |
38 |
40 |
user = self.get_user(id) |
user = self.get_user(id) |
39 |
41 |
if user == False: |
if user == False: |
40 |
42 |
return False |
return False |
41 |
43 |
return user['Alias'] |
return user['Alias'] |
42 |
44 |
|
|
43 |
45 |
def is_user_master(self, id): |
def is_user_master(self, id): |
|
46 |
|
"""Returns whether or not user is a master (can command bot).""" |
44 |
47 |
user = self.get_user(id) |
user = self.get_user(id) |
45 |
48 |
if user == False: |
if user == False: |
46 |
49 |
return False |
return False |
47 |
50 |
return user['IsMaster'] == '1' |
return user['IsMaster'] == '1' |
48 |
51 |
|
|
|
52 |
|
def _load_users(self): |
|
53 |
|
# TODO, Change how users are loaded. Don't load them all at start, but |
|
54 |
|
# only when requested. Also include non-hardcoded possible value. |
|
55 |
|
assert self.is_loaded(), 'Xml document is not loaded' |
|
56 |
|
self.users = [] |
|
57 |
|
for userxml in Db.dbxml.xpath('/BotDb/Knowledge/Users/User'): |
|
58 |
|
user = {} |
|
59 |
|
user['Id'] = self.get_value(userxml, 'Id') |
|
60 |
|
user['Alias'] = self.get_value(userxml, 'Alias') |
|
61 |
|
user['BotAffinity'] = self.get_value(userxml, 'BotAffinity') |
|
62 |
|
user['IsMaster'] = self.get_value(userxml, 'IsMaster') |
|
63 |
|
self.users.append(user) |
|
64 |
|
|
|
65 |
|
|
File botly/reaction.py changed (mode: 100644) (index 098ae11..258432e) |
|
1 |
|
#!/usr/bin/env python |
|
2 |
|
"""Contains the ReactionBase class, meant to be subclassed.""" |
|
3 |
|
|
1 |
4 |
import asyncio |
import asyncio |
2 |
5 |
from importlib import import_module |
from importlib import import_module |
3 |
6 |
from os import listdir |
from os import listdir |
4 |
7 |
from os.path import isfile, join |
from os.path import isfile, join |
5 |
8 |
|
|
6 |
|
from .trigger import Trigger |
|
|
9 |
|
from botly.trigger import Trigger |
|
10 |
|
|
7 |
11 |
|
|
8 |
12 |
class ReactionBase: |
class ReactionBase: |
|
13 |
|
"""Meant""" |
|
14 |
|
|
9 |
15 |
def __init__(self, eventName): |
def __init__(self, eventName): |
10 |
|
""" |
|
11 |
|
Subclass this and pass the event that this |
|
12 |
|
reaction should react and the trigger object to |
|
13 |
|
to the mother class constructor. |
|
14 |
|
""" |
|
|
16 |
|
"""When subclassed, pass the event name to the mother class.""" |
15 |
17 |
self.eventName = eventName |
self.eventName = eventName |
16 |
18 |
self.botly = False |
self.botly = False |
17 |
19 |
self.knowledge = False |
self.knowledge = False |
|
... |
... |
class ReactionBase: |
19 |
21 |
# Call the function that should have been subclassed |
# Call the function that should have been subclassed |
20 |
22 |
self.prepare_trigger(trigger) |
self.prepare_trigger(trigger) |
21 |
23 |
self.trigger = trigger |
self.trigger = trigger |
22 |
|
|
|
|
24 |
|
|
23 |
25 |
def prepare_trigger(self, trigger): |
def prepare_trigger(self, trigger): |
|
26 |
|
"""Allows the trigger to be personalized. Meant to be subclassed. |
|
27 |
|
|
|
28 |
|
This method should be redefined in the subclass. |
|
29 |
|
It should prepare the given trigger by defining its conditions. |
|
30 |
|
It does not require to return anything |
24 |
31 |
""" |
""" |
25 |
|
This method should be redefined in the subclass. |
|
26 |
|
It should prepare the given trigger by defining its |
|
27 |
|
conditions. |
|
28 |
|
""" |
|
29 |
32 |
pass |
pass |
30 |
33 |
|
|
|
34 |
|
async def react(self): |
|
35 |
|
"""Method to be subclassed. Called when conditions were met.""" |
|
36 |
|
pass |
|
37 |
|
|
|
38 |
|
def print(self, message): |
|
39 |
|
"""Helper for console printing. Will prepend current Reaction name.""" |
|
40 |
|
print('[{0}] {1}'.format(self.moduleName, message)) |
|
41 |
|
|
|
42 |
|
def is_mentioned(self): |
|
43 |
|
"""Returns whether or not our bot was mentioned in the message.""" |
|
44 |
|
assert self.botly, 'Botly instance not passed to this object' |
|
45 |
|
if self.message: |
|
46 |
|
return self.botly.me.mentioned_in(self.message) |
|
47 |
|
|
|
48 |
|
async def reply(self, message): |
|
49 |
|
"""Coroutine that replies in current chan with given message.""" |
|
50 |
|
if self.channel: |
|
51 |
|
await self.botly.comm.send_message(self.channel, message) |
|
52 |
|
|
|
53 |
|
async def send_to_chan(self, channel, message): |
|
54 |
|
"""Coroutine that send message to given channel.""" |
|
55 |
|
await self.botly.comm.send_message(channel, message) |
|
56 |
|
|
31 |
57 |
def set_botly_instance(self, botly): |
def set_botly_instance(self, botly): |
|
58 |
|
"""Saves botly instance. Only meant to becalled upon module import.""" |
32 |
59 |
self.botly = botly |
self.botly = botly |
33 |
60 |
self.knowledge = botly.knowledge |
self.knowledge = botly.knowledge |
34 |
61 |
|
|
35 |
62 |
def set_module_name(self, name): |
def set_module_name(self, name): |
|
63 |
|
"""Saves the name of this Reaction. Called upon module import.""" |
36 |
64 |
self.moduleName = name |
self.moduleName = name |
37 |
|
|
|
38 |
|
def get_trigger(self): |
|
39 |
|
return self.trigger |
|
40 |
|
|
|
41 |
|
def get_module_name(self): |
|
42 |
|
return self.moduleName |
|
43 |
|
|
|
44 |
|
def get_event_name(self): |
|
45 |
|
return self.eventName |
|
46 |
65 |
|
|
47 |
66 |
def prepare_react(self, **eventInfo): |
def prepare_react(self, **eventInfo): |
|
67 |
|
"""Only meant to be called from Botly. Do not call elsewhere.""" |
48 |
68 |
self.message = False |
self.message = False |
49 |
69 |
self.messageAfter = False |
self.messageAfter = False |
50 |
70 |
self.author = False |
self.author = False |
|
... |
... |
class ReactionBase: |
68 |
88 |
self.before = eventInfo['before'] |
self.before = eventInfo['before'] |
69 |
89 |
self.after = eventInfo['after'] |
self.after = eventInfo['after'] |
70 |
90 |
|
|
71 |
|
def is_mentioned(self): |
|
72 |
|
assert self.botly, 'Botly instance not passed to this object' |
|
73 |
|
if self.message: |
|
74 |
|
return self.botly.me.mentioned_in(self.message) |
|
75 |
|
|
|
76 |
|
async def reply(self, message): |
|
77 |
|
if self.channel: |
|
78 |
|
self.botly.comm.send_message(self.channel, message) |
|
79 |
|
|
|
80 |
|
async def reply_to_chan(self, channel, message): |
|
81 |
|
self.botly.comm.send_message(channel, message) |
|
82 |
|
|
|
83 |
|
async def react(self): |
|
84 |
|
""" |
|
85 |
|
Method to be subclassed. |
|
86 |
|
This will be called when the triggers will be triggered. |
|
87 |
|
""" |
|
88 |
|
pass |
|
89 |
|
|
|
90 |
91 |
def load_reactions(botly, reactionsParent): |
def load_reactions(botly, reactionsParent): |
|
92 |
|
"""Function that loads reactions from given parent module directory.""" |
|
93 |
|
# Convert module path to drive path. |
91 |
94 |
dpath = './' + reactionsParent.replace('.', '/') |
dpath = './' + reactionsParent.replace('.', '/') |
92 |
|
files = [f for f in listdir(dpath) \ |
|
93 |
|
if isfile(join(dpath,f)) \ |
|
|
95 |
|
|
|
96 |
|
# Retreive every python script in the reaction module directory |
|
97 |
|
files = [f for f in listdir(dpath) |
|
98 |
|
if isfile(join(dpath,f)) |
94 |
99 |
and f.endswith('.py')] |
and f.endswith('.py')] |
|
100 |
|
|
|
101 |
|
# Build the list of reactions |
95 |
102 |
reactions = [] |
reactions = [] |
96 |
103 |
for file in files: |
for file in files: |
|
104 |
|
# Module name does not include '.py' so remove that part |
97 |
105 |
module = file[:-3] |
module = file[:-3] |
98 |
106 |
_reaction = import_module(reactionsParent + '.' + module) |
_reaction = import_module(reactionsParent + '.' + module) |
99 |
107 |
reaction = _reaction.Reaction() |
reaction = _reaction.Reaction() |
|
108 |
|
reactions.append(reaction) |
100 |
109 |
assert len(reaction.get_event_name()), \ |
assert len(reaction.get_event_name()), \ |
101 |
110 |
'Loaded a reaction linked to no event.' |
'Loaded a reaction linked to no event.' |
|
111 |
|
# Inject instance info in reaction object |
102 |
112 |
reaction.set_botly_instance(botly) |
reaction.set_botly_instance(botly) |
103 |
113 |
reaction.set_module_name(module) |
reaction.set_module_name(module) |
104 |
|
reactions.append(reaction) |
|
105 |
|
print('Loaded '+str(len(reactions))+' reactions from drive.') |
|
|
114 |
|
print('Loaded ' + str(len(reactions)) + ' reactions from drive.') |
106 |
115 |
return reactions |
return reactions |
107 |
116 |
|
|
|
117 |
|
|
File botly/settings.py changed (mode: 100644) (index a68773d..bad6ea3) |
1 |
|
from .db import Db |
|
|
1 |
|
#!/usr/bin/env python |
|
2 |
|
"""Loads and manages the bot settings.""" |
|
3 |
|
|
|
4 |
|
from botly.db import Db |
|
5 |
|
|
2 |
6 |
|
|
3 |
7 |
class Settings(Db): |
class Settings(Db): |
4 |
8 |
|
|
5 |
9 |
def __init__(self): |
def __init__(self): |
6 |
10 |
self.settings = {} |
self.settings = {} |
7 |
11 |
|
|
8 |
|
def _load_setting(self, key): |
|
9 |
|
assert self.is_loaded(), "Database is not loaded." |
|
10 |
|
value = 'None' |
|
11 |
|
setting = Db.dbxml.xpath('/BotDb/Settings/' + key) |
|
12 |
|
if setting: |
|
13 |
|
value = setting[0].text |
|
14 |
|
return value |
|
15 |
|
|
|
16 |
|
def get_string(self, key, default = False): |
|
|
12 |
|
def get_string(self, key, default='None'): |
|
13 |
|
"""Returns the string value if it exists.""" |
17 |
14 |
if not key in self.settings: |
if not key in self.settings: |
18 |
|
self.settings[key] = self._load_setting(key) |
|
|
15 |
|
self.settings[key] = self._load_setting(key, default) |
19 |
16 |
return self.settings[key] |
return self.settings[key] |
20 |
17 |
|
|
21 |
|
def get_int(self, key, default = False): |
|
22 |
|
v = self.get_string(key) |
|
23 |
|
if v == 'None': |
|
24 |
|
return '0' |
|
|
18 |
|
def get_int(self, key, default='0'): |
|
19 |
|
"""Returns the int value if it exists.""" |
|
20 |
|
# Default default is a string because passed to get_string. |
|
21 |
|
# It's converted back to int below |
|
22 |
|
v = self.get_string(key, default=default) |
25 |
23 |
return int(v) |
return int(v) |
26 |
24 |
|
|
|
25 |
|
def _load_setting(self, key, default): |
|
26 |
|
assert self.is_loaded(), "Database is not loaded." |
|
27 |
|
setting = Db.dbxml.xpath('/BotDb/Settings/' + key) |
|
28 |
|
if setting: |
|
29 |
|
return setting[0].text |
|
30 |
|
else: |
|
31 |
|
return default |
File botly/trigger.py changed (mode: 100644) (index 871224c..6a06731) |
1 |
|
import discord |
|
|
1 |
|
#!/usr/bin/env python |
|
2 |
|
"""Trigger class defines condition for a Reaction to trigger.""" |
|
3 |
|
|
2 |
4 |
import re |
import re |
3 |
|
import random |
|
|
5 |
|
from random import randrange |
|
6 |
|
|
|
7 |
|
import discord |
|
8 |
|
|
|
9 |
|
from botly.knowledge import Knowledge |
4 |
10 |
|
|
5 |
|
from .knowledge import Knowledge |
|
6 |
11 |
|
|
7 |
12 |
class Trigger: |
class Trigger: |
|
13 |
|
"""Defienes and check for the conditions.""" |
|
14 |
|
|
8 |
15 |
def __init__(self, eventName): |
def __init__(self, eventName): |
9 |
16 |
self.eventName = eventName |
self.eventName = eventName |
10 |
17 |
self.conditions = [] |
self.conditions = [] |
|
... |
... |
class Trigger: |
12 |
19 |
self.requireMention = False |
self.requireMention = False |
13 |
20 |
self.triggerChance = 100 |
self.triggerChance = 100 |
14 |
21 |
|
|
15 |
|
def _pattern_valid(self, pattern): |
|
16 |
|
try: |
|
17 |
|
re.compile(pattern) |
|
18 |
|
return True |
|
19 |
|
except re.error: |
|
20 |
|
return False |
|
21 |
22 |
|
|
22 |
23 |
def add_condition(self, variable, pattern): |
def add_condition(self, variable, pattern): |
|
24 |
|
"""Add a simple condition where variable should match the pattern. |
|
25 |
|
|
|
26 |
|
The possible variable names are 'message' and 'author'. |
|
27 |
|
The pattern is a Regular Expression pattern that should respect |
|
28 |
|
the standard regex format. More information on this in the Python |
|
29 |
|
documentation. |
23 |
30 |
""" |
""" |
24 |
|
Add a condition where given variable should match given |
|
25 |
|
regular expression pattern. |
|
26 |
|
""" |
|
27 |
|
assert self._pattern_valid(pattern), 'Given pattern is invalid.' |
|
|
31 |
|
assert self._is_pattern_valid(pattern), \ |
|
32 |
|
'Given regular expression pattern is invalid.' |
28 |
33 |
|
|
29 |
34 |
if 'message' == variable: |
if 'message' == variable: |
30 |
35 |
assert 'message' in self.eventName, \ |
assert 'message' in self.eventName, \ |
31 |
|
'message variable not expected for this event' |
|
|
36 |
|
'message variable not expected for this event.' |
32 |
37 |
elif 'author' == variable: |
elif 'author' == variable: |
33 |
38 |
assert eventName != 'on_ready', \ |
assert eventName != 'on_ready', \ |
34 |
|
'author not supported for on_ready event' |
|
|
39 |
|
'author not supported for on_ready event.' |
35 |
40 |
|
|
36 |
41 |
condition = [] |
condition = [] |
37 |
42 |
condition.append(variable) |
condition.append(variable) |
|
... |
... |
class Trigger: |
39 |
44 |
self.conditions.append(condition) |
self.conditions.append(condition) |
40 |
45 |
|
|
41 |
46 |
def add_adv_condition(self, callback): |
def add_adv_condition(self, callback): |
42 |
|
""" |
|
43 |
|
Added a function as the condition. It should return |
|
44 |
|
True if the condition is respected or False if not. |
|
45 |
|
It will be called with the full event info table. |
|
|
47 |
|
"""Add a condition based on a callback variable. |
|
48 |
|
|
|
49 |
|
The function will be called upon trigger verification. |
|
50 |
|
It should return True if the condition is respected or False if not. |
|
51 |
|
The event info table will be passed as an argument |
46 |
52 |
""" |
""" |
47 |
53 |
assert callable(callback), 'Given argument must be a callback function' |
assert callable(callback), 'Given argument must be a callback function' |
48 |
54 |
self.advConditions.append(callback) |
self.advConditions.append(callback) |
49 |
55 |
|
|
50 |
56 |
def set_trigger_chance(self, percent): |
def set_trigger_chance(self, percent): |
51 |
|
assert isinstance(value, int), 'Int expected.' |
|
|
57 |
|
assert isinstance(value, int), 'Int expected for trigger chance.' |
52 |
58 |
percent = percent if percent >= 1 else 1 |
percent = percent if percent >= 1 else 1 |
53 |
59 |
percent = percent if percent <= 100 else 100 |
percent = percent if percent <= 100 else 100 |
54 |
60 |
self.triggerChance = percent |
self.triggerChance = percent |
55 |
61 |
|
|
56 |
62 |
def require_mention(self, value): |
def require_mention(self, value): |
57 |
|
assert isinstance(value, bool), 'Bool expected.' |
|
|
63 |
|
"""Defines whether or not tihs bot has to be mentioned for trigger.""" |
|
64 |
|
assert isinstance(value, bool), 'Bool expected for require mention.' |
58 |
65 |
self.requireMention = value |
self.requireMention = value |
59 |
66 |
|
|
|
67 |
|
def is_triggered(self, botly, **eventInfo): |
|
68 |
|
"""This should only be called from Botly class. |
|
69 |
|
|
|
70 |
|
Checks whether or not the trigger object activates based on the |
|
71 |
|
given information. This is meant to be called from the Botly class |
|
72 |
|
that processes events and check for triggers. |
|
73 |
|
""" |
|
74 |
|
# Run random. Do we have a chance to trigger? |
|
75 |
|
if not self._can_we_trigger(): |
|
76 |
|
return False |
|
77 |
|
|
|
78 |
|
# Checks if bot is mentioned if it is required: |
|
79 |
|
if self.requireMention and 'message' in self.eventName: |
|
80 |
|
if not botly.me.mentioned_in(eventInfo['message']): |
|
81 |
|
return False |
|
82 |
|
|
|
83 |
|
# Check for conditions: |
|
84 |
|
for condition in self.conditions: |
|
85 |
|
if not self._is_condition_true(condition, **eventInfo): |
|
86 |
|
return False |
|
87 |
|
|
|
88 |
|
# Check advanced conditions: |
|
89 |
|
for condition in self.advConditions: |
|
90 |
|
if not condition(**eventInfo): |
|
91 |
|
return False |
|
92 |
|
|
|
93 |
|
return True |
|
94 |
|
|
60 |
95 |
def _can_we_trigger(self): |
def _can_we_trigger(self): |
61 |
96 |
if self.triggerChance == 100: |
if self.triggerChance == 100: |
62 |
97 |
return True |
return True |
63 |
|
return random.randrange(1, 101) < self.triggerChance |
|
|
98 |
|
return randrange(1, 101) < self.triggerChance |
64 |
99 |
|
|
65 |
100 |
def _is_condition_true(self, condition, **eventInfo): |
def _is_condition_true(self, condition, **eventInfo): |
66 |
101 |
v = condition[0] |
v = condition[0] |
|
... |
... |
class Trigger: |
78 |
113 |
if re.match(p, eventInfo['message'].content): |
if re.match(p, eventInfo['message'].content): |
79 |
114 |
return True |
return True |
80 |
115 |
|
|
81 |
|
def is_triggered(self, botly, **eventInfo): |
|
82 |
|
# Run random. Do we have a chance to trigger? |
|
83 |
|
if not self._can_we_trigger(): |
|
|
116 |
|
def _is_pattern_valid(self, pattern): |
|
117 |
|
try: |
|
118 |
|
re.compile(pattern) |
|
119 |
|
return True |
|
120 |
|
except re.error: |
84 |
121 |
return False |
return False |
85 |
|
|
|
86 |
|
# Checks if bot is mentioned if it is required: |
|
87 |
|
if 'message' in self.eventName and self.requireMention: |
|
88 |
|
if not botly.me.mentioned_in(eventInfo['message']): |
|
89 |
|
return False |
|
90 |
|
|
|
91 |
|
# Check for conditions: |
|
92 |
|
for condition in self.conditions: |
|
93 |
|
if not self._is_condition_true(condition, **eventInfo): |
|
94 |
|
return False |
|
95 |
122 |
|
|
96 |
|
# Check advanced conditions: |
|
97 |
|
for condition in self.advConditions: |
|
98 |
|
if not condition(**eventInfo): |
|
99 |
|
return False |
|
100 |
|
|
|
101 |
|
# We trigger yo. |
|
102 |
|
return True |
|
103 |
123 |
|
|