Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
c26af97
Feat: Renaming of snippets and aliases (#3383)
martinbndr Nov 30, 2025
a7ff289
Fixes config_help notes (#3407)
martinbndr Dec 5, 2025
6e75c44
Git Repository check for bot update (#3406)
martinbndr Dec 5, 2025
cfedecb
Fix Plugin Help (#3322)
martinbndr Dec 7, 2025
d2b5042
Added submenu functionality to threadmenu. Fixes #3403 (#3404)
sebkuip Dec 8, 2025
6e094af
Fix: Blackformatting, workflows. Add snooze/unsnooze events. (#3412)
lorenzo132 Dec 8, 2025
3c3608f
Add threadmenu toggle notice (#3411)
martinbndr Dec 13, 2025
ab458f5
Improvements for alias creation/editing (#3422)
martinbndr Dec 20, 2025
ad801b1
Fixes thread_auto_close execution when disabled. (#3423)
martinbndr Dec 20, 2025
040195d
fix: plain replies deletion and edits (#3416)
lorenzo132 Jan 16, 2026
0df325d
Update gitignore
Taaku18 Dec 10, 2025
da6dff2
feat: bulk override perms for commands
lorenzo132 Dec 13, 2025
3ea1b5e
fix linting
lorenzo132 Dec 13, 2025
2f0c585
change reflection upon review
lorenzo132 Dec 14, 2025
df15ad8
linting
lorenzo132 Dec 14, 2025
9be3c4b
resolve review.
lorenzo132 Dec 18, 2025
ef8a5c2
fix: strip mention prefixes - show valid commands before apply.
lorenzo132 Dec 18, 2025
7758dd2
Implement requested change.
lorenzo132 Dec 20, 2025
72d2b98
improve invalid commmand proccesing.
lorenzo132 Feb 2, 2026
dec20f8
logging: bulk override
lorenzo132 Feb 4, 2026
cad5122
fix: show forwarded message in logviewer.
lorenzo132 Dec 5, 2025
74a2969
fix: show forwarded message in logviewer.
lorenzo132 Dec 5, 2025
47e30c5
fix: show forwarded message in logviewer.
lorenzo132 Dec 5, 2025
db9b81a
black formatting
lorenzo132 Dec 5, 2025
8b4dfcb
remove jump_url
lorenzo132 Dec 18, 2025
d9c7d15
feat: adds attachment support to forwarded content.
lorenzo132 Dec 18, 2025
58bb1ee
black formatting
lorenzo132 Dec 18, 2025
64e33f4
use: hassattr(), remove dedunant package
lorenzo132 Dec 27, 2025
d8e91c0
rebase import.
lorenzo132 Feb 2, 2026
c31c024
fix: linting
lorenzo132 Feb 2, 2026
70c8b56
Fix threadmenu command execution
martinbndr Dec 30, 2025
5ec2f11
Replace claim plugin (#3437)
martinbndr Mar 9, 2026
01d6c6c
Feat: Renaming of snippets and aliases (#3383)
martinbndr Nov 30, 2025
bd9d80a
Improvements for alias creation/editing (#3422)
martinbndr Dec 20, 2025
88e7171
fix: breaking threadmenu fixes.
lorenzo132 Dec 21, 2025
2c7019f
fix: thread_auto_close bug.
lorenzo132 Dec 24, 2025
4ab9055
Resolve git requests.
lorenzo132 Jun 11, 2026
d3163dd
Plugin registry checks upon boot
martinbndr Jan 2, 2026
1da6d46
Improve If-Statement readability
martinbndr Mar 7, 2026
5d4175e
fix: handle Forbidden when sending confirm-thread-creation DM (#3442)
13-Prabhat Jun 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 65 additions & 110 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,93 +1,29 @@
#### Modmail
# Plugins
plugins/*
!plugins/registry.json
!plugins/@local

# Config files
config.json
.env

# Data
temp/

### Python
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
Expand All @@ -98,43 +34,62 @@ ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject
#### PyCharm
.idea/

# mkdocs documentation
/site
#### VS Code
.vscode/

# mypy
.mypy_cache/
.dmypy.json
dmypy.json
### Windows
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db

# Pyre type checker
.pyre/
# Dump file
*.stackdump

# PyCharm
.idea/
# Folder config file
[Dd]esktop.ini

# MacOS
.DS_Store
# Recycle Bin used on file shares
$RECYCLE.BIN/

# VS Code
.vscode/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp

# Node
package-lock.json
node_modules/
# Windows shortcuts
*.lnk

# Modmail
plugins/*
!plugins/registry.json
!plugins/@local
config.json
temp/
test.py
stack.yml
.github/workflows/build_docker.yml
### macOS
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon

# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
71 changes: 57 additions & 14 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,73 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
This project mostly adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html);
however, insignificant breaking changes do not guarantee a major version bump, see the reasoning [here](https://github.com/modmail-dev/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section.

# Unreleased

### Fixed
* Confirm thread creation (react to contact) no longer leaves a thread stuck in a "not ready" cache state when the recipient has DMs disabled. The bot now catches `discord.Forbidden` when sending the confirmation prompt, cancels the thread, and clears the cache entry immediately instead of requiring a bot restart. (#3442)

# v4.2.1

### Added

**New Configuration Options:**
* `unsnooze_history_limit`: Limits the number of messages replayed when unsnoozing (genesis message and notes are always shown).
* `snooze_behavior`: Choose between `delete` (legacy) or `move` behavior for snoozing.
* `snoozed_category_id`: Target category for `move` snoozing; required when `snooze_behavior` is `move`.
* Thread-creation menu: Adds an interactive select step before a thread channel is created.
* Commands:
* `threadmenu toggle`: Enable/disable the menu.
* `threadmenu show`: List current top-level options.
* `threadmenu option add`: Interactive wizard to create an option.
* `threadmenu option edit/remove/show`: Manage or inspect an existing option.
* `threadmenu submenu create/delete/list/show`: Manage submenus.
* `threadmenu submenu option add/edit/remove`: Manage options inside a submenu.
* Configuration / Behavior:
* Per-option `category` targeting when creating a thread; falls back to `main_category_id` if invalid/missing.
* Optional selection logging (`thread_creation_menu_selection_log`) posts the chosen option in the new thread.
* Anonymous prompt support (`thread_creation_menu_anonymous_menu`).
* `snooze_store_attachments`: When enabled, image attachments are stored as base64 when snoozing with delete behavior, allowing them to be re-uploaded on unsnooze.
* `snooze_attachment_max_bytes`: Maximum size per attachment to store as base64 (default: 4 MiB).
* `thread_creation_menu_timeout`: Timeout duration for user interaction with the menu (default: 30 seconds).
* `thread_creation_menu_close_on_timeout`: Silently abort thread creation if user doesn't select an option.
* `thread_creation_menu_anonymous_menu`: Anonymize the initial menu prompt relayed to staff.
* `thread_creation_menu_embed_text`: Text shown in the embed above the selection dropdown.
* `thread_creation_menu_dropdown_placeholder`: Placeholder text in the dropdown before selection.
* `thread_creation_menu_selection_log`: Log the chosen menu option in the newly created thread channel.
* `thread_creation_menu_precreate_channel`: Create thread channel immediately upon first DM even if menu is enabled.
* `thread_creation_menu_embed_title`: Optional title for the thread-creation menu embed.
* `thread_creation_menu_embed_footer`: Optional footer text for the menu embed.
* `thread_creation_menu_embed_footer_icon_url`: Optional URL for the footer icon.
* `thread_creation_menu_embed_thumbnail_url`: Optional thumbnail image URL.
* `thread_creation_menu_embed_image_url`: Optional large hero image URL for the menu embed.
* `thread_creation_menu_embed_large_image`: Promote thumbnail to large hero image if no separate image URL is set.
* `thread_creation_menu_embed_color`: Color for the menu embed's side strip.

**Thread-Creation Menu Feature:**
* Full thread-creation menu system with interactive select menus:
* `?threadmenu toggle`: Enable/disable the menu globally.
* `?threadmenu show`: List current top-level options.
* `?threadmenu option add`: Interactive wizard to create an option.
* `?threadmenu option edit/remove/show`: Manage or inspect existing options.
* `?threadmenu submenu create/delete/list/show`: Manage submenus (nested menu levels).
* `?threadmenu submenu option add/edit/remove`: Manage options inside submenus.
* `?threadmenu dump_config`: Export current configuration to a file.
* `?threadmenu load_config`: Import configuration from a file.
* `?threadmenu reset`: Reset all thread-creation menu settings to defaults.
* Per-option category targeting: Each menu option can specify a target category where threads are created.
* Submenu support: Create up to 25 main-level options, each with up to 24 nested options.
* Optional selection logging: Log which menu option was chosen in the newly created thread channel.
* Anonymous menu support: Hide original prompt author context from staff when menu is anonymized.
* Category fallback: If an option's category is invalid/missing, creation falls back to `main_category_id`.

**Snooze Enhancements:**
* Attachment persistence for delete-behavior snoozing: Image attachments can now be stored as base64 data.
* Enhanced unsnooze functionality with configurable message replay limits.
* Auto-unsnooze task continuously monitors and automatically unsnoozes threads when duration expires.

### Changed
- Renamed `max_snooze_time` to `snooze_default_duration`. The old config will be invalidated.
- Renamed `max_snooze_time` to `snooze_default_duration` (accepts seconds or human-readable time like "7 days").
- When `snooze_behavior` is set to `move`, the snoozed category now has a hard limit of 49 channels. New snoozes are blocked once it’s full until space is freed.
- When switching `snooze_behavior` to `move` via `?config set`, the bot reminds admins to set `snoozed_category_id` if it’s missing.
- Thread-creation menu options & submenu options now support an optional per-option `category` target. The interactive wizards (`threadmenu option add` / `threadmenu submenu option add`) and edit commands allow specifying or updating a category. If the stored category is missing or invalid at selection time, channel creation automatically falls back to `main_category_id`.
- Thread-creation menu options and submenu options now support per-option `category` targeting.
- Category selection in menu option wizards allows specifying ID, name, or mention format.
- Snoozed thread restoration now respects `unsnooze_history_limit` (if set) to replay only the last N messages.
- Enhanced auto-unsnooze task monitors and automatically unsnoozes threads when their snooze duration expires.
- Snoozed threads can now be moved to a dedicated category instead of being deleted (via `snooze_behavior: move`).

### Fixed

- Corrected behavior when snooze channel count reaches the 49-channel limit in move-based snoozing.
- Improved category resolution in threadmenu wizards (handles ID, name, and mention formats reliably).
- Enhanced thread state restoration after unsnoozing to properly re-add all recipients.


# v4.2.0
Expand Down
17 changes: 9 additions & 8 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,29 @@ verify_ssl = true
bandit = ">=1.7.5"
black = "==23.11.0"
pylint = "==3.0.2"
tomli = "==2.2.1" # Needed for black on Python < 3.11
tomli = "==2.2.1"

[packages]
aiohttp = "==3.13.2"
async-timeout = {version = "==5.0.1", markers = "python_version < '3.11'"} # Required by aiohttp
typing-extensions = ">=4.12.2" # Required by aiohttp
async-timeout = {version = "==5.0.1", markers = "python_version < '3.11'"}
typing-extensions = "==4.15.0"
colorama = "==0.4.6"
"discord.py" = {version = "==2.6.3", extras = ["speed"]}
emoji = "==2.8.0"
isodate = "==0.6.1"
motor = "==3.7.1"
natural = "==0.2.0" # Why is this needed?
natural = "==0.2.0"
packaging = "==23.2"
parsedatetime = "==2.6"
dnspython = ">=2.8,<3" # Required by pymongo
pymongo = ">=4.9,<5" # Required by motor
dnspython = "==2.8.0"
pymongo = "==4.15.3"
python-dateutil = "==2.8.2"
python-dotenv = "==1.0.0"
uvloop = {version = ">=0.19.0", markers = "sys_platform != 'win32'"}
uvloop = {version = "==0.22.1", markers = "sys_platform != 'win32'"}
lottie = {version = "==0.7.2", extras = ["pdf"]}
setuptools = "*" # Needed for lottie
setuptools = "==80.9.0"
requests = "==2.31.0"
orjson = "==3.11.4"

[scripts]
bot = "python bot.py"
Loading
Loading