1
+ import asyncio
1
2
import importlib
2
3
import json
3
4
import logging
13
14
import discord
14
15
from discord .ext import commands
15
16
from discord .utils import async_all
16
-
17
17
from pkg_resources import parse_version
18
18
19
19
from core import checks
20
20
from core .models import PermissionLevel
21
21
from core .paginator import PaginatorSession
22
- from core .utils import info , error
22
+ from core .utils import error , info
23
23
24
24
logger = logging .getLogger ("Modmail" )
25
25
@@ -55,10 +55,20 @@ def _asubprocess_run(cmd):
55
55
56
56
@staticmethod
57
57
def parse_plugin (name ):
58
- # returns: (username, repo, plugin_name)
58
+ # returns: (username, repo, plugin_name, branch)
59
+ # default branch = master
59
60
try :
60
61
result = name .split ("/" )
61
62
result [2 ] = "/" .join (result [2 :])
63
+ if '@' in result [2 ]:
64
+ # branch is specified
65
+ # for example, fourjr/modmail-plugins/welcomer@develop is a valid name
66
+ branchsplit_result = result [2 ].split ('@' )
67
+ result .append (branchsplit_result [- 1 ])
68
+ result [2 ] = '@' .join (branchsplit_result [:- 1 ])
69
+ else :
70
+ result .append ('master' )
71
+
62
72
except IndexError :
63
73
return None
64
74
@@ -68,18 +78,18 @@ async def download_initial_plugins(self):
68
78
await self .bot ._connected .wait ()
69
79
70
80
for i in self .bot .config .plugins :
71
- parsed_plugin = self .parse_plugin (i )
81
+ username , repo , name , branch = self .parse_plugin (i )
72
82
73
83
try :
74
- await self .download_plugin_repo (* parsed_plugin [: - 1 ] )
84
+ await self .download_plugin_repo (username , repo , branch )
75
85
except DownloadError as exc :
76
- msg = f"{ parsed_plugin [ 0 ] } /{ parsed_plugin [ 1 ] } - { exc } "
86
+ msg = f"{ username } /{ repo } @ { branch } - { exc } "
77
87
logger .error (error (msg ))
78
88
else :
79
89
try :
80
- await self .load_plugin (* parsed_plugin )
90
+ await self .load_plugin (username , repo , name , branch )
81
91
except DownloadError as exc :
82
- msg = f"{ parsed_plugin [ 0 ] } /{ parsed_plugin [ 1 ] } - { exc } "
92
+ msg = f"{ username } /{ repo } @ { branch } [ { name } ] - { exc } "
83
93
logger .error (error (msg ))
84
94
85
95
await async_all (
@@ -88,10 +98,11 @@ async def download_initial_plugins(self):
88
98
89
99
logger .debug (info ("on_plugin_ready called." ))
90
100
91
- async def download_plugin_repo (self , username , repo ):
101
+ async def download_plugin_repo (self , username , repo , branch ):
92
102
try :
93
- cmd = f"git clone https://github.com/{ username } /{ repo } "
94
- cmd += f"plugins/{ username } -{ repo } -q"
103
+ cmd = f"git clone https://github.com/{ username } /{ repo } "
104
+ cmd += f"plugins/{ username } -{ repo } -{ branch } "
105
+ cmd += f"-b { branch } -q"
95
106
96
107
await self .bot .loop .run_in_executor (None , self ._asubprocess_run , cmd )
97
108
# -q (quiet) so there's no terminal output unless there's an error
@@ -100,11 +111,13 @@ async def download_plugin_repo(self, username, repo):
100
111
101
112
if not err .endswith ("already exists and is not an empty directory." ):
102
113
# don't raise error if the plugin folder exists
103
- raise DownloadError (error ) from exc
114
+ msg = f'Download Error: { username } /{ repo } @{ branch } '
115
+ logger .error (msg )
116
+ raise DownloadError (err ) from exc
104
117
105
- async def load_plugin (self , username , repo , plugin_name ):
106
- ext = f"plugins.{ username } -{ repo } .{ plugin_name } .{ plugin_name } "
107
- dirname = f"plugins/{ username } -{ repo } /{ plugin_name } "
118
+ async def load_plugin (self , username , repo , plugin_name , branch ):
119
+ ext = f"plugins.{ username } -{ repo } - { branch } .{ plugin_name } .{ plugin_name } "
120
+ dirname = f"plugins/{ username } -{ repo } - { branch } /{ plugin_name } "
108
121
109
122
if "requirements.txt" in os .listdir (dirname ):
110
123
# Install PIP requirements
@@ -113,7 +126,7 @@ async def load_plugin(self, username, repo, plugin_name):
113
126
await self .bot .loop .run_in_executor (
114
127
None ,
115
128
self ._asubprocess_run ,
116
- f"pip install -r { dirname } /requirements.txt -q -q" ,
129
+ f"pip install -r { dirname } /requirements.txt --user - q -q" ,
117
130
)
118
131
else :
119
132
await self .bot .loop .run_in_executor (
@@ -128,24 +141,26 @@ async def load_plugin(self, username, repo, plugin_name):
128
141
err = exc .stderr .decode ("utf8" ).strip ()
129
142
130
143
if err :
144
+ msg = f'Requirements Download Error: { username } /{ repo } @{ branch } [{ plugin_name } ]'
145
+ logger .error (error (msg ))
131
146
raise DownloadError (
132
- f"Unable to download requirements: ```\n { error } \n ```"
147
+ f"Unable to download requirements: ```\n { err } \n ```"
133
148
) from exc
134
149
else :
135
150
if not os .path .exists (site .USER_SITE ):
136
151
os .makedirs (site .USER_SITE )
137
152
138
153
sys .path .insert (0 , site .USER_SITE )
139
154
155
+ await asyncio .sleep (0.5 )
140
156
try :
141
157
self .bot .load_extension (ext )
142
158
except commands .ExtensionError as exc :
143
- # TODO: Add better error handling for plugin load faliure
144
- import traceback
145
- traceback .print_exc ()
159
+ msg = f'Plugin Load Failure: { username } /{ repo } @{ branch } [{ plugin_name } ]'
160
+ logger .error (error (msg ))
146
161
raise DownloadError ("Invalid plugin" ) from exc
147
162
else :
148
- msg = f"Loaded plugins. { username } - { repo } . { plugin_name } "
163
+ msg = f"Loaded Plugin: { username } / { repo } @ { branch } [ { plugin_name } ] "
149
164
logger .info (info (msg ))
150
165
151
166
@commands .group (aliases = ["plugins" ], invoke_without_command = True )
@@ -162,7 +177,7 @@ async def plugin_add(self, ctx, *, plugin_name: str):
162
177
163
178
if plugin_name in self .registry :
164
179
details = self .registry [plugin_name ]
165
- plugin_name = details ["repository" ] + "/" + plugin_name
180
+ plugin_name = details ["repository" ] + "/" + plugin_name + "@" + details [ "branch" ]
166
181
required_version = details ["bot_version" ]
167
182
168
183
if parse_version (self .bot .version ) < parse_version (required_version ):
@@ -194,24 +209,24 @@ async def plugin_add(self, ctx, *, plugin_name: str):
194
209
195
210
async with ctx .typing ():
196
211
if len (plugin_name .split ("/" )) >= 3 :
197
- parsed_plugin = self .parse_plugin (plugin_name )
212
+ username , repo , name , branch = self .parse_plugin (plugin_name )
198
213
199
214
try :
200
- await self .download_plugin_repo (* parsed_plugin [: - 1 ] )
215
+ await self .download_plugin_repo (username , repo , branch )
201
216
except DownloadError as exc :
202
217
embed = discord .Embed (
203
- description = f"Unable to fetch this plugin from Github: { exc } ." ,
218
+ description = f"Unable to fetch this plugin from Github: ` { exc } ` ." ,
204
219
color = self .bot .main_color ,
205
220
)
206
221
return await ctx .send (embed = embed )
207
222
208
223
importlib .invalidate_caches ()
209
224
210
225
try :
211
- await self .load_plugin (* parsed_plugin )
226
+ await self .load_plugin (username , repo , name , branch )
212
227
except Exception as exc :
213
228
embed = discord .Embed (
214
- description = f"Unable to load this plugin: { exc } ." ,
229
+ description = f"Unable to load this plugin: ` { exc } ` ." ,
215
230
color = self .bot .main_color ,
216
231
)
217
232
return await ctx .send (embed = embed )
@@ -224,7 +239,7 @@ async def plugin_add(self, ctx, *, plugin_name: str):
224
239
225
240
embed = discord .Embed (
226
241
description = "The plugin is installed.\n "
227
- "*Please note: any plugin that you install is at your OWN RISK *" ,
242
+ "*Please note: Any plugin that you install is at your **own risk** *" ,
228
243
color = self .bot .main_color ,
229
244
)
230
245
await ctx .send (embed = embed )
@@ -242,13 +257,13 @@ async def plugin_remove(self, ctx, *, plugin_name: str):
242
257
243
258
if plugin_name in self .registry :
244
259
details = self .registry [plugin_name ]
245
- plugin_name = details ["repository" ] + "/" + plugin_name
260
+ plugin_name = details ["repository" ] + "/" + plugin_name + "@" + details [ "branch" ]
246
261
247
262
if plugin_name in self .bot .config .plugins :
248
263
try :
249
- username , repo , name = self .parse_plugin (plugin_name )
264
+ username , repo , name , branch = self .parse_plugin (plugin_name )
250
265
251
- self .bot .unload_extension (f"plugins.{ username } -{ repo } .{ name } .{ name } " )
266
+ self .bot .unload_extension (f"plugins.{ username } -{ repo } - { branch } .{ name } .{ name } " )
252
267
except Exception :
253
268
pass
254
269
@@ -266,10 +281,11 @@ def onerror(func, path, exc_info): # pylint: disable=W0613
266
281
os .chmod (path , stat .S_IWUSR )
267
282
func (path )
268
283
269
- shutil .rmtree (f"plugins/{ username } -{ repo } " , onerror = onerror )
284
+ shutil .rmtree (f"plugins/{ username } -{ repo } - { branch } " , onerror = onerror )
270
285
except Exception as exc :
271
286
logger .error (str (exc ))
272
287
self .bot .config .plugins .append (plugin_name )
288
+ logger .error (error (exc ))
273
289
raise exc
274
290
275
291
await self .bot .config .update ()
@@ -292,7 +308,7 @@ async def plugin_update(self, ctx, *, plugin_name: str):
292
308
293
309
if plugin_name in self .registry :
294
310
details = self .registry [plugin_name ]
295
- plugin_name = details ["repository" ] + "/" + plugin_name
311
+ plugin_name = details ["repository" ] + "/" + plugin_name + "@" + details [ "branch" ]
296
312
297
313
if plugin_name not in self .bot .config .plugins :
298
314
embed = discord .Embed (
@@ -301,10 +317,10 @@ async def plugin_update(self, ctx, *, plugin_name: str):
301
317
return await ctx .send (embed = embed )
302
318
303
319
async with ctx .typing ():
304
- username , repo , name = self .parse_plugin (plugin_name )
320
+ username , repo , name , branch = self .parse_plugin (plugin_name )
305
321
306
322
try :
307
- cmd = f"cd plugins/{ username } -{ repo } && git reset --hard origin/master && git fetch --all && git pull"
323
+ cmd = f"cd plugins/{ username } -{ repo } - { branch } && git reset --hard origin/master && git fetch --all && git pull"
308
324
cmd = await self .bot .loop .run_in_executor (
309
325
None , self ._asubprocess_run , cmd
310
326
)
@@ -327,11 +343,11 @@ async def plugin_update(self, ctx, *, plugin_name: str):
327
343
328
344
if output != "Already up to date." :
329
345
# repo was updated locally, now perform the cog reload
330
- ext = f"plugins.{ username } -{ repo } .{ name } .{ name } "
346
+ ext = f"plugins.{ username } -{ repo } - { branch } .{ name } .{ name } "
331
347
self .bot .unload_extension (ext )
332
348
333
349
try :
334
- await self .load_plugin (username , repo , name )
350
+ await self .load_plugin (username , repo , name , branch )
335
351
except DownloadError as exc :
336
352
em = discord .Embed (
337
353
description = f"Unable to start the plugin: `{ exc } `." ,
@@ -434,7 +450,7 @@ async def plugin_registry_compact(self, ctx):
434
450
435
451
for name , details in registry :
436
452
repo = f"https://github.com/{ details ['repository' ]} "
437
- url = f"{ repo } /tree/master /{ name } "
453
+ url = f"{ repo } /tree/{ details [ 'branch' ] } /{ name } "
438
454
desc = details ["description" ].replace ("\n " , "" )
439
455
fmt = f"[`{ name } `]({ url } ) - { desc } "
440
456
length = len (fmt ) - len (url ) - 4
0 commit comments