Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 39 additions & 0 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python

name: Python application

on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]

permissions:
contents: read

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Set up Python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
pytest
66 changes: 15 additions & 51 deletions cogs/utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,56 +334,29 @@ async def about(self, ctx):
"""Shows information about this bot."""
embed = discord.Embed(color=self.bot.main_color, timestamp=discord.utils.utcnow())
embed.set_author(
name="Modmail - About",
name="Pebble — About",
icon_url=self.bot.user.display_avatar.url if self.bot.user.display_avatar else None,
url="https://discord.gg/F34cRU8",
)
embed.set_thumbnail(url=self.bot.user.display_avatar.url if self.bot.user.display_avatar else None)

desc = "This is an open source Discord bot that serves as a means for "
desc += "members to easily communicate with server administrators in "
desc += "an organised manner."
embed.description = desc
embed.description = (
"Pebble is Plover's internal support assistant — an organised shared "
"inbox that lets contributors handle customer support inquiries."
)

embed.add_field(name="Uptime", value=self.bot.uptime)
embed.add_field(name="Latency", value=f"{self.bot.latency * 1000:.2f} ms")
embed.add_field(name="Version", value=f"`{self.bot.version}`")
embed.add_field(name="Authors", value="`kyb3r`, `Taki`, `fourjr`")
embed.add_field(name="Hosting Method", value=self.bot.hosting_method.name)

changelog = await Changelog.from_url(self.bot)
latest = changelog.latest_version

if self.bot.version.is_prerelease:
stable = next(filter(lambda v: not Version(v.version).is_prerelease, changelog.versions))
footer = f"You are on the prerelease version • the latest version is v{stable.version}."
elif self.bot.version < Version(latest.version):
footer = f"A newer version is available v{latest.version}."
else:
footer = "You are up to date with the latest version."

embed.add_field(
name="Want Modmail in Your Server?",
value="Follow the installation guide on [GitHub](https://github.com/modmail-dev/modmail/) "
"and join our [Discord server](https://discord.gg/cnUpwrnpYb)!",
inline=False,
)

embed.add_field(
name="Support the Developers",
value="This bot is completely free for everyone. We rely on kind individuals "
"like you to support us on [`Buy Me A Coffee`](https://buymeacoffee.com/modmaildev) (perks included for memberships) "
"to keep this bot free forever!",
inline=False,
)

embed.add_field(
name="Project Sponsors",
value=f"Checkout the people who supported Modmail with command `{self.bot.prefix}sponsors`!",
name="Built On",
value="Pebble is built on the open-source [Modmail](https://github.com/modmail-dev/modmail) "
"project by `kyb3r`, `Taki`, and `fourjr`, licensed under AGPL-3.0.",
inline=False,
)

embed.set_footer(text=footer)
embed.set_footer(text="Pebble • Plover")
await ctx.send(embed=embed)

@commands.command(aliases=["sponsor"])
Expand All @@ -392,21 +365,12 @@ async def about(self, ctx):
async def sponsors(self, ctx):
"""Shows the sponsors of this project."""

async with self.bot.session.get(
"https://raw.githubusercontent.com/modmail-dev/modmail/master/SPONSORS.json"
) as resp:
data = loads(await resp.text())

embeds = []

for elem in data:
embed = discord.Embed.from_dict(elem["embed"])
embeds.append(embed)

random.shuffle(embeds)

session = EmbedPaginatorSession(ctx, *embeds)
await session.run()
embed = discord.Embed(
color=self.bot.main_color,
title="Sponsors",
description="This is a private Pebble instance operated by Plover.",
)
await ctx.send(embed=embed)

@commands.group(invoke_without_command=True)
@checks.has_permissions(PermissionLevel.OWNER)
Expand Down
8 changes: 4 additions & 4 deletions core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class ConfigManager:
"mention_channel_id": None,
"update_channel_id": None,
# updates
"update_notifications": True,
"update_notifications": False,
# threads
"sent_emoji": "\N{WHITE HEAVY CHECK MARK}",
"blocked_emoji": "\N{NO ENTRY SIGN}",
Expand Down Expand Up @@ -213,15 +213,15 @@ class ConfigManager:
"enable_eval": True,
# github access token for private repositories
"github_token": None,
"disable_autoupdates": False,
"disable_updates": False,
"disable_autoupdates": True,
"disable_updates": True,
# Logging
"log_level": "INFO",
"stream_log_format": "plain",
"file_log_format": "plain",
"discord_log_level": "INFO",
# data collection
"data_collection": True,
"data_collection": False,
}

