1
+ import asyncio
1
2
import importlib
2
- import subprocess
3
+ import os
3
4
import shutil
5
+ import stat
6
+ import subprocess
4
7
5
8
from colorama import Fore , Style
6
9
from discord .ext import commands
7
10
8
- from core .decorators import owner_only
9
11
from core .models import Bot
10
12
11
13
@@ -24,6 +26,10 @@ def __init__(self, bot: Bot):
24
26
self .bot = bot
25
27
self .bot .loop .create_task (self .download_initial_plugins ())
26
28
29
+ def _asubprocess_run (self , cmd ):
30
+ return subprocess .run (cmd , shell = True , check = True ,
31
+ capture_output = True )
32
+
27
33
@staticmethod
28
34
def parse_plugin (name ):
29
35
# returns: (username, repo, plugin_name)
@@ -42,18 +48,20 @@ async def download_initial_plugins(self):
42
48
try :
43
49
await self .download_plugin_repo (* parsed_plugin [:- 1 ])
44
50
except DownloadError as exc :
45
- msg = 'Unable to download plugin '
46
- msg += f'({ parsed_plugin [0 ]} /{ parsed_plugin [1 ]} - { exc } '
51
+ msg = f'{ exc } ({ parsed_plugin [0 ]} /{ parsed_plugin [1 ]} - { exc } '
47
52
print (Fore .RED + msg + Style .RESET_ALL )
48
53
49
54
await self .load_plugin (* parsed_plugin )
50
55
51
- @staticmethod
52
- async def download_plugin_repo (username , repo ):
56
+ async def download_plugin_repo (self , username , repo ):
53
57
try :
54
58
cmd = f'git clone https://github.com/{ username } /{ repo } '
55
59
cmd += f'plugins/{ username } -{ repo } -q'
56
- subprocess .run (cmd , check = True , capture_output = True )
60
+ await self .bot .loop .run_in_executor (
61
+ None ,
62
+ self ._asubprocess_run ,
63
+ cmd
64
+ )
57
65
# -q (quiet) so there's no terminal output unless there's an error
58
66
except subprocess .CalledProcessError as exc :
59
67
error = exc .stderr .decode ('utf-8' ).strip ()
@@ -63,17 +71,17 @@ async def download_plugin_repo(username, repo):
63
71
raise DownloadError (error ) from exc
64
72
65
73
async def load_plugin (self , username , repo , plugin_name ):
74
+ ext = f'plugins.{ username } -{ repo } .{ plugin_name } .{ plugin_name } '
66
75
try :
67
- ext = f'plugins.{ username } -{ repo } .{ plugin_name } .{ plugin_name } '
68
76
self .bot .load_extension (ext )
69
77
except ModuleNotFoundError as exc :
70
78
raise DownloadError ('Invalid plugin structure' ) from exc
71
79
else :
72
- msg = f'Loading plugins.{ username } -{ repo } .{ plugin_name } '
80
+ msg = f'Loaded plugins.{ username } -{ repo } .{ plugin_name } '
73
81
print (Fore .LIGHTCYAN_EX + msg + Style .RESET_ALL )
74
82
75
83
@commands .group (aliases = ['plugins' ])
76
- @owner_only ()
84
+ @commands . is_owner ()
77
85
async def plugin (self , ctx ):
78
86
"""Plugin handler. Controls the plugins in the bot."""
79
87
if ctx .invoked_subcommand is None :
@@ -83,7 +91,7 @@ async def plugin(self, ctx):
83
91
@plugin .command ()
84
92
async def add (self , ctx , * , plugin_name ):
85
93
"""Adds a plugin"""
86
- # parsing plugin_name
94
+ message = await ctx . send ( 'Downloading plugin...' )
87
95
async with ctx .typing ():
88
96
if len (plugin_name .split ('/' )) >= 3 :
89
97
parsed_plugin = self .parse_plugin (plugin_name )
@@ -95,6 +103,7 @@ async def add(self, ctx, *, plugin_name):
95
103
f'Unable to fetch plugin from Github: { exc } '
96
104
)
97
105
106
+ importlib .invalidate_caches ()
98
107
try :
99
108
await self .load_plugin (* parsed_plugin )
100
109
except DownloadError as exc :
@@ -106,11 +115,11 @@ async def add(self, ctx, *, plugin_name):
106
115
self .bot .config .plugins .append (plugin_name )
107
116
await self .bot .config .update ()
108
117
109
- await ctx . send ( 'Plugin installed. Any plugin that '
110
- 'you install is of your OWN RISK.' )
118
+ await message . edit ( content = 'Plugin installed. Any plugin that '
119
+ 'you install is of your OWN RISK.' )
111
120
else :
112
- await ctx . send ( 'Invalid plugin name format. '
113
- 'Use username/repo/plugin.' )
121
+ await message . edit ( content = 'Invalid plugin name format. '
122
+ 'Use username/repo/plugin.' )
114
123
115
124
@plugin .command ()
116
125
async def remove (self , ctx , * , plugin_name ):
@@ -127,9 +136,13 @@ async def remove(self, ctx, *, plugin_name):
127
136
if not any (i .startswith (f'{ username } /{ repo } ' )
128
137
for i in self .bot .config .plugins ):
129
138
# if there are no more of such repos, delete the folder
130
- shutil .rmtree (f'plugins/{ username } -{ repo } ' ,
131
- ignore_errors = True )
132
- await ctx .send ('' )
139
+ def onerror (func , path , exc_info ):
140
+ if not os .access (path , os .W_OK ):
141
+ # Is the error an access error ?
142
+ os .chmod (path , stat .S_IWUSR )
143
+ func (path )
144
+
145
+ shutil .rmtree (f'plugins/{ username } -{ repo } ' , onerror = onerror )
133
146
except Exception as exc :
134
147
print (exc )
135
148
self .bot .config .plugins .append (plugin_name )
@@ -143,12 +156,16 @@ async def remove(self, ctx, *, plugin_name):
143
156
144
157
@plugin .command ()
145
158
async def update (self , ctx , * , plugin_name ):
159
+ """Updates a certain plugin"""
146
160
async with ctx .typing ():
147
161
username , repo , name = self .parse_plugin (plugin_name )
148
162
try :
149
163
cmd = f'cd plugins/{ username } -{ repo } && git pull'
150
- cmd = subprocess .run (cmd , shell = True , check = True ,
151
- capture_output = True )
164
+ cmd = await self .bot .loop .run_in_executor (
165
+ None ,
166
+ self ._asubprocess_run ,
167
+ cmd
168
+ )
152
169
except subprocess .CalledProcessError as exc :
153
170
error = exc .stdout .decode ('utf8' ).strip ()
154
171
await ctx .send (f'Error when updating: { error } ' )
@@ -159,15 +176,17 @@ async def update(self, ctx, *, plugin_name):
159
176
# repo was updated locally, now perform the cog reload
160
177
ext = f'plugins.{ username } -{ repo } .{ name } .{ name } '
161
178
importlib .reload (importlib .import_module (ext ))
162
- self .bot .unload_extension (ext )
163
- self .bot .load_extension (ext )
179
+ self .load_plugin (username , repo , name )
164
180
165
181
await ctx .send (f'```\n { output } \n ```' )
166
182
167
183
@plugin .command (name = 'list' )
168
184
async def list_ (self , ctx ):
169
185
"""Shows a list of currently enabled plugins"""
170
- await ctx .send ('```\n ' + '\n ' .join (self .bot .config .plugins ) + '\n ```' )
186
+ if self .bot .config .plugins :
187
+ await ctx .send ('```\n ' + '\n ' .join (self .bot .config .plugins ) + '\n ```' )
188
+ else :
189
+ await ctx .send ('No plugins installed' )
171
190
172
191
173
192
def setup (bot ):
0 commit comments