mirror of
https://github.com/wlinator/luminara.git
synced 2024-10-02 18:03:12 +00:00
docker & mariadb init
This commit is contained in:
parent
0468abcf00
commit
e38dd8abdb
18 changed files with 226 additions and 54 deletions
13
.env.template
Normal file
13
.env.template
Normal file
|
@ -0,0 +1,13 @@
|
|||
TOKEN=
|
||||
INSTANCE=BETA
|
||||
OWNER_ID=784783517845946429
|
||||
XP_GAIN=1,1,1,2
|
||||
COOLDOWN=7,8,9,10
|
||||
CASH_BALANCE_NAME=$
|
||||
SPECIAL_BALANCE_NAME=majikoins
|
||||
DBX_OAUTH2_REFRESH_TOKEN=
|
||||
DBX_APP_KEY=
|
||||
DBX_APP_SECRET=
|
||||
MARIADB_USER=
|
||||
MARIADB_PASSWORD=
|
||||
MARIADB_ROOT_PASSWORD=
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -4,4 +4,7 @@ __pycache__/
|
|||
|
||||
*.db
|
||||
.env
|
||||
*.log
|
||||
*.log
|
||||
|
||||
db/data/
|
||||
db/init/2-data.sql
|
19
Dockerfile
Normal file
19
Dockerfile
Normal file
|
@ -0,0 +1,19 @@
|
|||
FROM python:3.11
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y locales && \
|
||||
sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
|
||||
dpkg-reconfigure locales
|
||||
|
||||
COPY requirements.txt ./
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
ENV LANG en_US.UTF-8
|
||||
ENV LC_ALL en_US.UTF-8
|
||||
|
||||
CMD [ "python", "./main.py" ]
|
4
README.md
Normal file
4
README.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
This branch uses Docker and MariaDB to run Racu. It has the exact same functionality within Discord.
|
||||
There is an ".env.template" file, please copy it to `.env` and fill out the details if you want to host Racu yourself.
|
||||
|
||||
Beware that this branch is much more complicated than Racu when its ran locally with sqlite3, however it is more suitable if you plan to host Racu on a major scale with thousands or millions of Discord servers.
|
|
@ -13,7 +13,7 @@ class Birthday:
|
|||
query = """
|
||||
INSERT OR REPLACE INTO birthdays
|
||||
(user_id, birthday)
|
||||
VALUES (?, ?)
|
||||
VALUES (%s, %s)
|
||||
"""
|
||||
|
||||
database.execute_query(query, (self.user_id, birthday))
|
||||
|
@ -23,7 +23,7 @@ class Birthday:
|
|||
query = """
|
||||
SELECT user_id
|
||||
FROM birthdays
|
||||
WHERE strftime('%m-%d', birthday) = ?
|
||||
WHERE strftime('%m-%d', birthday) = %s
|
||||
"""
|
||||
|
||||
tz = pytz.timezone('US/Eastern')
|
||||
|
|
|
@ -15,7 +15,7 @@ class BlackJackStats:
|
|||
def push(self):
|
||||
query = """
|
||||
INSERT INTO stats_bj (user_id, is_won, bet, payout, hand_player, hand_dealer)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
VALUES (%s, %s, %s, %s, %s, %s)
|
||||
"""
|
||||
|
||||
values = (self.user_id, self.is_won, self.bet, self.payout, self.hand_player, self.hand_dealer)
|
||||
|
@ -32,7 +32,7 @@ class BlackJackStats:
|
|||
SUM(CASE WHEN is_won = 1 THEN 1 ELSE 0 END) AS winning,
|
||||
SUM(CASE WHEN is_won = 0 THEN 1 ELSE 0 END) AS losing
|
||||
FROM stats_bj
|
||||
WHERE user_id = ?;
|
||||
WHERE user_id = %s;
|
||||
"""
|
||||
(amount_of_games, total_bet,
|
||||
total_payout, winning_amount, losing_amount) = database.select_query(query, (user_id,))[0]
|
||||
|
|
|
@ -29,8 +29,8 @@ class Currency:
|
|||
def push(self):
|
||||
query = """
|
||||
UPDATE currency
|
||||
SET cash_balance = ?, special_balance = ?
|
||||
WHERE user_id = ?
|
||||
SET cash_balance = %s, special_balance = %s
|
||||
WHERE user_id = %s
|
||||
"""
|
||||
|
||||
database.execute_query(query, (round(self.cash), round(self.special), self.user_id))
|
||||
|
@ -40,7 +40,7 @@ class Currency:
|
|||
query = """
|
||||
SELECT cash_balance, special_balance
|
||||
FROM currency
|
||||
WHERE user_id = ?
|
||||
WHERE user_id = %s
|
||||
"""
|
||||
|
||||
try:
|
||||
|
@ -54,7 +54,7 @@ class Currency:
|
|||
if cash_balance is None or special_balance is None:
|
||||
query = """
|
||||
INSERT INTO currency (user_id, cash_balance, special_balance)
|
||||
VALUES (?, 50, 3)
|
||||
VALUES (%s, 50, 3)
|
||||
"""
|
||||
database.execute_query(query, (user_id,))
|
||||
return 50, 3
|
||||
|
|
|
@ -34,7 +34,7 @@ class Dailies:
|
|||
|
||||
query = """
|
||||
INSERT INTO dailies (user_id, amount, claimed_at, streak)
|
||||
VALUES (?, ?, ?, ?)
|
||||
VALUES (%s, %s, %s, %s)
|
||||
"""
|
||||
values = (self.user_id, self.amount, self.claimed_at, self.streak)
|
||||
database.execute_query(query, values)
|
||||
|
@ -80,7 +80,7 @@ class Dailies:
|
|||
WHERE id = (
|
||||
SELECT MAX(id)
|
||||
FROM dailies
|
||||
WHERE user_id = ?
|
||||
WHERE user_id = %s
|
||||
)
|
||||
"""
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ class Inventory:
|
|||
|
||||
query = """
|
||||
INSERT OR REPLACE INTO inventory (user_id, item_id, quantity)
|
||||
VALUES (?, ?, COALESCE((SELECT quantity FROM inventory WHERE user_id = ? AND item_id = ?), 0) + ?)
|
||||
VALUES (%s, %s, COALESCE((SELECT quantity FROM inventory WHERE user_id = %s AND item_id = %s), 0) + %s)
|
||||
"""
|
||||
|
||||
database.execute_query(query, (self.user_id, item.id, self.user_id, item.id, abs(quantity)))
|
||||
|
@ -29,13 +29,13 @@ class Inventory:
|
|||
def take_item(self, item: Item.Item, quantity=1):
|
||||
query = """
|
||||
INSERT OR REPLACE INTO inventory (user_id, item_id, quantity)
|
||||
VALUES (?, ?, COALESCE((SELECT quantity FROM inventory WHERE user_id = ? AND item_id = ?) - ?, 0))
|
||||
VALUES (%s, %s, COALESCE((SELECT quantity FROM inventory WHERE user_id = %s AND item_id = %s) - %s, 0))
|
||||
"""
|
||||
|
||||
database.execute_query(query, (self.user_id, item.id, self.user_id, item.id, abs(quantity)))
|
||||
|
||||
def get_inventory(self):
|
||||
query = "SELECT item_id, quantity FROM inventory WHERE user_id = ? AND quantity > 0"
|
||||
query = "SELECT item_id, quantity FROM inventory WHERE user_id = %s AND quantity > 0"
|
||||
results = database.select_query(query, (self.user_id,))
|
||||
|
||||
items_dict = {}
|
||||
|
@ -47,7 +47,7 @@ class Inventory:
|
|||
return items_dict
|
||||
|
||||
def get_item_quantity(self, item: Item.Item):
|
||||
query = "SELECT COALESCE(quantity, 0) FROM inventory WHERE user_id = ? AND item_id = ?"
|
||||
query = "SELECT COALESCE(quantity, 0) FROM inventory WHERE user_id = %s AND item_id = %s"
|
||||
result = database.select_query_one(query, (self.user_id, item.id))
|
||||
return result
|
||||
|
||||
|
@ -57,7 +57,7 @@ class Inventory:
|
|||
FROM inventory
|
||||
JOIN ShopItem ON inventory.item_id = ShopItem.item_id
|
||||
JOIN item ON inventory.item_id = item.id
|
||||
WHERE inventory.user_id = ? AND inventory.quantity > 0 AND ShopItem.worth > 0
|
||||
WHERE inventory.user_id = %s AND inventory.quantity > 0 AND ShopItem.worth > 0
|
||||
"""
|
||||
|
||||
try:
|
||||
|
|
12
data/Item.py
12
data/Item.py
|
@ -25,7 +25,7 @@ class Item:
|
|||
query = """
|
||||
SELECT name, display_name, description, image_url, emote_id, quote, type
|
||||
FROM item
|
||||
WHERE id = ?
|
||||
WHERE id = %s
|
||||
"""
|
||||
|
||||
data = database.select_query(query, (self.id,))[0]
|
||||
|
@ -33,7 +33,7 @@ class Item:
|
|||
|
||||
def get_quantity(self, author_id):
|
||||
query = """
|
||||
SELECT COALESCE((SELECT quantity FROM inventory WHERE user_id = ? AND item_id = ?), 0) AS quantity
|
||||
SELECT COALESCE((SELECT quantity FROM inventory WHERE user_id = %s AND item_id = %s), 0) AS quantity
|
||||
"""
|
||||
|
||||
quantity = database.select_query_one(query, (author_id, self.id))
|
||||
|
@ -44,7 +44,7 @@ class Item:
|
|||
query = """
|
||||
SELECT worth
|
||||
FROM ShopItem
|
||||
WHERE item_id = ?
|
||||
WHERE item_id = %s
|
||||
"""
|
||||
|
||||
return database.select_query_one(query, (self.id,))
|
||||
|
@ -66,7 +66,7 @@ class Item:
|
|||
query = """
|
||||
INSERT OR REPLACE INTO item
|
||||
(id, name, display_name, description, image_url, emote_id, quote, type)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
||||
"""
|
||||
database.execute_query(query,
|
||||
(index, name, display_name, description, image_url, emote_id, quote, item_type))
|
||||
|
@ -92,12 +92,12 @@ class Item:
|
|||
|
||||
@staticmethod
|
||||
def get_item_by_display_name(display_name):
|
||||
query = "SELECT id FROM item WHERE display_name = ?"
|
||||
query = "SELECT id FROM item WHERE display_name = %s"
|
||||
item_id = database.select_query_one(query, (display_name,))
|
||||
return Item(item_id)
|
||||
|
||||
@staticmethod
|
||||
def get_item_by_name(name):
|
||||
query = "SELECT id FROM item WHERE name = ?"
|
||||
query = "SELECT id FROM item WHERE name = %s"
|
||||
item_id = database.select_query_one(query, (name,))
|
||||
return Item(item_id)
|
||||
|
|
|
@ -18,22 +18,22 @@ class ShopItem:
|
|||
"""
|
||||
|
||||
def set_price(self, price):
|
||||
query = "UPDATE ShopItem SET price = ? WHERE item_id = ?"
|
||||
query = "UPDATE ShopItem SET price = %s WHERE item_id = %s"
|
||||
database.execute_query(query, (price, self.item.id))
|
||||
self.price = price
|
||||
|
||||
def set_price_special(self, price_special):
|
||||
query = "UPDATE ShopItem SET price_special = ? WHERE item_id = ?"
|
||||
query = "UPDATE ShopItem SET price_special = %s WHERE item_id = %s"
|
||||
database.execute_query(query, (price_special, self.item.id))
|
||||
self.price_special = price_special
|
||||
|
||||
def set_worth(self, worth):
|
||||
query = "UPDATE ShopItem SET worth = ? WHERE item_id = ?"
|
||||
query = "UPDATE ShopItem SET worth = %s WHERE item_id = %s"
|
||||
database.execute_query(query, (worth, self.item.id))
|
||||
self.worth = worth
|
||||
|
||||
def set_description(self, description):
|
||||
query = "UPDATE ShopItem SET description = ? WHERE item_id = ?"
|
||||
query = "UPDATE ShopItem SET description = %s WHERE item_id = %s"
|
||||
database.execute_query(query, (description, self.item.id))
|
||||
self.description = description
|
||||
|
||||
|
@ -41,7 +41,7 @@ class ShopItem:
|
|||
query = """
|
||||
SELECT price, price_special, worth, description
|
||||
FROM ShopItem
|
||||
WHERE item_id = ?
|
||||
WHERE item_id = %s
|
||||
"""
|
||||
|
||||
try:
|
||||
|
@ -49,7 +49,7 @@ class ShopItem:
|
|||
except (IndexError, TypeError):
|
||||
query = """
|
||||
INSERT INTO ShopItem (item_id, price, price_special, worth, description)
|
||||
VALUES (?, 0, 0, 0, "placeholder_descr")
|
||||
VALUES (%s, 0, 0, 0, "placeholder_descr")
|
||||
"""
|
||||
database.execute_query(query, (self.item.id,))
|
||||
(price, price_special, worth, description) = 0, 0, 0, "placeholder_descr"
|
||||
|
|
|
@ -15,7 +15,7 @@ class SlotsStats:
|
|||
def push(self):
|
||||
query = """
|
||||
INSERT INTO stats_slots (user_id, is_won, bet, payout, spin_type, icons)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
VALUES (%s, %s, %s, %s, %s, %s)
|
||||
"""
|
||||
|
||||
values = (self.user_id, self.is_won, self.bet, self.payout, self.spin_type, self.icons)
|
||||
|
@ -34,7 +34,7 @@ class SlotsStats:
|
|||
SUM(CASE WHEN spin_type = 'three_diamonds' AND is_won = 1 THEN 1 ELSE 0 END) AS games_won_three_diamonds,
|
||||
SUM(CASE WHEN spin_type = 'jackpot' AND is_won = 1 THEN 1 ELSE 0 END) AS games_won_jackpot
|
||||
FROM stats_slots
|
||||
WHERE user_id = ?
|
||||
WHERE user_id = %s
|
||||
"""
|
||||
|
||||
(amount_of_games, total_bet,
|
||||
|
|
|
@ -24,13 +24,13 @@ class Xp:
|
|||
def push(self):
|
||||
query = """
|
||||
UPDATE xp
|
||||
SET user_xp = ?, user_level = ?, cooldown = ?
|
||||
WHERE user_id = ?
|
||||
SET user_xp = %s, user_level = %s, cooldown = %s
|
||||
WHERE user_id = %s
|
||||
"""
|
||||
database.execute_query(query, (self.xp, self.level, self.ctime, self.user_id))
|
||||
|
||||
def fetch_or_create_xp(self):
|
||||
query = "SELECT user_xp, user_level, cooldown FROM xp WHERE user_id = ?"
|
||||
query = "SELECT user_xp, user_level, cooldown FROM xp WHERE user_id = %s"
|
||||
|
||||
try:
|
||||
(user_xp, user_level, cooldown) = database.select_query(query, (self.user_id,))[0]
|
||||
|
@ -40,7 +40,7 @@ class Xp:
|
|||
if any(var is None for var in [user_xp, user_level, cooldown]):
|
||||
query = """
|
||||
INSERT INTO xp (user_id, user_xp, user_level, cooldown)
|
||||
VALUES (?, 0, 0, ?)
|
||||
VALUES (%s, 0, 0, %s)
|
||||
"""
|
||||
database.execute_query(query, (self.user_id, time.time()))
|
||||
(user_xp, user_level, cooldown) = (0, 0, time.time())
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
import logging
|
||||
import sqlite3
|
||||
from sqlite3 import Error
|
||||
import mysql.connector
|
||||
from mysql.connector import Error
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
racu_logs = logging.getLogger('Racu.Core')
|
||||
load_dotenv('.env')
|
||||
|
||||
|
||||
def create_connection():
|
||||
try:
|
||||
conn = sqlite3.connect("db/rcu.db")
|
||||
except Error as e:
|
||||
racu_logs.error("'create_connection()' Error occurred: {}".format(e))
|
||||
return
|
||||
|
||||
return conn
|
||||
cnxpool = mysql.connector.pooling.MySQLConnectionPool(
|
||||
pool_name='core-pool',
|
||||
pool_size=25,
|
||||
host='db',
|
||||
user=os.getenv("MARIADB_USER"),
|
||||
password=os.getenv("MARIADB_PASSWORD"),
|
||||
database='racudb'
|
||||
)
|
||||
|
||||
|
||||
def execute_query(query, values=None):
|
||||
conn = create_connection()
|
||||
conn = cnxpool.get_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
if values:
|
||||
|
@ -25,32 +28,42 @@ def execute_query(query, values=None):
|
|||
cursor.execute(query)
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
racu_logs.debug(f"database.execute_query: 'query': {query}, 'values': {values}")
|
||||
return cursor
|
||||
|
||||
|
||||
def select_query(query, values=None):
|
||||
conn = create_connection()
|
||||
conn = cnxpool.get_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
racu_logs.debug(f"database.select_query: 'query': {query}, 'values': {values}")
|
||||
|
||||
if values:
|
||||
return cursor.execute(query, values).fetchall()
|
||||
cursor.execute(query, values)
|
||||
output = cursor.fetchall()
|
||||
else:
|
||||
return cursor.execute(query).fetchall()
|
||||
cursor.execute(query)
|
||||
output = cursor.fetchall()
|
||||
|
||||
conn.close()
|
||||
return output
|
||||
|
||||
|
||||
def select_query_one(query, values=None):
|
||||
conn = create_connection()
|
||||
conn = cnxpool.get_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
racu_logs.debug(f"database.select_query_one: 'query': {query}, 'values': {values}")
|
||||
|
||||
if values:
|
||||
output = cursor.execute(query, values).fetchone()
|
||||
cursor.execute(query, values)
|
||||
output = cursor.fetchone()
|
||||
else:
|
||||
output = cursor.execute(query).fetchone()
|
||||
cursor.execute(query)
|
||||
output = cursor.fetchone()
|
||||
|
||||
conn.close()
|
||||
|
||||
if output:
|
||||
return output[0]
|
||||
|
|
82
db/init/1-tables.sql
Normal file
82
db/init/1-tables.sql
Normal file
|
@ -0,0 +1,82 @@
|
|||
SET FOREIGN_KEY_CHECKS=0;
|
||||
|
||||
CREATE TABLE xp (
|
||||
user_id BIGINT PRIMARY KEY NOT NULL,
|
||||
user_xp INT NOT NULL,
|
||||
user_level INT NOT NULL,
|
||||
cooldown FLOAT
|
||||
);
|
||||
|
||||
CREATE TABLE currency (
|
||||
user_id BIGINT PRIMARY KEY NOT NULL,
|
||||
cash_balance BIGINT NOT NULL,
|
||||
special_balance BIGINT
|
||||
);
|
||||
|
||||
CREATE TABLE stats_bj (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id BIGINT,
|
||||
is_won BOOLEAN,
|
||||
bet BIGINT,
|
||||
payout BIGINT,
|
||||
hand_player TEXT,
|
||||
hand_dealer TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE stats_slots (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id BIGINT,
|
||||
is_won BOOLEAN,
|
||||
bet BIGINT,
|
||||
payout BIGINT,
|
||||
spin_type TEXT,
|
||||
icons TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE stats_duel (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id BIGINT,
|
||||
is_won BOOLEAN,
|
||||
bet BIGINT
|
||||
);
|
||||
|
||||
CREATE TABLE dailies (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id BIGINT,
|
||||
amount BIGINT,
|
||||
claimed_at TINYTEXT,
|
||||
streak INT
|
||||
);
|
||||
|
||||
CREATE TABLE item (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
name TEXT,
|
||||
display_name TEXT,
|
||||
description TEXT,
|
||||
image_url TEXT,
|
||||
emote_id BIGINT,
|
||||
quote TEXT,
|
||||
type TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE inventory (
|
||||
user_id BIGINT,
|
||||
item_id INT,
|
||||
quantity INT,
|
||||
|
||||
PRIMARY KEY (user_id, item_id),
|
||||
FOREIGN KEY (item_id) REFERENCES item (id)
|
||||
);
|
||||
|
||||
CREATE TABLE ShopItem (
|
||||
item_id INT PRIMARY KEY,
|
||||
price BIGINT,
|
||||
price_special BIGINT,
|
||||
worth BIGINT,
|
||||
description TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE birthdays (
|
||||
user_id BIGINT NOT NULL PRIMARY KEY,
|
||||
birthday DATETIME DEFAULT NULL
|
||||
);
|
34
docker-compose.yml
Normal file
34
docker-compose.yml
Normal file
|
@ -0,0 +1,34 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
core:
|
||||
build: .
|
||||
restart: always
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
db:
|
||||
image: mariadb
|
||||
restart: always
|
||||
environment:
|
||||
MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
|
||||
MARIADB_USER: ${MARIADB_USER}
|
||||
MARIADB_PASSWORD: ${MARIADB_PASSWORD}
|
||||
MARIADB_DATABASE: racudb
|
||||
volumes:
|
||||
- ./db/init:/docker-entrypoint-initdb.d/
|
||||
- ./db/data:/var/lib/mysql
|
||||
ports:
|
||||
- 3306:3306
|
||||
healthcheck:
|
||||
test: [ "CMD", "mariadb", "-h", "localhost", "-u", "${MARIADB_USER}", "-p${MARIADB_PASSWORD}", "-e", "SELECT 1" ]
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
|
||||
adminer:
|
||||
image: adminer
|
||||
restart: always
|
||||
ports:
|
||||
- 8080:8080
|
7
main.py
7
main.py
|
@ -9,6 +9,9 @@ SPECIAL_BALANCE_NAME=
|
|||
DBX_OAUTH2_REFRESH_TOKEN=
|
||||
DBX_APP_KEY=
|
||||
DBX_APP_SECRET=
|
||||
MARIADB_USER=
|
||||
MARIADB_PASSWORD=
|
||||
MARIADB_ROOT_PASSWORD=
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
@ -280,8 +283,8 @@ if __name__ == '__main__':
|
|||
load_dotenv('.env')
|
||||
|
||||
# load db
|
||||
db.tables.sync_database()
|
||||
Item.insert_items()
|
||||
# db.tables.sync_database()
|
||||
# Item.insert_items()
|
||||
|
||||
load_cogs()
|
||||
sbbot.run(os.getenv('TOKEN'))
|
||||
|
|
|
@ -2,4 +2,5 @@ py-cord==2.4.1
|
|||
python-dotenv==1.0.0
|
||||
setuptools==67.8.0
|
||||
pytz==2023.3
|
||||
dropbox==11.36.2
|
||||
dropbox==11.36.2
|
||||
mysql-connector-python==8.1.0
|
Loading…
Reference in a new issue