Skip to content

Commit ee2f25a

Browse files
committed
Merge branch 'master' into development
2 parents 480ad57 + 7454a80 commit ee2f25a

File tree

14 files changed

+99
-64
lines changed

14 files changed

+99
-64
lines changed

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ node_modules/
133133
config.json
134134
plugins/
135135
!plugins/registry.json
136+
!plugins/@local/
136137
temp/
137138
test.py
138139

.gitignore

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,10 @@ package-lock.json
130130
node_modules/
131131

132132
# Modmail
133-
config.json
134-
plugins/
133+
plugins/*
135134
!plugins/registry.json
135+
!plugins/@local
136+
config.json
136137
temp/
137138
test.py
138139
stack.yml

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66
This project mostly adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html);
77
however, insignificant breaking changes do not guarantee a major version bump, see the reasoning [here](https://github.com/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section.
88

9+
10+
# v3.8.6
11+
12+
### Added
13+
14+
- Ability to install local plugins without relying on git / external sources
15+
- Simply add your extension to plugins/@local, and use `?plugin add local/plugin-name` to load the plugin as normal
16+
- Updated deps for requirements.min.txt and pyproject.toml
17+
918
# v3.8.5
1019

1120
### Added

bot.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "3.8.5"
1+
__version__ = "3.8.6"
22

33

44
import asyncio
@@ -46,7 +46,7 @@
4646
)
4747
from core.thread import ThreadManager
4848
from core.time import human_timedelta
49-
from core.utils import human_join, normalize_alias, truncate
49+
from core.utils import normalize_alias, truncate
5050

5151
logger = getLogger(__name__)
5252

@@ -1591,7 +1591,7 @@ def main():
15911591
# check discord version
15921592
if discord.__version__ != "1.6.0":
15931593
logger.error(
1594-
"Dependencies are not updated, run pipenv install. discord.py version expected 1.6.0, recieved %s",
1594+
"Dependencies are not updated, run pipenv install. discord.py version expected 1.6.0, received %s",
15951595
discord.__version__,
15961596
)
15971597
sys.exit(0)

cogs/modmail.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import asyncio
2-
from operator import truediv
32
import re
43
from datetime import datetime
54
from itertools import zip_longest

cogs/plugins.py

Lines changed: 71 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,28 @@ class InvalidPluginError(commands.BadArgument):
3131

3232

3333
class Plugin:
34-
def __init__(self, user, repo, name, branch=None):
35-
self.user = user
36-
self.repo = repo
37-
self.name = name
38-
self.branch = branch if branch is not None else "master"
39-
self.url = f"https://github.com/{user}/{repo}/archive/{self.branch}.zip"
40-
self.link = f"https://github.com/{user}/{repo}/tree/{self.branch}/{name}"
34+
def __init__(self, user, repo=None, name=None, branch=None):
35+
if repo is None:
36+
self.user = "@local"
37+
self.repo = "@local"
38+
self.name = user
39+
self.local = True
40+
self.branch = "@local"
41+
self.url = f"@local/{user}"
42+
self.link = f"@local/{user}"
43+
else:
44+
self.user = user
45+
self.repo = repo
46+
self.name = name
47+
self.local = False
48+
self.branch = branch if branch is not None else "master"
49+
self.url = f"https://github.com/{user}/{repo}/archive/{self.branch}.zip"
50+
self.link = f"https://github.com/{user}/{repo}/tree/{self.branch}/{name}"
4151

4252
@property
4353
def path(self):
54+
if self.local:
55+
return PurePath("plugins") / "@local" / self.name
4456
return PurePath("plugins") / self.user / self.repo / f"{self.name}-{self.branch}"
4557

4658
@property
@@ -49,6 +61,8 @@ def abs_path(self):
4961

5062
@property
5163
def cache_path(self):
64+
if self.local:
65+
raise ValueError("No cache path for local plugins!")
5266
return (
5367
Path(__file__).absolute().parent.parent
5468
/ "temp"
@@ -58,20 +72,27 @@ def cache_path(self):
5872

5973
@property
6074
def ext_string(self):
75+
if self.local:
76+
return f"plugins.@local.{self.name}.{self.name}"
6177
return f"plugins.{self.user}.{self.repo}.{self.name}-{self.branch}.{self.name}"
6278

6379
def __str__(self):
80+
if self.local:
81+
return f"@local/{self.name}"
6482
return f"{self.user}/{self.repo}/{self.name}@{self.branch}"
6583

6684
def __lt__(self, other):
6785
return self.name.lower() < other.name.lower()
6886

6987
@classmethod
7088
def from_string(cls, s, strict=False):
71-
if not strict:
72-
m = match(r"^(.+?)/(.+?)/(.+?)(?:@(.+?))?$", s)
73-
else:
74-
m = match(r"^(.+?)/(.+?)/(.+?)@(.+?)$", s)
89+
m = match(r"^@?local/(.+)$", s)
90+
if m is None:
91+
if not strict:
92+
m = match(r"^(.+?)/(.+?)/(.+?)(?:@(.+?))?$", s)
93+
else:
94+
m = match(r"^(.+?)/(.+?)/(.+?)@(.+?)$", s)
95+
7596
if m is not None:
7697
return Plugin(*m.groups())
7798
raise InvalidPluginError("Cannot decipher %s.", s) # pylint: disable=raising-format-tuple
@@ -152,9 +173,12 @@ async def initial_load_plugins(self):
152173
await self.bot.config.update()
153174

154175
async def download_plugin(self, plugin, force=False):
155-
if plugin.abs_path.exists() and not force:
176+
if plugin.abs_path.exists() and (not force or plugin.local):
156177
return
157178

179+
if plugin.local:
180+
raise InvalidPluginError(f"Local plugin {plugin} not found!")
181+
158182
plugin.abs_path.mkdir(parents=True, exist_ok=True)
159183

160184
if plugin.cache_path.exists() and not force:
@@ -178,14 +202,14 @@ async def download_plugin(self, plugin, force=False):
178202
if raw == "Not Found":
179203
raise InvalidPluginError("Plugin not found")
180204
else:
181-
raise InvalidPluginError("Invalid download recieved, non-bytes object")
205+
raise InvalidPluginError("Invalid download received, non-bytes object")
182206

183-
plugin_io = io.BytesIO(raw)
184-
if not plugin.cache_path.parent.exists():
185-
plugin.cache_path.parent.mkdir(parents=True)
207+
plugin_io = io.BytesIO(raw)
208+
if not plugin.cache_path.parent.exists():
209+
plugin.cache_path.parent.mkdir(parents=True)
186210

187-
with plugin.cache_path.open("wb") as f:
188-
f.write(raw)
211+
with plugin.cache_path.open("wb") as f:
212+
f.write(raw)
189213

190214
with zipfile.ZipFile(plugin_io) as zipf:
191215
for info in zipf.infolist():
@@ -253,7 +277,7 @@ async def parse_user_input(self, ctx, plugin_name, check_version=False):
253277
description="Plugins are disabled, enable them by setting `ENABLE_PLUGINS=true`",
254278
color=self.bot.main_color,
255279
)
256-
await ctx.send(embed=em)
280+
await ctx.send(embed=embed)
257281
return
258282

259283
if not self._ready_event.is_set():
@@ -290,7 +314,7 @@ async def parse_user_input(self, ctx, plugin_name, check_version=False):
290314
embed = discord.Embed(
291315
description="Invalid plugin name, double check the plugin name "
292316
"or use one of the following formats: "
293-
"username/repo/plugin, username/repo/plugin@branch.",
317+
"username/repo/plugin-name, username/repo/plugin-name@branch, local/plugin-name.",
294318
color=self.bot.error_color,
295319
)
296320
await ctx.send(embed=embed)
@@ -314,7 +338,8 @@ async def plugins_add(self, ctx, *, plugin_name: str):
314338
Install a new plugin for the bot.
315339
316340
`plugin_name` can be the name of the plugin found in `{prefix}plugin registry`,
317-
or a direct reference to a GitHub hosted plugin (in the format `user/repo/name[@branch]`).
341+
or a direct reference to a GitHub hosted plugin (in the format `user/repo/name[@branch]`)
342+
or `local/name` for local plugins.
318343
"""
319344

320345
plugin = await self.parse_user_input(ctx, plugin_name, check_version=True)
@@ -335,10 +360,16 @@ async def plugins_add(self, ctx, *, plugin_name: str):
335360
)
336361
return await ctx.send(embed=embed)
337362

338-
embed = discord.Embed(
339-
description=f"Starting to download plugin from {plugin.link}...",
340-
color=self.bot.main_color,
341-
)
363+
if plugin.local:
364+
embed = discord.Embed(
365+
description=f"Starting to load local plugin from {plugin.link}...",
366+
color=self.bot.main_color,
367+
)
368+
else:
369+
embed = discord.Embed(
370+
description=f"Starting to download plugin from {plugin.link}...",
371+
color=self.bot.main_color,
372+
)
342373
msg = await ctx.send(embed=embed)
343374

344375
try:
@@ -395,7 +426,7 @@ async def plugins_remove(self, ctx, *, plugin_name: str):
395426
Remove an installed plugin of the bot.
396427
397428
`plugin_name` can be the name of the plugin found in `{prefix}plugin registry`, or a direct reference
398-
to a GitHub hosted plugin (in the format `user/repo/name[@branch]`).
429+
to a GitHub hosted plugin (in the format `user/repo/name[@branch]`) or `local/name` for local plugins.
399430
"""
400431
plugin = await self.parse_user_input(ctx, plugin_name)
401432
if plugin is None:
@@ -416,17 +447,18 @@ async def plugins_remove(self, ctx, *, plugin_name: str):
416447

417448
self.bot.config["plugins"].remove(str(plugin))
418449
await self.bot.config.update()
419-
shutil.rmtree(
420-
plugin.abs_path,
421-
onerror=lambda *args: logger.warning(
422-
"Failed to remove plugin files %s: %s", plugin, str(args[2])
423-
),
424-
)
425-
try:
426-
plugin.abs_path.parent.rmdir()
427-
plugin.abs_path.parent.parent.rmdir()
428-
except OSError:
429-
pass # dir not empty
450+
if not plugin.local:
451+
shutil.rmtree(
452+
plugin.abs_path,
453+
onerror=lambda *args: logger.warning(
454+
"Failed to remove plugin files %s: %s", plugin, str(args[2])
455+
),
456+
)
457+
try:
458+
plugin.abs_path.parent.rmdir()
459+
plugin.abs_path.parent.parent.rmdir()
460+
except OSError:
461+
pass # dir not empty
430462

431463
embed = discord.Embed(
432464
description="The plugin is successfully uninstalled.", color=self.bot.main_color
@@ -477,7 +509,7 @@ async def plugins_update(self, ctx, *, plugin_name: str = None):
477509
Update a plugin for the bot.
478510
479511
`plugin_name` can be the name of the plugin found in `{prefix}plugin registry`, or a direct reference
480-
to a GitHub hosted plugin (in the format `user/repo/name[@branch]`).
512+
to a GitHub hosted plugin (in the format `user/repo/name[@branch]`) or `local/name` for local plugins.
481513
482514
To update all plugins, do `{prefix}plugins update`.
483515
"""
@@ -514,7 +546,7 @@ async def plugins_reset(self, ctx):
514546
shutil.rmtree(cache_path)
515547

516548
for entry in os.scandir(Path(__file__).absolute().parent.parent / "plugins"):
517-
if entry.is_dir():
549+
if entry.is_dir() and entry.name != "@local":
518550
shutil.rmtree(entry.path)
519551
logger.warning("Removing %s.", entry.name)
520552

cogs/utility.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import os
44
import random
55
import re
6-
from sys import stdout
76
import traceback
87
from contextlib import redirect_stdout
98
from datetime import datetime

core/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from core._color_data import ALL_COLORS
1515
from core.models import DMDisabled, InvalidConfigError, Default, getLogger
1616
from core.time import UserFriendlyTimeSync
17-
from core.utils import strtobool, tryint
17+
from core.utils import strtobool
1818

1919
logger = getLogger(__name__)
2020
load_dotenv()

core/models.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,14 +190,13 @@ def get_value(self, key, args, kwds):
190190
except KeyError:
191191
return "{" + key + "}"
192192
else:
193-
return Formatter.get_value(key, args, kwds)
193+
return super().get_value(key, args, kwds)
194194

195195

196196
class SimilarCategoryConverter(commands.CategoryChannelConverter):
197197
async def convert(self, ctx, argument):
198198
bot = ctx.bot
199199
guild = ctx.guild
200-
result = None
201200

202201
try:
203202
return await super().convert(ctx, argument)

core/thread.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
import copy
33
import io
44
import re
5+
import time
56
import typing
67
from datetime import datetime, timedelta
7-
import time
88
from types import SimpleNamespace
99

1010
import isodate

0 commit comments

Comments
 (0)