@@ -41,16 +41,15 @@ def __init__(self, bot):
41
41
self .registry = {}
42
42
self .bot .loop .create_task (self .download_initial_plugins ())
43
43
self .bot .loop .create_task (self .populate_registry ())
44
-
44
+
45
45
async def populate_registry (self ):
46
46
url = 'https://raw.githubusercontent.com/kyb3r/modmail/master/plugins/registry.json'
47
47
async with self .bot .session .get (url ) as resp :
48
48
self .registry = json .loads (await resp .text ())
49
49
50
50
@staticmethod
51
51
def _asubprocess_run (cmd ):
52
- return subprocess .run (cmd , shell = True , check = True ,
53
- capture_output = True )
52
+ return subprocess .run (cmd , shell = True , check = True , capture_output = True )
54
53
55
54
@staticmethod
56
55
def parse_plugin (name ):
@@ -60,10 +59,12 @@ def parse_plugin(name):
60
59
result [2 ] = '/' .join (result [2 :])
61
60
except IndexError :
62
61
return None
62
+
63
63
return tuple (result )
64
64
65
65
async def download_initial_plugins (self ):
66
66
await self .bot ._connected .wait ()
67
+
67
68
for i in self .bot .config .plugins :
68
69
parsed_plugin = self .parse_plugin (i )
69
70
@@ -78,29 +79,29 @@ async def download_initial_plugins(self):
78
79
except DownloadError as exc :
79
80
msg = f'{ parsed_plugin [0 ]} /{ parsed_plugin [1 ]} - { exc } '
80
81
logger .error (error (msg ))
82
+
81
83
await async_all (env () for env in self .bot .extra_events .get ('on_plugin_ready' , []))
84
+
82
85
logger .debug (info ('on_plugin_ready called.' ))
83
86
84
87
async def download_plugin_repo (self , username , repo ):
85
88
try :
86
89
cmd = f'git clone https://github.com/{ username } /{ repo } '
87
90
cmd += f'plugins/{ username } -{ repo } -q'
88
- await self .bot .loop .run_in_executor (
89
- None ,
90
- self ._asubprocess_run ,
91
- cmd
92
- )
91
+
92
+ await self .bot .loop .run_in_executor (None , self ._asubprocess_run , cmd )
93
93
# -q (quiet) so there's no terminal output unless there's an error
94
94
except subprocess .CalledProcessError as exc :
95
95
err = exc .stderr .decode ('utf-8' ).strip ()
96
- if not err . endswith ( 'already exists and is '
97
- ' not an empty directory.' ):
96
+
97
+ if not err . endswith ( 'already exists and is not an empty directory.' ):
98
98
# don't raise error if the plugin folder exists
99
99
raise DownloadError (error ) from exc
100
100
101
101
async def load_plugin (self , username , repo , plugin_name ):
102
102
ext = f'plugins.{ username } -{ repo } .{ plugin_name } .{ plugin_name } '
103
103
dirname = f'plugins/{ username } -{ repo } /{ plugin_name } '
104
+
104
105
if 'requirements.txt' in os .listdir (dirname ):
105
106
# Install PIP requirements
106
107
try :
@@ -113,6 +114,7 @@ async def load_plugin(self, username, repo, plugin_name):
113
114
# so there's no terminal output unless there's an error
114
115
except subprocess .CalledProcessError as exc :
115
116
err = exc .stderr .decode ('utf8' ).strip ()
117
+
116
118
if err :
117
119
raise DownloadError (
118
120
f'Unable to download requirements: ```\n { error } \n ```'
@@ -135,68 +137,105 @@ async def load_plugin(self, username, repo, plugin_name):
135
137
@checks .has_permissions (PermissionLevel .OWNER )
136
138
async def plugin (self , ctx ):
137
139
"""Plugin handler. Controls the plugins in the bot."""
140
+
138
141
await ctx .send_help (ctx .command )
139
142
140
143
@plugin .command (name = 'add' , aliases = ['install' ])
141
144
@checks .has_permissions (PermissionLevel .OWNER )
142
145
async def plugin_add (self , ctx , * , plugin_name : str ):
143
146
"""Add a plugin."""
147
+
144
148
if plugin_name in self .registry :
145
149
info = self .registry [plugin_name ]
146
150
plugin_name = info ['repository' ] + '/' + plugin_name
147
151
required_version = info ['bot_version' ]
152
+
148
153
if parse_version (self .bot .version ) < parse_version (required_version ):
149
- return await ctx .send (f"Bot version too low, plugin requires version `{ required_version } `" )
154
+ em = discord .Embed (
155
+ description = f'Your bot\' s version is too low. This plugin requires version `{ required_version } `.' ,
156
+ color = self .bot .main_color
157
+ )
158
+ return await ctx .send (embed = em )
159
+
150
160
if plugin_name in self .bot .config .plugins :
151
- return await ctx .send ('Plugin already installed.' )
161
+ em = discord .Embed (
162
+ description = 'This plugin is already installed.' ,
163
+ color = self .bot .main_color
164
+ )
165
+ return await ctx .send (embed = em )
166
+
152
167
if plugin_name in self .bot .cogs .keys ():
153
168
# another class with the same name
154
- return await ctx .send ('Another cog exists with the same name.' )
169
+ em = discord .Embed (
170
+ description = 'There\' s another cog installed with the same name.' ,
171
+ color = self .bot .main_color
172
+ )
173
+ return await ctx .send (embed = em )
174
+
175
+ em = discord .Embed (
176
+ description = 'Downloading this plugin...' ,
177
+ color = self .bot .main_color
178
+ )
179
+ message = await ctx .send (embed = em )
155
180
156
- message = await ctx .send ('Downloading plugin...' )
157
181
async with ctx .typing ():
158
182
if len (plugin_name .split ('/' )) >= 3 :
159
183
parsed_plugin = self .parse_plugin (plugin_name )
160
184
161
185
try :
162
186
await self .download_plugin_repo (* parsed_plugin [:- 1 ])
163
187
except DownloadError as exc :
164
- return await ctx .send (
165
- f'Unable to fetch plugin from Github: { exc } .'
188
+ em = discord .Embed (
189
+ description = f'Unable to fetch this plugin from Github: { exc } .' ,
190
+ color = self .bot .main_color
166
191
)
192
+ return await ctx .send (embed = em )
167
193
168
194
importlib .invalidate_caches ()
195
+
169
196
try :
170
197
await self .load_plugin (* parsed_plugin )
171
198
except DownloadError as exc :
172
- return await ctx .send (f'Unable to load plugin: `{ exc } `.' )
199
+ em = discord .Embed (
200
+ description = f'Unable to load this plugin: { exc } .' ,
201
+ color = self .bot .main_color
202
+ )
203
+ return await ctx .send (embed = em )
173
204
174
205
# if it makes it here, it has passed all checks and should
175
206
# be entered into the config
176
207
177
208
self .bot .config .plugins .append (plugin_name )
178
209
await self .bot .config .update ()
179
210
180
- await message .edit (content = 'Plugin installed. Any plugin that '
181
- 'you install is of your OWN RISK.' )
211
+ em = discord .Embed (
212
+ description = 'The plugin is installed.\n '
213
+ '*Please note: any plugin that you install is of your OWN RISK*' ,
214
+ color = self .bot .main_color
215
+ )
216
+ await message .edit (embed = em )
182
217
else :
183
- await message .edit (content = 'Invalid plugin name format. '
184
- 'Use username/repo/plugin.' )
218
+ em = discord .Embed (
219
+ description = 'Invalid plugin name format: use username/repo/plugin.' ,
220
+ color = self .bot .main_color
221
+ )
222
+ await message .edit (embed = em )
185
223
186
224
@plugin .command (name = 'remove' , aliases = ['del' , 'delete' , 'rm' ])
187
225
@checks .has_permissions (PermissionLevel .OWNER )
188
226
async def plugin_remove (self , ctx , * , plugin_name : str ):
189
227
"""Remove a plugin."""
228
+
190
229
if plugin_name in self .registry :
191
230
info = self .registry [plugin_name ]
192
231
plugin_name = info ['repository' ] + '/' + plugin_name
232
+
193
233
if plugin_name in self .bot .config .plugins :
194
234
try :
195
235
username , repo , name = self .parse_plugin (plugin_name )
196
- self .bot .unload_extension (
197
- f'plugins.{ username } -{ repo } .{ name } .{ name } '
198
- )
199
- except :
236
+
237
+ self .bot .unload_extension (f'plugins.{ username } -{ repo } .{ name } .{ name } ' )
238
+ except Exception :
200
239
pass
201
240
202
241
self .bot .config .plugins .remove (plugin_name )
@@ -211,67 +250,102 @@ def onerror(func, path, exc_info): # pylint: disable=W0613
211
250
os .chmod (path , stat .S_IWUSR )
212
251
func (path )
213
252
214
- shutil .rmtree (f'plugins/{ username } -{ repo } ' ,
215
- onerror = onerror )
253
+ shutil .rmtree (f'plugins/{ username } -{ repo } ' , onerror = onerror )
216
254
except Exception as exc :
217
255
logger .error (str (exc ))
218
256
self .bot .config .plugins .append (plugin_name )
219
257
raise exc
220
258
221
259
await self .bot .config .update ()
222
- await ctx .send ('Plugin uninstalled and '
223
- 'all related data is erased.' )
260
+
261
+ em = discord .Embed (
262
+ description = 'The plugin is uninstalled and all its data is erased.' ,
263
+ color = self .bot .main_color
264
+ )
265
+ await ctx .send (embed = em )
224
266
else :
225
- await ctx .send ('Plugin not installed.' )
267
+ em = discord .Embed (
268
+ description = 'That plugin is not installed.' ,
269
+ color = self .bot .main_color
270
+ )
271
+ await ctx .send (embed = em )
226
272
227
273
@plugin .command (name = 'update' )
228
274
@checks .has_permissions (PermissionLevel .OWNER )
229
275
async def plugin_update (self , ctx , * , plugin_name : str ):
230
276
"""Update a plugin."""
277
+
231
278
if plugin_name in self .registry :
232
279
info = self .registry [plugin_name ]
233
280
plugin_name = info ['repository' ] + '/' + plugin_name
281
+
234
282
if plugin_name not in self .bot .config .plugins :
235
- return await ctx .send ('Plugin not installed.' )
283
+ em = discord .Embed (
284
+ description = 'That plugin is not installed.' ,
285
+ color = self .bot .main_color
286
+ )
287
+ return await ctx .send (embed = em )
236
288
237
289
async with ctx .typing ():
238
290
username , repo , name = self .parse_plugin (plugin_name )
291
+
239
292
try :
240
293
cmd = f'cd plugins/{ username } -{ repo } && git pull'
241
- cmd = await self .bot .loop .run_in_executor (
242
- None ,
243
- self ._asubprocess_run ,
244
- cmd
245
- )
294
+ cmd = await self .bot .loop .run_in_executor (None , self ._asubprocess_run , cmd )
246
295
except subprocess .CalledProcessError as exc :
247
296
err = exc .stderr .decode ('utf8' ).strip ()
248
- await ctx .send (f'Error while updating: { err } .' )
297
+
298
+ em = discord .Embed (
299
+ description = f'An error occured while updating: { err } .' ,
300
+ color = self .bot .main_color
301
+ )
302
+ await ctx .send (embed = em )
303
+
249
304
else :
250
305
output = cmd .stdout .decode ('utf8' ).strip ()
251
- await ctx .send (f'```\n { output } \n ```' )
306
+
307
+ em = discord .Embed (
308
+ description = f'```\n { output } \n ```' ,
309
+ color = self .bot .main_color
310
+ )
311
+ await ctx .send (embed = em )
252
312
253
313
if output != 'Already up to date.' :
254
314
# repo was updated locally, now perform the cog reload
255
315
ext = f'plugins.{ username } -{ repo } .{ name } .{ name } '
256
316
self .bot .unload_extension (ext )
317
+
257
318
try :
258
319
await self .load_plugin (username , repo , name )
259
320
except DownloadError as exc :
260
- await ctx .send (f'Unable to start plugin: `{ exc } `.' )
321
+ em = discord .Embed (
322
+ description = f'Unable to start the plugin: `{ exc } `.' ,
323
+ color = self .bot .main_color
324
+ )
325
+ await ctx .send (embed = em )
261
326
262
327
@plugin .command (name = 'enabled' , aliases = ['installed' ])
263
328
@checks .has_permissions (PermissionLevel .OWNER )
264
329
async def plugin_enabled (self , ctx ):
265
330
"""Shows a list of currently enabled plugins."""
331
+
266
332
if self .bot .config .plugins :
267
333
msg = '```\n ' + '\n ' .join (self .bot .config .plugins ) + '\n ```'
268
- await ctx .send (msg )
334
+ em = discord .Embed (
335
+ description = msg ,
336
+ color = self .bot .main_color
337
+ )
338
+ await ctx .send (embed = em )
269
339
else :
270
- await ctx .send ('No plugins installed.' )
340
+ em = discord .Embed (
341
+ description = 'There are no plugins installed.' ,
342
+ color = self .bot .main_color
343
+ )
344
+ await ctx .send (embed = em )
271
345
272
346
@plugin .group (invoke_without_command = True , name = 'registry' , aliases = ['list' ])
273
347
@checks .has_permissions (PermissionLevel .OWNER )
274
- async def plugin_registry (self , ctx , * , plugin_name :str = None ):
348
+ async def plugin_registry (self , ctx , * , plugin_name : str = None ):
275
349
"""Shows a list of all approved plugins."""
276
350
277
351
await self .populate_registry ()
@@ -293,13 +367,15 @@ def find_index(name):
293
367
index = find_index (plugin_name )
294
368
elif plugin_name is not None :
295
369
em = discord .Embed (
296
- color = discord .Color .red (),
297
- description = f'Could not find a plugin with name "{ plugin_name } " within the registry.'
298
- )
370
+ color = discord .Color .red (),
371
+ description = f'Could not find a plugin with name "{ plugin_name } " within the registry.'
372
+ )
299
373
300
374
matches = get_close_matches (plugin_name , self .registry .keys ())
375
+
301
376
if matches :
302
377
em .add_field (name = 'Perhaps you meant' , value = '\n ' .join (f'`{ m } `' for m in matches ))
378
+
303
379
return await ctx .send (embed = em )
304
380
305
381
for name , info in registry :
@@ -312,11 +388,11 @@ def find_index(name):
312
388
url = repo ,
313
389
title = info ['repository' ]
314
390
)
315
-
391
+
316
392
em .add_field (
317
- name = 'Installation' ,
393
+ name = 'Installation' ,
318
394
value = f'```{ self .bot .prefix } plugins add { name } ```' )
319
-
395
+
320
396
em .set_author (name = info ['title' ], icon_url = info .get ('icon_url' ), url = url )
321
397
if info .get ('thumbnail_url' ):
322
398
em .set_thumbnail (url = info .get ('thumbnail_url' ))
@@ -351,12 +427,12 @@ async def plugin_registry_compact(self, ctx):
351
427
pages .append (fmt + '\n ' )
352
428
else :
353
429
pages [- 1 ] += fmt + '\n '
354
-
430
+
355
431
embeds = []
356
432
357
433
for page in pages :
358
434
em = discord .Embed (
359
- color = self .bot .main_color ,
435
+ color = self .bot .main_color ,
360
436
description = page ,
361
437
)
362
438
em .set_author (name = 'Plugin Registry' , icon_url = self .bot .user .avatar_url )
@@ -366,9 +442,5 @@ async def plugin_registry_compact(self, ctx):
366
442
await paginator .run ()
367
443
368
444
369
-
370
-
371
-
372
-
373
445
def setup (bot ):
374
446
bot .add_cog (Plugins (bot ))
0 commit comments