1
+ from collections import namedtuple
2
+ from typing import Iterable , List , Sequence
1
3
import json
2
4
import logging
3
5
import os
12
14
logger = logging .getLogger (__name__ )
13
15
14
16
replies_cache = pylru .lrucache (1000 )
17
+ Module = namedtuple ("Module" , "code,title" )
15
18
16
19
17
20
class OUModulesBot (discord .Client ):
@@ -23,8 +26,12 @@ def __init__(self, *args, **kwargs):
23
26
super ().__init__ (* args , ** kwargs )
24
27
self .backend = OUModulesBackend ()
25
28
26
- async def do_mentions (self , message ):
27
- modules = []
29
+ async def process_mentions (self , message : discord .Message ) -> None :
30
+ """
31
+ Process module code mentions from given `message`, and reply with
32
+ thieir names/URLs if any were found.
33
+ """
34
+ modules : List [Module ] = []
28
35
any_found = False
29
36
for module in self .MENTION_RE .findall (message .content )[
30
37
: self .MODULES_COUNT_LIMIT
@@ -33,14 +40,23 @@ async def do_mentions(self, message):
33
40
title = await self .backend .get_module_title (module_code )
34
41
if title :
35
42
any_found = True
36
- modules .append ((module_code , title ))
43
+ modules .append (Module (module_code , title ))
37
44
else :
38
- modules .append ((module_code , "not found" ))
45
+ modules .append (Module (module_code , "not found" ))
39
46
if any_found :
40
47
# don't spam unless we're sure we at least found some modules
41
48
await self .post_modules (message , modules )
42
49
43
- async def format_course (self , code , title , for_embed = False ):
50
+ async def format_course (
51
+ self , code : str , title : str , for_embed : bool = False
52
+ ) -> str :
53
+ """
54
+ Return a string describing a module ready for posting to Discord,
55
+ for given module `code` and `title`. Appends URL if available.
56
+
57
+ Uses more compact formatting if `for_embed` is True, which should
58
+ be used if multiple modules are presented as part of an embed.
59
+ """
44
60
fmt = " * {} " if for_embed else "{}"
45
61
fmt_link = " * [{}]({}) " if for_embed else "{} ({})"
46
62
url = await self .backend .get_module_url (code )
@@ -53,24 +69,38 @@ async def format_course(self, code, title, for_embed=False):
53
69
else :
54
70
return "{}: {}" .format (code , result )
55
71
56
- async def _embed_modules (self , embed , modules ):
72
+ async def embed_modules (
73
+ self , embed : discord .Embed , modules : Iterable [Module ]
74
+ ) -> None :
75
+ """
76
+ Adds `embed` fields for each provided module.
77
+ """
57
78
for (code , title ) in modules :
58
79
embed .add_field (
59
80
name = code ,
60
81
value = await self .format_course (code , title , for_embed = True ),
61
82
inline = True ,
62
83
)
63
84
64
- async def post_modules (self , message , modules ):
85
+ async def post_modules (
86
+ self , message : discord .Message , modules : Sequence [Module ]
87
+ ) -> None :
88
+ """
89
+ Create or update a bot message for given users's input message,
90
+ and a list of modules.
91
+
92
+ Message is updated instead of created if the input was already replied
93
+ to, which means this time the input was edited.
94
+ """
65
95
modify_message = None
66
96
if message .id in replies_cache :
67
97
modify_message = replies_cache [message .id ]
68
98
69
99
embed = discord .Embed ()
70
100
if len (modules ) > 1 :
71
101
content = " " # force removal when modifying
72
- await self ._embed_modules (embed , modules )
73
- elif len (modules ) > 0 :
102
+ await self .embed_modules (embed , modules )
103
+ elif len (modules ) == 1 :
74
104
code , title = modules [0 ]
75
105
content = await self .format_course (code , title )
76
106
else :
@@ -88,11 +118,13 @@ async def post_modules(self, message, modules):
88
118
content , embed = embed if len (modules ) > 1 else None
89
119
)
90
120
91
- async def on_message (self , message ) :
92
- await self .do_mentions (message )
121
+ async def on_message (self , message : discord . Message ) -> None :
122
+ await self .process_mentions (message )
93
123
94
- async def on_message_edit (self , before , after ):
95
- await self .do_mentions (after )
124
+ async def on_message_edit (
125
+ self , before : discord .Message , after : discord .Message
126
+ ) -> None :
127
+ await self .process_mentions (after )
96
128
97
129
98
130
def main ():
0 commit comments