mirror of
https://github.com/wlinator/luminara.git
synced 2024-10-02 18:03:12 +00:00
Merge branch 'main' into moderation
This commit is contained in:
commit
39559d54f1
18 changed files with 245 additions and 117 deletions
60
.github/workflows/docker-image.yml
vendored
Normal file
60
.github/workflows/docker-image.yml
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
name: Create and Publish Docker Image CI
|
||||
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
tags: [ "v*.*.*" ]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
|
||||
docker:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
wlinator/luminara
|
||||
ghcr.io/wlinator/luminara
|
||||
tags: |
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
-
|
||||
name: Login to GHCR
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,6 +3,7 @@ venv/
|
|||
__pycache__/
|
||||
.run/
|
||||
.vscode/
|
||||
data/
|
||||
|
||||
*.db
|
||||
.env
|
||||
|
|
|
@ -7,7 +7,7 @@ from discord.ext import bridge, commands
|
|||
from discord.ext.commands import EmojiConverter, TextChannelConverter
|
||||
from loguru import logger
|
||||
|
||||
from lib import metadata
|
||||
from lib.constants import CONST
|
||||
|
||||
|
||||
class LumiBot(bridge.Bot):
|
||||
|
@ -18,7 +18,7 @@ class LumiBot(bridge.Bot):
|
|||
Logs various information about the bot and the environment it is running on.
|
||||
Note: This function isn't guaranteed to only be called once. The event is called when a RESUME request fails.
|
||||
"""
|
||||
logger.info(f"{metadata.__title__} v{metadata.__version__}")
|
||||
logger.info(f"{CONST.TITLE} v{CONST.VERSION}")
|
||||
logger.info(f"Logged in with ID {self.user.id if self.user else 'Unknown'}")
|
||||
logger.info(f"discord.py API version: {discord.__version__}")
|
||||
logger.info(f"Python version: {platform.python_version()}")
|
||||
|
|
17
Dockerfile
17
Dockerfile
|
@ -1,21 +1,24 @@
|
|||
FROM python:3.12
|
||||
FROM python:3.12-slim-bookworm
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y locales mariadb-client && \
|
||||
apt-get install -y --no-install-recommends locales mariadb-client && \
|
||||
sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
|
||||
dpkg-reconfigure locales
|
||||
dpkg-reconfigure locales && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY pyproject.toml poetry.lock ./
|
||||
RUN pip install poetry && \
|
||||
RUN pip install --no-cache-dir poetry && \
|
||||
poetry config virtualenvs.create false && \
|
||||
poetry install --no-interaction --no-ansi
|
||||
poetry install --no-interaction --no-ansi --no-dev && \
|
||||
pip cache purge
|
||||
|
||||
COPY . .
|
||||
|
||||
ENV LANG en_US.UTF-8
|
||||
ENV LC_ALL en_US.UTF-8
|
||||
ENV LANG=en_US.UTF-8
|
||||
ENV LC_ALL=en_US.UTF-8
|
||||
|
||||
CMD [ "poetry", "run", "python", "./Luminara.py" ]
|
31
LICENSE
31
LICENSE
|
@ -1,24 +1,7 @@
|
|||
Luminara
|
||||
Copyright (C) 2022-2024 wlinator
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
@ -648,8 +631,8 @@ to attach them to the start of each source file to most effectively
|
|||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
Luminara - A Discord bot application
|
||||
Copyright (C) 2022-present wlinator (dokimakimaki@gmail.com)
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -662,14 +645,14 @@ the "copyright" line and a pointer to where the full notice is found.
|
|||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
Luminara Copyright (C) 2022-2024 wlinator
|
||||
Luminara Copyright (C) 2022-present wlinator (dokimakimaki@gmail.com)
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
@ -681,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box".
|
|||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
|
|
|
@ -10,6 +10,7 @@ import services.config_service
|
|||
import services.help_service
|
||||
from lib.constants import CONST
|
||||
from services.blacklist_service import BlacklistUserService
|
||||
from db.database import run_migrations
|
||||
|
||||
# Remove the default logger configuration
|
||||
logger.remove()
|
||||
|
@ -42,9 +43,7 @@ client = Client.LumiBot(
|
|||
|
||||
@client.check
|
||||
async def blacklist_check(ctx):
|
||||
if BlacklistUserService.is_user_blacklisted(ctx.author.id):
|
||||
return False
|
||||
return True
|
||||
return not BlacklistUserService.is_user_blacklisted(ctx.author.id)
|
||||
|
||||
|
||||
def load_modules():
|
||||
|
@ -87,6 +86,9 @@ if __name__ == "__main__":
|
|||
|
||||
logger.info("LUMI IS BOOTING")
|
||||
|
||||
# Run database migrations
|
||||
run_migrations()
|
||||
|
||||
# cache all JSON
|
||||
[
|
||||
config.parser.JsonCache.read_json(file[:-5])
|
||||
|
|
37
README.md
37
README.md
|
@ -2,6 +2,43 @@
|
|||
|
||||
![Lumi art](https://git.wlinator.org/assets/img/logo.png)
|
||||
|
||||
|
||||
## Self-Hosting
|
||||
|
||||
Self-hosting refers to running Luminara on your own server or computer, rather than using the publicly hosted version. This approach offers the ability to manage your own instance of the bot and give it a custom name and avatar.
|
||||
|
||||
### Requirements
|
||||
|
||||
Before you begin, make sure you have the following installed on your system:
|
||||
- [Docker](https://docs.docker.com/get-docker/)
|
||||
- [Docker Compose](https://docs.docker.com/compose/install/)
|
||||
|
||||
Additionally, you'll need to create a Discord bot application and obtain a token:
|
||||
|
||||
1. Go to the [Discord Developer Portal](https://discord.com/developers/applications).
|
||||
2. Click on "New Application" and give it a name.
|
||||
3. Navigate to the "Bot" tab and click "Add Bot".
|
||||
4. Under the bot's username, click "Reset Token" to reveal your bot token.
|
||||
5. Copy this token; you'll need it for the `.env` file later.
|
||||
|
||||
*Note: remember to keep your bot token secret and never share it publicly.*
|
||||
|
||||
### Running Luminara:
|
||||
|
||||
1. Copy the contents from [docker-compose.prod.yml](docker-compose.prod.yml) to a new file named `docker-compose.yml` in an empty directory.
|
||||
|
||||
2. Copy the `.env.example` file to a new file named `.env` in the same directory.
|
||||
|
||||
3. Fill out the `.env` file with your specific configuration details.
|
||||
|
||||
4. Run the following command in your terminal:
|
||||
|
||||
```
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
This will build and start Luminara in detached mode.
|
||||
|
||||
---
|
||||
|
||||
Some icons used in Lumi are provided by [Icons8](https://icons8.com/).
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import json
|
||||
|
||||
import yaml
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class JsonCache:
|
||||
_cache = {}
|
||||
|
@ -13,21 +10,5 @@ class JsonCache:
|
|||
if path not in JsonCache._cache:
|
||||
with open(f"config/JSON/{path}.json") as file:
|
||||
JsonCache._cache[path] = json.load(file)
|
||||
logger.debug(f"{path}.json was loaded and cached.")
|
||||
|
||||
return JsonCache._cache[path]
|
||||
|
||||
|
||||
class YamlCache:
|
||||
_cache = {}
|
||||
|
||||
@staticmethod
|
||||
def read_credentialsl():
|
||||
"""Read and cache the creds.yaml data if not already cached."""
|
||||
path = "creds"
|
||||
if path not in YamlCache._cache:
|
||||
with open(f"{path}.yaml") as file:
|
||||
YamlCache._cache[path] = yaml.safe_load(file)
|
||||
logger.debug(f"{path}.yaml was loaded and cached.")
|
||||
|
||||
return YamlCache._cache[path]
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import mysql.connector
|
||||
from loguru import logger
|
||||
from mysql.connector import pooling
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
|
||||
from lib.constants import CONST
|
||||
|
||||
|
@ -54,3 +57,57 @@ def select_query_dict(query, values=None):
|
|||
with conn.cursor(dictionary=True) as cursor:
|
||||
cursor.execute(query, values)
|
||||
return cursor.fetchall()
|
||||
|
||||
def run_migrations():
|
||||
migrations_dir = "db/migrations"
|
||||
migration_files = sorted(
|
||||
[f for f in os.listdir(migrations_dir) if f.endswith(".sql")],
|
||||
)
|
||||
|
||||
with _cnxpool.get_connection() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
# Create migrations table if it doesn't exist
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS migrations (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
filename VARCHAR(255) NOT NULL,
|
||||
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""")
|
||||
|
||||
for migration_file in migration_files:
|
||||
# Check if migration has already been applied
|
||||
cursor.execute(
|
||||
"SELECT COUNT(*) FROM migrations WHERE filename = %s",
|
||||
(migration_file,),
|
||||
)
|
||||
if cursor.fetchone()[0] > 0:
|
||||
logger.debug(
|
||||
f"Migration {migration_file} already applied, skipping.",
|
||||
)
|
||||
continue
|
||||
|
||||
# Read and execute migration file
|
||||
migration_sql = pathlib.Path(
|
||||
os.path.join(migrations_dir, migration_file),
|
||||
).read_text()
|
||||
try:
|
||||
# Split the migration file into individual statements
|
||||
statements = re.split(r";\s*$", migration_sql, flags=re.MULTILINE)
|
||||
for statement in statements:
|
||||
if statement.strip():
|
||||
cursor.execute(statement)
|
||||
|
||||
# Record successful migration
|
||||
cursor.execute(
|
||||
"INSERT INTO migrations (filename) VALUES (%s)",
|
||||
(migration_file,),
|
||||
)
|
||||
conn.commit()
|
||||
logger.debug(f"Successfully applied migration: {migration_file}")
|
||||
except mysql.connector.Error as e:
|
||||
conn.rollback()
|
||||
logger.error(f"Error applying migration {migration_file}: {e}")
|
||||
raise
|
||||
|
||||
logger.debug("All migrations completed.")
|
||||
|
|
|
@ -3,6 +3,9 @@ services:
|
|||
build: .
|
||||
container_name: lumi-core
|
||||
restart: always
|
||||
env_file:
|
||||
- path: ./.env
|
||||
required: true
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
@ -17,8 +20,7 @@ services:
|
|||
MARIADB_PASSWORD: ${MARIADB_PASSWORD}
|
||||
MARIADB_DATABASE: ${MARIADB_DATABASE}
|
||||
volumes:
|
||||
- ./db/migrations:/docker-entrypoint-initdb.d/
|
||||
- database:/var/lib/mysql/
|
||||
- ./data:/var/lib/mysql/
|
||||
healthcheck:
|
||||
test: [ "CMD", "mariadb", "-h", "localhost", "-u", "${MARIADB_USER}", "-p${MARIADB_PASSWORD}", "-e", "SELECT 1" ]
|
||||
interval: 5s
|
||||
|
@ -30,7 +32,4 @@ services:
|
|||
container_name: lumi-adminer
|
||||
restart: always
|
||||
ports:
|
||||
- 8080:8080
|
||||
|
||||
volumes:
|
||||
database:
|
||||
- 8080:8080
|
28
docker-compose.prod.yml
Normal file
28
docker-compose.prod.yml
Normal file
|
@ -0,0 +1,28 @@
|
|||
services:
|
||||
core:
|
||||
image: ghcr.io/wlinator/luminara:2 # Remove "ghcr.io/" if you want to use the Docker Hub image.
|
||||
container_name: lumi-core
|
||||
restart: always
|
||||
env_file:
|
||||
- path: ./.env
|
||||
required: true
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
db:
|
||||
image: mariadb
|
||||
container_name: lumi-db
|
||||
restart: always
|
||||
environment:
|
||||
MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
|
||||
MARIADB_USER: ${MARIADB_USER}
|
||||
MARIADB_PASSWORD: ${MARIADB_PASSWORD}
|
||||
MARIADB_DATABASE: ${MARIADB_DATABASE}
|
||||
volumes:
|
||||
- ./data:/var/lib/mysql/
|
||||
healthcheck:
|
||||
test: [ "CMD", "mariadb", "-h", "localhost", "-u", "${MARIADB_USER}", "-p${MARIADB_PASSWORD}", "-e", "SELECT 1" ]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 5
|
|
@ -1,18 +1,19 @@
|
|||
import os
|
||||
from typing import Optional, Set
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from config.parser import JsonCache
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
art = JsonCache.read_json("art")
|
||||
resources = JsonCache.read_json("resources")
|
||||
|
||||
|
||||
class Constants:
|
||||
# metadata
|
||||
TITLE = "Luminara"
|
||||
AUTHOR = "wlinator"
|
||||
LICENSE = "GNU General Public License v3.0"
|
||||
VERSION = "2.7.0"
|
||||
|
||||
# bot credentials
|
||||
TOKEN: Optional[str] = os.environ.get("TOKEN", None)
|
||||
INSTANCE: Optional[str] = os.environ.get("INSTANCE", None)
|
||||
|
@ -39,9 +40,9 @@ class Constants:
|
|||
EMOTES_GUILD_ID = 1038051105642401812
|
||||
|
||||
# color scheme
|
||||
COLOR_DEFAULT = int(0xFF8C00)
|
||||
COLOR_WARNING = int(0xFF7600)
|
||||
COLOR_ERROR = int(0xFF4500)
|
||||
COLOR_DEFAULT = 0xFF8C00
|
||||
COLOR_WARNING = 0xFF7600
|
||||
COLOR_ERROR = 0xFF4500
|
||||
|
||||
# strings
|
||||
STRINGS = JsonCache.read_json("strings")
|
||||
|
@ -84,3 +85,6 @@ class Constants:
|
|||
|
||||
|
||||
CONST = Constants()
|
||||
|
||||
|
||||
CONST = Constants()
|
||||
|
|
|
@ -36,14 +36,13 @@ class BlackJackButtons(View):
|
|||
self.stop()
|
||||
|
||||
async def interaction_check(self, interaction) -> bool:
|
||||
if interaction.user != self.ctx.author:
|
||||
await interaction.response.send_message(
|
||||
"You can't use these buttons, they're someone else's!",
|
||||
ephemeral=True,
|
||||
)
|
||||
return False
|
||||
else:
|
||||
if interaction.user == self.ctx.author:
|
||||
return True
|
||||
await interaction.response.send_message(
|
||||
"You can't use these buttons, they're someone else's!",
|
||||
ephemeral=True,
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
class ExchangeConfirmation(View):
|
||||
|
@ -69,11 +68,10 @@ class ExchangeConfirmation(View):
|
|||
self.stop()
|
||||
|
||||
async def interaction_check(self, interaction) -> bool:
|
||||
if interaction.user != self.ctx.author:
|
||||
await interaction.response.send_message(
|
||||
"You can't use these buttons, they're someone else's!",
|
||||
ephemeral=True,
|
||||
)
|
||||
return False
|
||||
else:
|
||||
if interaction.user == self.ctx.author:
|
||||
return True
|
||||
await interaction.response.send_message(
|
||||
"You can't use these buttons, they're someone else's!",
|
||||
ephemeral=True,
|
||||
)
|
||||
return False
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
import subprocess
|
||||
|
||||
|
||||
def get_latest_git_tag():
|
||||
"""
|
||||
Retrieves the latest git tag.
|
||||
"""
|
||||
try:
|
||||
command = ["git", "describe", "--abbrev=0", "--tags"]
|
||||
return subprocess.check_output(command).decode().strip()
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error: {e}")
|
||||
return "BETA"
|
||||
|
||||
|
||||
__title__ = "Luminara"
|
||||
__version__ = get_latest_git_tag()
|
||||
__author__ = "wlinator"
|
||||
__license__ = "MIT License"
|
|
@ -16,7 +16,4 @@ def seconds_until(hours, minutes):
|
|||
if future_exec < now:
|
||||
future_exec += datetime.timedelta(days=1)
|
||||
|
||||
# Calculate the time difference in seconds
|
||||
seconds_until_execution = (future_exec - now).total_seconds()
|
||||
|
||||
return seconds_until_execution
|
||||
return (future_exec - now).total_seconds()
|
||||
|
|
|
@ -5,7 +5,6 @@ import discord
|
|||
import psutil
|
||||
from discord.ext import bridge
|
||||
|
||||
from lib import metadata
|
||||
from lib.constants import CONST
|
||||
from lib.embed_builder import EmbedBuilder
|
||||
from services.currency_service import Currency
|
||||
|
@ -34,7 +33,7 @@ async def cmd(self, ctx: bridge.Context, unix_timestamp: int) -> None:
|
|||
show_name=False,
|
||||
)
|
||||
embed.set_author(
|
||||
name=f"{metadata.__title__} v{metadata.__version__}",
|
||||
name=f"{CONST.TITLE} v{CONST.VERSION}",
|
||||
url=CONST.REPO_URL,
|
||||
icon_url=CONST.CHECK_ICON,
|
||||
)
|
||||
|
|
6
renovate.json
Normal file
6
renovate.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended"
|
||||
]
|
||||
}
|
|
@ -57,15 +57,7 @@ class Currency:
|
|||
query = "SELECT user_id, balance FROM currency ORDER BY balance DESC"
|
||||
data = database.select_query(query)
|
||||
|
||||
leaderboard = []
|
||||
rank = 1
|
||||
for row in data:
|
||||
row_user_id = row[0]
|
||||
balance = row[1]
|
||||
leaderboard.append((row_user_id, balance, rank))
|
||||
rank += 1
|
||||
|
||||
return leaderboard
|
||||
return [(row[0], row[1], rank) for rank, row in enumerate(data, start=1)]
|
||||
|
||||
@staticmethod
|
||||
def format(num):
|
||||
|
|
Loading…
Reference in a new issue