colors = {
Expand Down
42 changes: 42 additions & 0 deletions deploy/oracle/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copy this file to `.env` in the same directory and fill in your values.
# cp .env.example .env && nano .env

# Your Discord bot token.
TOKEN=MyBotToken

# The ID of the Discord server (guild) this bot serves.
GUILD_ID=1234567890

# Comma-separated user IDs allowed to run owner-only commands.
OWNERS=Owner1ID,Owner2ID

# Your EXISTING MongoDB connection URI (kept from your previous host).
# The logviewer reuses this same URI via MONGO_URI in docker-compose.yml.
CONNECTION_URI=mongodb+srv://user:pass@cluster.mongodb.net/modmail

# Public URL where the logviewer is reachable, used in generated log links.
# With Cloudflare Tunnel this is your domain over HTTPS, e.g.:
# https://logs.yourdomain.com
LOG_URL=https://logs.yourdomain.com

# Cloudflare Tunnel token. Create a tunnel in the Cloudflare Zero Trust
# dashboard (Networks -> Connectors), route your hostname to http://caddy:8080,
# then copy the connector token here. See deploy/oracle/README.md.
TUNNEL_TOKEN=your-cloudflare-tunnel-token

# --- Discord OAuth2 login for the logviewer (role-gated access) ---------------
# Only members of GUILD_ID holding REQUIRED_ROLE_ID can view the logs.

# Your Discord OAuth2 application's credentials (Developer Portal -> your app).
DISCORD_CLIENT_ID=729540679365296178
DISCORD_CLIENT_SECRET=your-discord-client-secret

# Must EXACTLY match a redirect added under OAuth2 -> Redirects in the portal.
DISCORD_REDIRECT_URI=https://pebble.getplover.com/auth/callback

# The role a user must have (in GUILD_ID, set above) to view logs.
REQUIRED_ROLE_ID=766320574733221939

# Random secret used to sign login session cookies. Generate one with:
# openssl rand -hex 32
SESSION_SECRET=change-me-to-a-long-random-string
47 changes: 47 additions & 0 deletions deploy/oracle/COMMERCIAL_LICENSE_OUTREACH.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Commercial license outreach — draft

The Modmail bot and the logviewer are licensed under **AGPL-3.0** / **GPL-3.0**.
To use a modified version as closed/proprietary software (e.g. offered to
external customers without publishing source), Plover needs a **commercial
license** from the copyright holders, or must build a clean-room replacement.

This file is a starting point for requesting a commercial/dual license. Send it
to the maintainers (their Discord / Buy Me a Coffee / GitHub).

---

**Subject:** Commercial license inquiry for Modmail + Logviewer

Hi,

I'm reaching out from **Plover** (getplover.com). We operate a self-hosted,
customised deployment of Modmail and the Logviewer to handle customer support
for our product, and we'd like to do this properly with respect to the AGPL-3.0
/ GPL-3.0 licensing.

We're interested in a **commercial / dual license** that would let us run a
modified version internally (and potentially as part of a customer-facing
support workflow) **without the AGPL's network source-disclosure obligation**.

Could you let us know:

1. Whether a commercial license for Modmail (and the Logviewer) is available.
2. Pricing / terms (one-time, annual, per-instance, etc.).
3. What it covers — modifications, the §13 network clause, rebranding, and
whether the Logviewer's premium Discord-OAuth feature can be included.
4. Who holds copyright and can sign such an agreement.

Happy to jump on a call. We want to support the project and stay compliant.

Thanks,
<your name> — Plover

---

## Notes / fallbacks if a commercial license isn't available

- **Internal-use + published fork (AGPL-compliant):** keep the deployment
AGPL, publish our fork's source, and offer it to network users. Lowest cost,
but our modifications stay public.
- **Clean-room rebuild:** build a Plover-owned support bot from scratch
(our own code/copyright). Most effort; full ownership and no AGPL.
25 changes: 25 additions & 0 deletions deploy/oracle/Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Edge proxy for the logviewer, sitting between Cloudflare Tunnel and the app.
# TLS is terminated by Cloudflare, so Caddy just serves plain HTTP on :8080.
#
# Point your Cloudflare Tunnel public hostname at http://caddy:8080 (NOT the
# logviewer directly) so every request passes through the Discord auth check.

{
admin off
auto_https off
}

:8080 {
# Auth endpoints are handled directly by the Discord OAuth proxy.
handle /auth/* {
reverse_proxy authproxy:5000
}

# Everything else must pass the role check before reaching the logviewer.
handle {
forward_auth authproxy:5000 {
uri /auth/verify
}
reverse_proxy logviewer:8000
}
}
Loading
Loading