大幅な仕様変更のためtestingをmainにマージ

This commit is contained in:
suti7yk5032 2024-08-22 22:46:41 +09:00
commit 7fc3ca701c
24 changed files with 1492 additions and 239 deletions

4
.gitignore vendored
View file

@ -162,4 +162,6 @@ cython_debug/
config/ config/
db/ db/
test.py test.py
data/
export/

22
Dockerfile_auth Normal file
View file

@ -0,0 +1,22 @@
FROM python:3
USER root
RUN mkdir /dislocker
RUN apt-get update
RUN apt-get -y install locales && \
localedef -f UTF-8 -i ja_JP ja_JP.UTF-8
ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP:ja
ENV LC_ALL ja_JP.UTF-8
ENV TZ JST-9
ENV TERM xterm
RUN apt-get install -y nano less
RUN pip install --upgrade pip
RUN pip install --upgrade setuptools
RUN python -m pip install flask psycopg2-binary requests
WORKDIR /dislocker
CMD python -u ./dislocker_auth.py

22
Dockerfile_bot Normal file
View file

@ -0,0 +1,22 @@
FROM python:3
USER root
RUN mkdir /dislocker
RUN apt-get update
RUN apt-get -y install locales && \
localedef -f UTF-8 -i ja_JP ja_JP.UTF-8
ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP:ja
ENV LC_ALL ja_JP.UTF-8
ENV TZ JST-9
ENV TERM xterm
RUN apt-get install -y nano less
RUN pip install --upgrade pip
RUN pip install --upgrade setuptools
RUN python -m pip install discord.py psycopg2-binary requests openpyxl
WORKDIR /dislocker
CMD python -u ./dislocker.py

View file

@ -1,2 +1,9 @@
# Dislocker # Dislocker
課題研究用リポジトリ
# 環境構築
## サーバー側
基本的にはDocker上での起動を推奨します。
このリポジトリをクローンし、`docker compose up -d`で起動すると一式のコンテナが起動します。
データベースだけを起動したい場合は、ファイルに`compose_db.yml`を指定してください。
## クライアント側
pyinstallerでビルドしたものを、起動してください。

45
compose.yml Normal file
View file

@ -0,0 +1,45 @@
services:
bot:
build:
context: "./"
dockerfile: "Dockerfile_bot"
restart: always
environment:
- TZ=Asia/Tokyo
depends_on:
- db
volumes:
- ./:/dislocker
networks:
- dislocker_network
auth:
build:
context: "./"
dockerfile: "Dockerfile_auth"
restart: always
environment:
- TZ=Asia/Tokyo
volumes:
- ./:/dislocker
ports:
- 12244:5000
networks:
- dislocker_network
db:
image: postgres:alpine3.20
restart: always
environment:
- TZ=Asia/Tokyo
volumes:
- ./data/db:/var/lib/postgresql/data
ports:
- 12245:5432
env_file:
- ./data/.env
networks:
- dislocker_network
networks:
dislocker_network:

18
compose_db.yml Normal file
View file

@ -0,0 +1,18 @@
services:
db:
image: postgres:alpine3.20
restart: always
environment:
- TZ=Asia/Tokyo
- POSTGRES_DB=dislocker
- POSTGRES_USER=dislocker
- POSTGRES_PASSWORD=Password
volumes:
- ./data/db:/var/lib/postgresql/data
ports:
- 12245:5432
networks:
- dislocker_network
networks:
dislocker_network:

View file

@ -1,14 +1,116 @@
import json import json
import discord import discord
from discord import Interaction, TextStyle, app_commands
from discord.ui import TextInput, View, Modal
import os import os
import psycopg2 import psycopg2
from psycopg2 import sql
import hashlib import hashlib
import string import string
import random import random
from datetime import datetime, timedelta
from openpyxl import Workbook
import threading
import time
class DL():
def __init__(self):
self.config_dir_path = "./config/"
self.export_dir_path = "./export/"
self.server_config_path = self.config_dir_path + "server.json"
try:
if not os.path.isdir(self.config_dir_path):
print("config ディレクトリが見つかりません... 作成します。")
os.mkdir(self.config_dir_path)
if not os.path.isfile(self.server_config_path):
print("config ファイルが見つかりません... 作成します。")
self.server_config = {
"db": {
"host": "localhost",
"port": "5432",
"db_name": "dislocker",
"username": "user",
"password": "password"
},
"bot": {
"token": "TYPE HERE BOTS TOKEN KEY",
"activity": {
"name": "Dislocker",
"details": "ロック中...",
"type": "playing",
"state": "ロック中..."
},
"log_channel_id" : "TYPE HERE CHANNEL ID (YOU MUST USE INT !!!!)",
"config_channel_id": "TYPE HERE CHANNEL ID (YOU MUST USE INT !!!!)",
"config_public_channel_id": "TYPE HERE CHANNEL ID (YOU MUST USE INT !!!!)",
"monitor": {
"search_frequency": 1,
"allowable_time": 180,
"fstop_time": "21:00:00"
}
}
}
with open(self.server_config_path, "w", encoding="utf-8") as w:
json.dump(self.server_config, w, indent=4, ensure_ascii=False)
elif os.path.isfile(self.server_config_path):
with open(self.server_config_path, "r", encoding="utf-8") as r:
self.server_config = json.load(r)
print("config ファイルを読み込みました。")
if not os.path.isdir(self.export_dir_path):
print("export ディレクトリが見つかりません... 作成します。")
os.mkdir(self.export_dir_path)
if type(self.server_config["bot"]["log_channel_id"]) is not int or type(self.server_config["bot"]["config_channel_id"]) is not int:
print("config ファイル内でチャンネルIDがint型で記述されていません。int型で記述して、起動してください。")
self.init_result = "not_int"
else:
self.db = psycopg2.connect(f"host={self.server_config['db']['host']} dbname={self.server_config['db']['db_name']} port={self.server_config['db']['port']} user={self.server_config['db']['username']} password={self.server_config['db']['password']}")
cursor = self.db.cursor()
self.pc_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
cursor.execute("SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'club_member')")
find_club_member_table = cursor.fetchall()
print(find_club_member_table)
if find_club_member_table[0][0] == False:
cursor.execute("CREATE TABLE club_member (id SERIAL NOT NULL, name VARCHAR(128) NOT NULL, discord_username VARCHAR(128) NOT NULL, discord_userid VARCHAR(18) NOT NULL, PRIMARY KEY (id))")
self.db.commit()
cursor.execute("SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'pc_list')")
find_pc_list_table = cursor.fetchall()
print(find_pc_list_table)
if find_pc_list_table[0][0] == False:
cursor.execute("CREATE TABLE pc_list (pc_number INTEGER NOT NULL, using_user_id INTEGER, password_hash VARCHAR(32), PRIMARY KEY (pc_number), FOREIGN KEY (using_user_id) REFERENCES club_member(id))")
for i in range(10):
print(i)
cursor.execute("INSERT INTO pc_list (pc_number) VALUES (%s)", (i + 1,))
self.db.commit()
cursor.execute("SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'pc_usage_history')")
find_pc_usage_history_table = cursor.fetchall()
print(find_pc_usage_history_table)
if find_pc_usage_history_table[0][0] == False:
cursor.execute("CREATE TABLE pc_usage_history (id SERIAL NOT NULL, member_id INTEGER NOT NULL, pc_number INTEGER NOT NULL, device_number INTEGER NOT NULL, start_use_time TIMESTAMP NOT NULL, end_use_time TIMESTAMP, use_detail VARCHAR(128), bot_about VARCHAR(128), PRIMARY KEY (id), FOREIGN KEY (member_id) REFERENCES club_member(id), FOREIGN KEY (pc_number) REFERENCES pc_list(pc_number))")
self.db.commit()
cursor.close()
self.init_result = "ok"
except (Exception) as error:
print("初回処理でエラーが発生しました。\nエラー内容\n" + str(error))
self.init_result = "error"
finally:
pass
class Bot(discord.Client): class Bot(discord.Client):
def db_connect(self, host, db, port, user, password):
self.db = psycopg2.connect(f"host={host} dbname={db} port={port} user={user} password={password}")
def password_generate(self, length): def password_generate(self, length):
numbers = string.digits # (1) numbers = string.digits # (1)
@ -19,106 +121,331 @@ class Bot(discord.Client):
hashed = hashlib.md5(source.encode()) hashed = hashlib.md5(source.encode())
return hashed.hexdigest() return hashed.hexdigest()
def register(self, **kwrags): def register(self, **kwargs):
discord_user_id = str(kwrags["user_id"]) try:
pc_number = int(kwrags["pc_number"]) user_info = {
device_number = int(kwrags["device_number"]) "id": str(kwargs["user_id"]),
if "detail" in kwrags: "name": str(kwargs["name"]),
detail = str(kwrags["detail"]) "display_name": str(kwargs["display_name"]),
else: "pc_number": int(kwargs["pc_number"]),
detail = None "device_number": int(kwargs["device_number"]),
#パスワード生成、ハッシュ化 "detail": None
password = self.password_generate(4) }
password_hash = self.hash_genarate(password) if "detail" in kwargs:
print("password generated") user_info["detail"] = str(kwargs["detail"])
#メンバーリストと送信者を照合 else:
cursor = self.db.cursor() pass
cursor.execute("SELECT * FROM club_member WHERE discord_userid = %s", (discord_user_id,)) #パスワード生成、ハッシュ化
user_record = cursor.fetchall() password = self.password_generate(4)
#ユーザーデータがなかったら(未登録の場合) password_hash = self.hash_genarate(password)
if not user_record: print("password generated")
result = {"result": "user_data_not_found"} #メンバーリストと送信者を照合
#ユーザーデータが見つかった場合(登録済みの場合) cursor = dislocker.db.cursor()
else: cursor.execute("SELECT * FROM club_member WHERE discord_userid = %s", (user_info["id"],))
print("found user data") user_record = cursor.fetchall()
cursor.execute("SELECT * FROM pc_usage_history WHERE member_id=%s ORDER BY id DESC LIMIT 1", (user_record[0][0],)) #ユーザーデータがなかったら(未登録の場合)
pc_usage_history_record = cursor.fetchall() if not user_record:
if pc_usage_history_record: result = {"result": "user_data_not_found"}
print("used") #ユーザーデータが見つかった場合(登録済みの場合)
if pc_usage_history_record[0][5] == None: else:
result = {"result": "pc_already_in_use_by_you", "pc_number": str(pc_usage_history_record[0][2]), "device_number": str(pc_usage_history_record[0][3]), "start_time": str(pc_usage_history_record[0][4]), "detail": str(pc_usage_history_record[0][6])} print("found user data")
else: cursor.execute("SELECT * FROM pc_usage_history WHERE member_id=%s ORDER BY id DESC LIMIT 1", (user_record[0][0],))
cursor.execute("SELECT * FROM pc_list WHERE pc_number=%s", (pc_number,)) pc_usage_history_record = cursor.fetchall()
pc_list_record = cursor.fetchall() if pc_usage_history_record:
if not pc_list_record[0][1] == None: print("used")
result = {"result": "pc_already_in_use_by_other"} if pc_usage_history_record[0][5] == None:
result = {"result": "pc_already_in_use_by_you", "pc_number": str(pc_usage_history_record[0][2]), "device_number": str(pc_usage_history_record[0][3]), "start_time": str(pc_usage_history_record[0][4]), "detail": str(pc_usage_history_record[0][6])}
else: else:
if detail == None: cursor.execute("SELECT * FROM pc_list WHERE pc_number=%s", (user_info["pc_number"],))
cursor.execute("INSERT INTO pc_usage_history (member_id, pc_number, device_number, start_use_time) VALUES (%s, %s, %s, current_timestamp)", (user_record[0][0], pc_number, device_number)) pc_list_record = cursor.fetchall()
if not pc_list_record[0][1] == None:
result = {"result": "pc_already_in_use_by_other"}
else:
if user_info["detail"] == None:
cursor.execute("INSERT INTO pc_usage_history (member_id, pc_number, device_number, start_use_time) VALUES (%s, %s, %s, clock_timestamp())", (user_record[0][0], user_info["pc_number"], user_info["device_number"]))
#使用用途があるとき
else:
cursor.execute("INSERT INTO pc_usage_history (member_id, pc_number, device_number, start_use_time, use_detail) VALUES (%s, %s, %s, clock_timestamp(), %s)", (user_record[0][0], user_info["pc_number"], user_info["device_number"], user_info["detail"]))
cursor.execute("UPDATE pc_list SET using_user_id = %s WHERE pc_number = %s", (user_record[0][0], user_info["pc_number"]))
cursor.execute("UPDATE pc_list SET password_hash = %s WHERE pc_number = %s", (password_hash, user_info["pc_number"]))
dislocker.db.commit()
result = {"result": "ok", "password": str(password), "name": str(user_record[0][1])}
else:
print("unused")
cursor.execute("SELECT * FROM pc_list WHERE pc_number=%s", (user_info["pc_number"],))
pc_list_record = cursor.fetchall()
if pc_list_record:
if not pc_list_record[0][1] == None:
result = {"result": "pc_already_in_use_by_other"}
else:
if user_info["detail"] == None:
cursor.execute("INSERT INTO pc_usage_history (member_id, pc_number, device_number, start_use_time) VALUES (%s, %s, %s, clock_timestamp())", (user_record[0][0], user_info["pc_number"], user_info["device_number"]))
#使用用途があるとき
else:
cursor.execute("INSERT INTO pc_usage_history (member_id, pc_number, device_number, start_use_time, use_detail) VALUES (%s, %s, %s, clock_timestamp(), %s)", (user_record[0][0], user_info["pc_number"], user_info["device_number"], user_info["detail"]))
cursor.execute("UPDATE pc_list SET using_user_id = %s WHERE pc_number = %s", (user_record[0][0], user_info["pc_number"]))
cursor.execute("UPDATE pc_list SET password_hash = %s WHERE pc_number = %s", (password_hash, user_info["pc_number"]))
dislocker.db.commit()
result = {"result": "ok", "password": str(password), "name": str(user_record[0][1])}
else:
if user_info["detail"] == None:
cursor.execute("INSERT INTO pc_usage_history (member_id, pc_number, device_number, start_use_time) VALUES (%s, %s, %s, clock_timestamp())", (user_record[0][0], user_info["pc_number"], user_info["device_number"]))
#使用用途があるとき #使用用途があるとき
else: else:
cursor.execute("INSERT INTO pc_usage_history (member_id, pc_number, device_number, start_use_time, use_detail) VALUES (%s, %s, %s, current_timestamp, %s)", (user_record[0][0], pc_number, device_number, detail)) cursor.execute("INSERT INTO pc_usage_history (member_id, pc_number, device_number, start_use_time, use_detail) VALUES (%s, %s, %s, clock_timestamp(), %s)", (user_record[0][0], user_info["pc_number"], user_info["device_number"], user_info["detail"]))
cursor.execute("UPDATE pc_list SET using_user_id = %s WHERE pc_number = %s", (user_record[0][0], pc_number)) cursor.execute("UPDATE pc_list SET using_user_id = %s WHERE pc_number = %s", (user_record[0][0], user_info["pc_number"]))
cursor.execute("UPDATE pc_list SET password_hash = %s WHERE pc_number = %s", (password_hash, pc_number)) cursor.execute("UPDATE pc_list SET password_hash = %s WHERE pc_number = %s", (password_hash, user_info["pc_number"]))
self.db.commit() dislocker.db.commit()
result = {"result": "ok", "password": str(password), "name": str(user_record[0][1])} result = {"result": "ok", "password": str(password), "name": str(user_record[0][1])}
except Exception as error:
print("登録処理中にエラーが発生しました。\nエラー内容")
print(str(error.__class__.__name__))
print(str(error.args))
print(str(error))
result = {"result": "error"}
finally:
cursor.close()
return result
def stop(self, **kwargs):
try:
discord_user_id = str(kwargs["user_id"])
if "bot_about" in kwargs:
bot_about = kwargs["bot_about"]
else: else:
cursor.execute("SELECT * FROM pc_list WHERE pc_number=%s", (pc_number,)) bot_about = None
cursor = dislocker.db.cursor()
cursor.execute("SELECT * FROM club_member WHERE discord_userid = %s", (discord_user_id,))
user_record = cursor.fetchall()
if user_record:
cursor.execute("SELECT * FROM pc_usage_history WHERE member_id= %s ORDER BY id DESC LIMIT 1", (user_record[0][0],))
pc_usage_history_record = cursor.fetchall()
if pc_usage_history_record:
if not pc_usage_history_record[0][5] == None:
result = {"result": "unused"}
else:
if not bot_about == None:
cursor.execute("UPDATE pc_usage_history SET end_use_time = clock_timestamp(), bot_about = %s WHERE id = %s", (bot_about, pc_usage_history_record[0][0]))
else:
cursor.execute("UPDATE pc_usage_history SET end_use_time = clock_timestamp() WHERE id = %s", (pc_usage_history_record[0][0],))
cursor.execute("UPDATE pc_list SET using_user_id = NULL, password_hash = NULL WHERE pc_number = %s", (pc_usage_history_record[0][2],))
dislocker.db.commit()
result = {"result": "ok", "pc_number": str(pc_usage_history_record[0][2]), "name": str(user_record[0][1])}
else:
result = {"result": "unused"}
else:
result = {"result": "user_data_not_found"}
except:
print("停止処理にエラーが発生しました。")
result = {"result": "error"}
finally:
cursor.close()
return result
def user_register(self, **kwargs):
try:
discord_user_id = str(kwargs["discord_user_id"])
discord_user_name = str(kwargs["discord_user_name"])
name = str(kwargs["name"])
cursor = dislocker.db.cursor()
cursor.execute("SELECT * FROM club_member WHERE discord_userid = %s", (discord_user_id,))
user_record = cursor.fetchall()
if not user_record:
cursor.execute("INSERT INTO club_member (name, discord_username, discord_userid) VALUES (%s, %s, %s)", (name, discord_user_name, discord_user_id))
dislocker.db.commit()
result = {"result": "ok"}
else:
result = {"result": "already_exists"}
except Exception as error:
print("ユーザー登録中にエラーが発生しました。\nエラー内容")
print(str(error.__class__.__name__))
print(str(error.args))
print(str(error))
result = {"result": "error"}
finally:
cursor.close()
return result
def format_datetime(self, value):
if isinstance(value, datetime):
return value.strftime('%Y-%m-%d %H:%M:%S')
return value
def pc_register(self, **kwargs):
try:
pc_number = int(kwargs["pc_number"])
cursor = dislocker.db.cursor()
cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s", (pc_number,))
pc_list = cursor.fetchall()
if not pc_list:
cursor.execute("INSERT INTO pc_list (pc_number) VALUES (%s)", (pc_number,))
dislocker.db.commit()
result = {"result": "ok"}
else:
result = {"result": "already_exists"}
except Exception as error:
print("PCの登録中にエラーが発生しました。\nエラー内容")
print(str(error.__class__.__name__))
print(str(error.args))
print(str(error))
result = {"result": "error"}
finally:
cursor.close()
return result
def report_export(self, **kwargs):
try:
cursor = dislocker.db.cursor()
csv_file_path = dislocker.export_dir_path + "pc_usage_history.csv"
main_table = "pc_usage_history"
related_table = "club_member"
excel_file_path = dislocker.export_dir_path + "pc_usage_history.xlsx"
# メインテーブルの列情報を取得user_idを除く
cursor.execute(sql.SQL("SELECT * FROM {} LIMIT 0").format(sql.Identifier(main_table)))
main_columns = [desc[0] for desc in cursor.description if desc[0] != 'member_id']
# クエリを作成(列名を明確に指定)
query = sql.SQL("""
SELECT {main_columns}, {related_table}.name
FROM {main_table}
LEFT JOIN {related_table} ON {main_table}.member_id = {related_table}.id
ORDER BY id
""").format(
main_columns=sql.SQL(', ').join([sql.SQL("{}.{}").format(sql.Identifier(main_table), sql.Identifier(col)) for col in main_columns]),
main_table=sql.Identifier(main_table),
related_table=sql.Identifier(related_table)
)
cursor.execute(query)
# 列名を再構成nameを2番目に配置
column_names = [main_columns[0], 'name'] + main_columns[1:]
rows = cursor.fetchall()
# Excelワークブックを作成
wb = Workbook()
ws = wb.active
# 列名を書き込み
ws.append(column_names)
# データを書き込み
for row in rows:
# nameを2番目に移動
formatted_row = [self.format_datetime(row[0])] + [row[-1]] + [self.format_datetime(field) if field is not None else '' for field in row[1:-1]]
ws.append(formatted_row)
# 列幅を自動調整
for col in ws.columns:
max_length = 0
column = col[0].column_letter
for cell in col:
try:
if len(str(cell.value)) > max_length:
max_length = len(str(cell.value))
except:
pass
adjusted_width = (max_length + 2) * 1.2
ws.column_dimensions[column].width = adjusted_width
# Excelファイルを保存
wb.save(excel_file_path)
print(f"テーブル '{main_table}' の内容を '{excel_file_path}' に出力しました。")
result = {"result": "ok", "file_path": excel_file_path}
except (Exception, psycopg2.Error) as error:
print("使用履歴のエクスポート時にエラーが発生しました\nエラー内容\n", str(error))
result = {"result": "export_error"}
finally:
cursor.close()
return result
def force_stop(self, **kwargs):
try:
pc_number = kwargs["pc_number"]
if "bot_about" in kwargs:
bot_about = kwargs["bot_about"]
cursor = dislocker.db.cursor()
cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s", (pc_number,))
pc_list_record = cursor.fetchall() pc_list_record = cursor.fetchall()
if not pc_list_record[0][1] == None: if not pc_list_record[0][1] == None:
result = {"result": "pc_already_in_use_by_other"} cursor.execute("UPDATE pc_list SET using_user_id = NULL WHERE pc_number = %s", (pc_number,))
if not pc_list_record[0][2] == None:
cursor.execute("UPDATE pc_list SET password_hash = NULL WHERE pc_number = %s", (pc_number,))
cursor.execute("SELECT * FROM pc_usage_history WHERE member_id = %s AND pc_number = %s ORDER BY id DESC LIMIT 1", (pc_list_record[0][1], pc_number))
pc_usage_history_record = cursor.fetchall()
cursor.execute("UPDATE pc_usage_history SET end_use_time = clock_timestamp(), bot_about = %s WHERE id = %s", (bot_about, pc_usage_history_record[0][0]))
dislocker.db.commit()
result = {"result": "ok"}
else: else:
if detail == None: result = {"result": "not_used"}
cursor.execute("INSERT INTO pc_usage_history (member_id, pc_number, device_number, start_use_time) VALUES (%s, %s, %s, current_timestamp)", (user_record[0][0], pc_number, device_number))
#使用用途があるとき
else:
cursor.execute("INSERT INTO pc_usage_history (member_id, pc_number, device_number, start_use_time, use_detail) VALUES (%s, %s, %s, current_timestamp, %s)", (user_record[0][0], pc_number, device_number, detail))
cursor.execute("UPDATE pc_list SET using_user_id = %s WHERE pc_number = %s", (user_record[0][0], pc_number))
cursor.execute("UPDATE pc_list SET password_hash = %s WHERE pc_number = %s", (password_hash, pc_number))
self.db.commit()
result = {"result": "ok", "password": str(password), "name": str(user_record[0][1])}
return result
def stop(self, **kwrags):
discord_user_id = str(kwrags["user_id"])
cursor = self.db.cursor()
cursor.execute("SELECT * FROM club_member WHERE discord_userid = %s", (discord_user_id,))
user_record = cursor.fetchall()
cursor.execute("SELECT * FROM pc_usage_history WHERE member_id= %s ORDER BY id DESC LIMIT 1", (user_record[0][0],))
pc_usage_history_record = cursor.fetchall()
if pc_usage_history_record:
if not pc_usage_history_record[0][5] == None:
result = {"result": "unused"}
else: else:
cursor.execute("UPDATE pc_usage_history SET end_use_time = current_timestamp WHERE id = %s", (pc_usage_history_record[0][0],)) bot_about = None
cursor.execute("UPDATE pc_list SET using_user_id = NULL WHERE pc_number = %s", (pc_usage_history_record[0][2],)) result = {"result": "bot_about_not_found"}
cursor.execute("UPDATE pc_list SET password_hash = NULL WHERE pc_number = %s", (pc_usage_history_record[0][2],))
self.db.commit()
result = {"result": "ok", "pc_number": str(pc_usage_history_record[0][2]), "name": str(user_record[0][1])}
return result except:
result = {"result": "error"}
def user_register(self, **kwrags):
discord_user_id = str(kwrags["discord_user_id"]) finally:
discord_user_name = str(kwrags["discord_user_name"]) cursor.close()
name = str(kwrags["name"]) return result
cursor = self.db.cursor()
cursor.execute("SELECT * FROM club_member WHERE discord_userid = %s", (discord_user_id,))
user_record = cursor.fetchall() async def timeout_notify(self, **kwargs):
if not user_record: try:
cursor.execute("INSERT INTO club_member (name, discord_username, discord_userid) VALUES (%s, %s, %s)", (name, discord_user_name, discord_user_id)) pc_number = kwargs["pc_number"]
self.db.commit() discord_display_name = kwargs["discord_display_name"]
result = {"result": "ok"}
else: await self.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':negative_squared_cross_mark: {discord_display_name} さんのPC {pc_number} の使用登録はタイムアウトにより解除されました。')
result = {"result": "already_exists"} result = {"result": "ok"}
except Exception as error:
print("自動停止処理中にエラーが発生しました。\nエラー内容")
print(str(error.__class__.__name__))
print(str(error.args))
print(str(error))
result = {"result": "error"}
finally:
return result
return result
async def on_ready(self): async def on_ready(self):
print("ログイン成功") print("DiscordのBotが起動しました。")
dislocker_activity = discord.Activity(
name=dislocker.server_config["bot"]["activity"]["name"],
type=discord.ActivityType.competing,
details=dislocker.server_config["bot"]["activity"]["details"],
state=dislocker.server_config["bot"]["activity"]["state"]
)
await bot.change_presence(activity=dislocker_activity)
async def on_interaction(self, interaction:discord.Interaction):
try:
if interaction.data["component_type"] == 2:
await self.on_button(interaction)
except KeyError:
pass
async def on_message(self, message): async def on_message(self, message):
if message.author.bot: if message.author.bot:
@ -129,7 +456,7 @@ class Bot(discord.Client):
if msg_split[0] == "/password" or msg_split[0] == "/start": if msg_split[0] == "/password" or msg_split[0] == "/start":
#メッセージの要素が2つ以下の場合は拒否 #メッセージの要素が2つ以下の場合は拒否
if len(msg_split) <= 2: if len(msg_split) <= 2:
await message.channel.send("PC番号、もしくはデバイス番号が入力されていません。") await message.channel.send("# :warning: PC番号、もしくはデバイス番号が入力されていません。")
#メッセージの要素が3つ以上の場合 #メッセージの要素が3つ以上の場合
elif len(msg_split) >= 3: elif len(msg_split) >= 3:
#番号が数字であることを確認 #番号が数字であることを確認
@ -137,92 +464,338 @@ class Bot(discord.Client):
#PC番号が1以上10以下であることを確認 #PC番号が1以上10以下であることを確認
if int(msg_split[1]) <= 10 and int(msg_split[1]) >= 1: if int(msg_split[1]) <= 10 and int(msg_split[1]) >= 1:
if len(msg_split) == 3: if len(msg_split) == 3:
register = self.register(user_id=message.author.id, pc_number=msg_split[1], device_number=msg_split[2]) register = self.register(user_id=message.author.id, name=message.author.name, display_name=message.author.display_name, pc_number=msg_split[1], device_number=msg_split[2])
elif len(msg_split) == 4: elif len(msg_split) == 4:
register = self.register(user_id=message.author.id, pc_number=msg_split[1], device_number=msg_split[2], detail=msg_split[3]) register = self.register(user_id=message.author.id, name=message.author.name, display_name=message.author.display_name, pc_number=msg_split[1], device_number=msg_split[2], detail=msg_split[3])
if register["result"] == "ok": if register["result"] == "ok":
if len(msg_split) == 3: if len(msg_split) == 3:
await message.channel.send(f"使用が開始されました。\nパスワード | {register["password"]}\nPC番号 | {msg_split[1]}\nデバイス番号 | {msg_split[2]}") await message.channel.send(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["password"]}\n## PC番号 | {msg_split[1]}\n## デバイス番号 | {msg_split[2]}")
elif len(msg_split) == 4: elif len(msg_split) == 4:
await message.channel.send(f"使用が開始されました。\nパスワード | {register["password"]}\nPC番号 | {msg_split[1]}\nデバイス番号 | {msg_split[2]}\n使用目的 | {msg_split[3]}") await message.channel.send(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["password"]}\n## PC番号 | {msg_split[1]}\n## デバイス番号 | {msg_split[2]}\n## 使用目的 | {msg_split[3]}")
await self.get_channel(bot_config["log_channel_id"]).send(f'{register["name"]} さんがPC {msg_split[1]} を使用しています') await self.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':white_check_mark: {register["name"]} さんがPC {msg_split[1]} の使用を開始しました。')
elif register["result"] == "user_data_not_found": elif register["result"] == "user_data_not_found":
await message.channel.send("ユーザーとして登録されていないようです。管理者に問い合わせてください。") await message.channel.send("# :dizzy_face: ユーザーとして登録されていないようです。\n最初にサーバーで登録を行ってください。")
elif register["result"] == "pc_already_in_use_by_you": elif register["result"] == "pc_already_in_use_by_you":
await message.channel.send(f"あなたはPCをもう使用されているようです。使用状態を解除するには /stop で使用終了をお知らせください。\nPC番号 | {register["pc_number"]}\nデバイス番号 | {register["device_number"]}\n使用開始時刻 | {register["start_time"]}\n使用目的 | {register["detail"]}") await message.channel.send(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには /stop で使用終了をお知らせください。\n>>> # PC番号 | {register["pc_number"]}\n# デバイス番号 | {register["device_number"]}\n# 使用開始時刻 | {register["start_time"]}\n# 使用目的 | {register["detail"]}")
elif register["result"] == "pc_already_in_use_by_other": elif register["result"] == "pc_already_in_use_by_other":
await message.channel.send(f"PCはもう使用されています。別のPC番号を指定して、再度お試しください。") await message.channel.send(f"# :man_gesturing_no: そのPCは他のメンバーによって使用されています。\n別のPC番号を指定して、再度お試しください。")
else: else:
await message.channel.send("番号がおかしいようです。") await message.channel.send("# :dizzy_face: 番号がおかしいようです。")
else: else:
await message.channel.send("指定された番号は不正です。") await message.channel.send("# :dizzy_face: 指定された番号は不正です。")
elif msg_split[0] == "/stop": elif msg_split[0] == "/stop":
stop = self.stop(user_id=message.author.id) stop = self.stop(user_id=message.author.id)
if stop["result"] == "unused": if stop["result"] == "unused":
await message.channel.send("使用されていないようです...") await message.channel.send("# :shaking_face: 使用されていないようです...")
elif stop["result"] == "ok": elif stop["result"] == "ok":
await message.channel.send(f"PC番号 {stop["pc_number"]} の使用が終了されました。") await message.channel.send(f":white_check_mark: PC番号 {stop["pc_number"]} の使用が終了されました。")
await self.get_channel(bot_config["log_channel_id"]).send(f'{stop["name"]} さんがPC {stop["pc_number"]} の使用を終了しました') await self.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':negative_squared_cross_mark: {stop["name"]} さんがPC {stop["pc_number"]} の使用を終了しました')
elif message.channel.id == bot_config["config_channel_id"]: elif message.channel.id == dislocker.server_config["bot"]["config_channel_id"]:
msg_split = message.content.split() msg_split = message.content.split()
if msg_split[0] == "/register": if msg_split[0] == "/register":
if len(msg_split) <= 3: print(len(msg_split))
await message.channel.send("名前、Discordのユーザー名、DiscordのユーザーIDのいずれかが入力されていません。") if len(msg_split) == 1:
register = self.user_register(name=message.author.display_name, discord_user_name=message.author.name, discord_user_id=message.author.id)
print(register)
if register["result"] == "ok":
await message.channel.send(f"# :white_check_mark: ユーザー情報が登録されました。\n>>> ユーザー名:{message.author.display_name}")
elif register["result"] == "already_exists":
await message.channel.send("# :no_entry: 登録できませんでした。\nもう登録されている可能性があります。")
else:
await message.channel.send("# :no_entry: 登録できませんでした。\n内部エラーが発生しています。")
elif len(msg_split) <= 3:
await message.channel.send("# :japanese_goblin: 入力内容に不備があります。\n名前、Discordのユーザー名、DiscordのユーザーIDのいずれかが入力されていません。")
elif len(msg_split) == 4: elif len(msg_split) == 4:
if msg_split[3].isdigit(): if msg_split[3].isdigit():
register = self.user_register(name=msg_split[1], discord_user_name=msg_split[2], discord_user_id=msg_split[3]) register = self.user_register(name=msg_split[1], discord_user_name=msg_split[2], discord_user_id=msg_split[3])
if register["result"] == "ok": if register["result"] == "ok":
await message.channel.send(f"登録が完了しました。\n名前 | {msg_split[1]}\nDiscordのユーザー名 | {msg_split[2]}\nDiscordのユーザーID | {msg_split[3]}") await message.channel.send(f"# :white_check_mark: 登録が完了しました。\n>>> # 名前 | {msg_split[1]}\n# Discordのユーザー名 | {msg_split[2]}\n# DiscordのユーザーID | {msg_split[3]}")
elif register["result"] == "already_exists": elif register["result"] == "already_exists":
await message.channel.send("そのDiscordアカウントはすでに登録されています。") await message.channel.send("# :skull_crossbones: 登録できませんでした。\nそのDiscordアカウントはすでに登録されています。")
else: else:
await message.channel.send("DiscordのユーザーIDが不正です。") await message.channel.send("# :skull_crossbones: 登録できませんでした。\nDiscordのユーザーIDが不正です。")
else: else:
await message.channel.send("なんでかわからんけど不正です。") await message.channel.send("# :skull_crossbones: 登録できませんでした。\n内部エラーが発生しています。")
elif msg_split[0] == "/export":
export = self.report_export()
if export["result"] == "ok":
await message.channel.send("# :page_facing_up: 使用履歴のレポートです。", file=discord.File(export["file_path"]))
pass
elif export["result"] == "export_error":
await message.channel.send("# :volcano: エクスポートに失敗しました。")
elif msg_split[0] == "/fstop":
if len(msg_split) == 1:
await message.channel.send("# :warning: 登録を解除できませんでした。\n使用を停止したいPC番号を指定してください。\n-# /fstop PC番号")
elif len(msg_split) == 2:
if msg_split[1].isdigit():
fstop = self.force_stop(pc_number=msg_split[1], bot_about="管理者による強制停止。")
if fstop["result"] == "ok":
await message.channel.send(f"# :white_check_mark: PC番号 {msg_split[1]} の使用登録を解除しました。")
elif fstop["result"] == "not_used":
await message.channel.send("# :exploding_head: 登録を解除できませんでした。\nPCは使用されていないようです...")
else:
await message.channel.send("# :x: 登録を解除できませんでした。\n内部エラーが発生しています。")
else:
await message.channel.send("# :warning: 登録を解除できませんでした。\nPC番号を認識できません。\n-# 半角数字で入力してください。")
else:
await message.channel.send("# warning: 登録を解除できませんでした。\構文が間違っています。\n-# /fstop PC番号")
elif msg_split[0] == "/pcregister":
if len(msg_split) == 1:
await message.channel.send("# :warning: PCを登録できませんでした。\n登録したいPC番号を指定してください。\n-# 半角数字で入力してください。")
elif len(msg_split) == 2:
if msg_split[1].isdigit():
pc_register = self.pc_register(pc_number=msg_split[1])
if pc_register["result"] == "ok":
await message.channel.send(f"# :white_check_mark: PCを登録しました。\n>>> # PC番号 | {msg_split[1]}")
elif pc_register["result"] == "already_exists":
await message.channel.send(f":x: PCを登録できませんでした。\nその番号のPCは既に存在します。")
else:
await message.channel.send("# :x: PCを登録できませんでした。\n内部エラーが発生しています。")
else:
await message.channel.send("# :warning: PCを登録できませんでした。\nPC番号を認識できません。\n-# 半角数字で入力してください。")
else:
await message.channel.send("# :warning: PCを登録できませんでした。\n構文が間違っています。\n-# /pcregister PC番号")
elif msg_split[0] == "/registerbutton":
pc_button_view = View(timeout=None)
for i in range(1, 11):
pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{i}", custom_id=f"pcregister_{i}")
pc_button_view.add_item(pc_register_button)
await self.get_channel(dislocker.server_config["bot"]["config_public_channel_id"]).send(f'# :index_pointing_at_the_viewer: 使いたいPCの番号を選んでください', view=pc_button_view)
elif msg_split[0] == "/stopbutton":
stop_button_view = View(timeout=None)
stop_button = discord.ui.Button(style=discord.ButtonStyle.danger, label="PCの使用を停止", custom_id="stop")
stop_button_view.add_item(stop_button)
await self.get_channel(dislocker.server_config["bot"]["config_public_channel_id"]).send(f'# :index_pointing_at_the_viewer: 使用を停止しますか?', view=stop_button_view)
elif msg_split[0] == "/userbutton":
user_register_button_view = View(timeout=None)
user_register_button = discord.ui.Button(style=discord.ButtonStyle.green, label="ユーザー登録", custom_id="user_register")
user_register_button_view.add_item(user_register_button)
await self.get_channel(dislocker.server_config["bot"]["config_public_channel_id"]).send(f'# :index_pointing_at_the_viewer: ユーザー登録はお済ですか?', view=user_register_button_view)
elif message.channel.id == dislocker.server_config["bot"]["config_public_channel_id"]:
msg_split = message.content.split()
if msg_split[0] == "/register":
print(len(msg_split))
if len(msg_split) == 1:
register = self.user_register(name=message.author.display_name, discord_user_name=message.author.name, discord_user_id=message.author.id)
print(register)
if register["result"] == "ok":
await message.channel.send(f"# :white_check_mark: ユーザー情報が登録されました。\nユーザー名:{message.author.display_name}")
elif register["result"] == "already_exists":
await message.channel.send("# :skull_crossbones: 登録できませんでした。\nそのDiscordアカウントはすでに登録されています。")
else:
await message.channel.send("# :skull_crossbones: 登録できませんでした。\n内部エラーが発生しています。")
else:
await message.channel.send("# :skull_crossbones: 登録できませんでした。\n\n-# もしかして...\n-# 手動でメンバーを登録したいですか?\n-# もしそうなら、このチャンネルにはその権限がありません。\n-# そのチャンネルに移動してから、もう一度試してみてください!")
async def on_button(self, interaction: Interaction):
custom_id = interaction.data["custom_id"]
custom_id_split = custom_id.split("_")
print(custom_id, custom_id_split[0])
if custom_id_split[0] == "pcregister":
device_register_view = View(timeout=15)
pc_number = custom_id_split[1]
print(custom_id_split)
for i in range(1, 11):
device_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{i}", custom_id=f"deviceregister_{str(pc_number)}_{i}")
device_register_view.add_item(device_register_button)
await interaction.response.send_message(f"# :keyboard: デバイス番号を選んでください!\n>>> # PC番号 | {str(pc_number)}", view=device_register_view, ephemeral=True)
elif custom_id_split[0] == "deviceregister":
pc_number = custom_id_split[1]
device_number = custom_id_split[2]
reason_register_view = View(timeout=15)
reason_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="使用目的を入力する", custom_id=f"reasonregister_{str(pc_number)}_{str(device_number)}")
reason_register_view.add_item(reason_button)
await interaction.response.send_message(f"# :regional_indicator_q: 使用目的を書いてください!\n>>> # PC番号 | {str(pc_number)}\n# デバイス番号 | {str(device_number)}", view=reason_register_view, ephemeral=True)
elif custom_id_split[0] == "reasonregister":
pc_number = custom_id_split[1]
device_number = custom_id_split[2]
reason_input_form = Reason(title="Dislocker | 登録", pc_number=str(pc_number), device_number=str(device_number))
await interaction.response.send_modal(reason_input_form)
elif custom_id_split[0] == "stop":
print("STOP running")
pc_stop = self.stop(user_id=interaction.user.id)
print(pc_stop)
stop_view = View(timeout=15)
if pc_stop["result"] == "unused":
await interaction.response.send_message("# :shaking_face: 使用されていないようです...", ephemeral=True)
elif pc_stop["result"] == "user_data_not_found":
await interaction.response.send_message("# :dizzy_face: ユーザーとして登録されていないようです。\n最初にサーバーで登録を行ってください。", ephemeral=True)
elif pc_stop["result"] == "ok":
await interaction.response.send_message(f":white_check_mark: PC番号 {pc_stop["pc_number"]} の使用が終了されました。", ephemeral=True)
await self.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':negative_squared_cross_mark: {pc_stop["name"]} さんがPC {pc_stop["pc_number"]} の使用を終了しました。')
else:
await interaction.response.send_message("# :skull_crossbones: 停止できませんでした。\n内部エラーが発生しています。", ephemeral=True)
elif custom_id_split[0] == "user" and custom_id_split[1] == "register":
print("User Register RUnning")
user_register = self.user_register(name=interaction.user.display_name, discord_user_name=interaction.user.name, discord_user_id=interaction.user.id)
if user_register["result"] == "ok":
await interaction.response.send_message(f"# :white_check_mark: ユーザー情報が登録されました。\n>>> ユーザー名:{interaction.user.display_name}", ephemeral=True)
elif user_register["result"] == "already_exists":
await interaction.response.send_message("# :no_entry: 登録できませんでした。\nもう登録されている可能性があります。", ephemeral=True)
else:
await interaction.response.send_message("# :no_entry: 登録できませんでした。\n内部エラーが発生しています。", ephemeral=True)
class Monitor():
def __init__(self, **kwargs) -> None:
self.search_frequency = kwargs["search_frequency"]
self.allowable_time = kwargs["allowable_time"]
self.fstop_time = kwargs["fstop_time"]
self.init_wait_time = 10
def start(self, **kwargs):
search_thread = threading.Thread(target=self.search)
search_thread.start()
config_dir_path = "./config/" def search(self):
db_config_path = config_dir_path + "db.json" try:
if not os.path.isfile(db_config_path): time.sleep(self.init_wait_time)
if not os.path.isdir(config_dir_path): while True:
os.mkdir(config_dir_path) cursor = dislocker.db.cursor()
cursor.execute("SELECT * FROM pc_list WHERE password_hash IS NOT NULL")
pc_list = cursor.fetchall()
current_datetime = datetime.now()
fstop_time = self.fstop_time
if current_datetime.time().strftime("%H:%M:%S") == fstop_time:
for i in dislocker.pc_list:
stop = bot.force_stop(pc_number=i, bot_about="使用停止忘れによるBotによる強制停止。")
result = {"result": "FSTOP"}
else:
if pc_list:
if len(pc_list) == 1:
user_id = pc_list[0][1]
cursor.execute("SELECT * FROM pc_usage_history WHERE member_id= %s AND end_use_time IS NULL ORDER BY id DESC LIMIT 1", (user_id,))
pc_usage = cursor.fetchall()
print(pc_usage)
start_time = pc_usage[0][4]
print(start_time)
print(type(start_time))
time_difference = current_datetime - start_time
print(current_datetime, start_time)
print(time_difference.seconds, timedelta(seconds=self.allowable_time).seconds)
if time_difference.seconds >= timedelta(seconds=self.allowable_time).seconds:
cursor.execute("SELECT * FROM club_member WHERE id = %s", (user_id,))
user_info = cursor.fetchall()
stop = bot.stop(user_id=user_info[0][3], bot_about="パスワードのタイムアウトでBotによる強制停止。")
bot.timeout_notify(pc_number=pc_list[0][0], discord_display_name=user_info[0][1])
result = {"result": "STOP", "details": str(pc_usage)}
else:
result = {"result": "BUT SAFE", "details": str(pc_usage)}
db_config = {
"host": "localhost",
"db": "dislocker",
"username": "user",
"password": "example_pass",
"port": "5432"
}
with open(db_config_path, "w") as w:
json.dump(db_config, w, indent=4)
elif os.path.isfile(db_config_path):
with open(db_config_path, "r") as r:
db_config = json.load(r)
bot_config_path = config_dir_path + "bot.json" elif len(pc_list) >= 2:
if not os.path.isfile(bot_config_path): for i in pc_list:
if not os.path.isdir(config_dir_path): print(i)
os.mkdir(config_dir_path) user_id = i[1]
cursor.execute("SELECT * FROM pc_usage_history WHERE member_id= %s AND end_use_time IS NULL ORDER BY id DESC LIMIT 1", (user_id,))
pc_usage = cursor.fetchall()
print(pc_usage)
start_time = pc_usage[0][4]
print(start_time)
print(type(start_time))
time_difference = current_datetime - start_time
print(time_difference.seconds, timedelta(seconds=self.allowable_time).seconds)
if time_difference.seconds >= timedelta(seconds=self.allowable_time).seconds:
cursor.execute("SELECT * FROM club_member WHERE id = %s", (user_id,))
user_info = cursor.fetchall()
stop = bot.stop(user_id=user_info[0][3], bot_about="タイムアウトでBotによる強制停止。")
bot.timeout_notify(pc_number=i[0], discord_display_name=user_info[0][1])
result = {"result": "STOP", "details": str(pc_usage)}
else:
result = {"result": "BUT SAFE", "details": str(pc_usage)}
bot_config = { else:
"token": "TYPE HERE BOTS TOKEN KEY", result = {"result": "NONE"}
"log_channel_id" : "TYPE HERE CHANNEL ID", else:
"config_channel_id": "TYPE HERE CHANNEL ID" result = {"result": "NONE"}
}
with open(bot_config_path, "w") as w:
json.dump(bot_config, w, indent=4)
elif os.path.isfile(bot_config_path): if result["result"] == "NONE":
with open(bot_config_path, "r") as r: pass
bot_config = json.load(r) else:
print(current_datetime)
print(result["result"])
time.sleep(self.search_frequency)
intents = discord.Intents.default()
intents.message_content = True
bot = Bot(intents=intents) except Exception as error:
bot.db_connect(db_config["host"], db_config["db"], db_config["port"], db_config["username"], db_config["password"]) print("自動停止処理中にエラーが発生しました。\nエラー内容")
bot.run(bot_config["token"]) print(str(error.__class__.__name__))
print(str(error.args))
print(str(error))
result = {"result": "error"}
dislocker.db.rollback()
finally:
cursor.close()
print(result["result"])
return result
class Reason(Modal):
def __init__(self, title: str, pc_number: str, device_number: str, timeout=15) -> None:
super().__init__(title=title, timeout=timeout)
print(pc_number)
print(device_number)
self.reason_input_form = TextInput(label="使用目的を入力してください", style=TextStyle.short, custom_id=f"register_{pc_number}_{device_number}")
self.add_item(self.reason_input_form)
async def on_submit(self, interaction: Interaction) -> None:
custom_id = interaction.data["components"][0]["components"][0]["custom_id"]
print(custom_id)
custom_id_split = custom_id.split("_")
pc_number = custom_id_split[1]
device_number = custom_id_split[2]
register = bot.register(user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, device_number=device_number, detail=self.reason_input_form.value)
print(register["result"])
if register["result"] == "ok":
await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["password"]}\n## PC番号 | {pc_number}\n## デバイス番号 | {device_number}\n## 使用目的 | {self.reason_input_form.value}", ephemeral=True)
await bot.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':white_check_mark: {register["name"]} さんがPC {pc_number} の使用を開始しました。')
elif register["result"] == "pc_already_in_use_by_you":
await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {register["pc_number"]}\n# デバイス番号 | {register["device_number"]}\n# 使用開始時刻 | {register["start_time"]}\n# 使用目的 | {register["detail"]}", ephemeral=True)
elif register["result"] == "pc_already_in_use_by_other":
await interaction.response.send_message(f"# :man_gesturing_no: そのPCは他のメンバーによって使用されています。\n別のPC番号を指定して、再度お試しください。", ephemeral=True)
elif register["result"] == "user_data_not_found":
await interaction.response.send_message("# :dizzy_face: ユーザーとして登録されていないようです。\n最初にサーバーで登録を行ってください。", ephemeral=True)
else:
await interaction.response.send_message("# :skull_crossbones: 登録できませんでした。\n内部エラーが発生しています。", ephemeral=True)
dislocker = DL()
if dislocker.init_result == "ok":
print("Botを起動します...")
intents = discord.Intents.default()
intents.message_content = True
bot = Bot(intents=intents)
monitor = Monitor(search_frequency=dislocker.server_config["bot"]["monitor"]["search_frequency"], allowable_time=dislocker.server_config["bot"]["monitor"]["allowable_time"], fstop_time=dislocker.server_config["bot"]["monitor"]["fstop_time"])
monitor.start()
bot.run(dislocker.server_config['bot']['token'])
else:
pass

View file

@ -4,56 +4,122 @@ import json
from flask import Flask, request, jsonify, render_template from flask import Flask, request, jsonify, render_template
config_dir_path = "./config/" config_dir_path = "./config/"
db_config_path = config_dir_path + "db.json" server_config_path = config_dir_path + "server.json"
if not os.path.isfile(db_config_path): if not os.path.isfile(server_config_path):
if not os.path.isdir(config_dir_path): if not os.path.isdir(config_dir_path):
os.mkdir(config_dir_path) os.mkdir(config_dir_path)
db_config = { server_config = {
"host": "localhost", "db": {
"db": "dislocker", "host": "localhost",
"username": "user", "port": "5432",
"password": "example_pass", "db_name": "dislocker",
"port": "5432" "username": "user",
"password": "password"
},
"bot": {
"token": "TYPE HERE BOTS TOKEN KEY",
"activity": {
"name": "Dislocker",
"details": "ロック中...",
"type": "playing",
"state": "ロック中..."
},
"log_channel_id" : "TYPE HERE CHANNEL ID (YOU MUST USE INT !!!!)",
"config_channel_id": "TYPE HERE CHANNEL ID (YOU MUST USE INT !!!!)",
"config_public_channel_id": "TYPE HERE CHANNEL ID (YOU MUST USE INT !!!!)"
}
} }
with open(db_config_path, "w") as w: with open(server_config_path, "w") as w:
json.dump(db_config, w, indent=4) json.dump(server_config, w, indent=4)
elif os.path.isfile(db_config_path): elif os.path.isfile(server_config_path):
with open(db_config_path, "r") as r: with open(server_config_path, "r") as r:
db_config = json.load(r) server_config = json.load(r)
class Auth(): class Auth():
def __init__(self, host, db, port, user, password): def __init__(self, host, db, port, user, password):
self.db = psycopg2.connect(f"host={host} dbname={db} port={port} user={user} password={password}") self.db = psycopg2.connect(f"host={host} dbname={db} port={port} user={user} password={password}")
def check(self, pc_number, password): def check(self, pc_number, password):
cursor = self.db.cursor() try:
cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s AND password_hash = %s", (pc_number, password)) cursor = self.db.cursor()
pc_info = cursor.fetchall() cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s AND password_hash = %s", (pc_number, password))
if not pc_info: pc_info = cursor.fetchall()
return 1 if not pc_info:
else: return 1
return 0 else:
return 0
finally:
cursor.close()
def delete(self, pc_number): def delete(self, pc_number):
cursor = self.db.cursor() try:
cursor.execute("UPDATE pc_list SET password_hash = NULL WHERE pc_number = %s", (pc_number,)) cursor = self.db.cursor()
self.db.commit() cursor.execute("UPDATE pc_list SET password_hash = NULL WHERE pc_number = %s", (pc_number,))
self.db.commit()
finally:
cursor.close()
def stop(self, **kwargs):
try:
pc_number = int(kwargs["pc_number"])
cursor = self.db.cursor()
cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s", (pc_number,))
pc_list_record = cursor.fetchall()
if not pc_list_record[0][1] == None:
cursor.execute("UPDATE pc_list SET using_user_id = NULL WHERE pc_number = %s", (pc_number,))
if not pc_list_record[0][2] == None:
cursor.execute("UPDATE pc_list SET password_hash = NULL WHERE pc_number = %s", (pc_number,))
cursor.execute("SELECT * FROM pc_usage_history WHERE member_id = %s AND pc_number = %s ORDER BY id DESC LIMIT 1", (pc_list_record[0][1], pc_number))
pc_usage_history_record = cursor.fetchall()
cursor.execute("UPDATE pc_usage_history SET end_use_time = current_timestamp WHERE id = %s", (pc_usage_history_record[0][0],))
self.db.commit()
result = {"result": "ok"}
else:
result = {"result": "not_used"}
except:
result = {"result": "error"}
finally:
cursor.close()
return result
app = Flask(__name__, static_folder="./resource/") app = Flask(__name__, static_folder="./resource/")
auth = Auth(db_config["host"], db_config["db"], db_config["port"], db_config["username"], db_config["password"]) auth = Auth(server_config["db"]["host"], server_config["db"]["db_name"], server_config["db"]["port"], server_config["db"]["username"], server_config["db"]["password"])
@app.route('/verify', methods=['POST']) @app.route('/verify', methods=['POST'])
def verify(): def verify():
pc_number = int(request.json.get('pc_number')) pc_number = int(request.json.get('pc_number'))
password = request.json.get('password') password = request.json.get('password')
print(str(pc_number) + "の認証処理を開始...")
if auth.check(pc_number, password) == 0: if auth.check(pc_number, password) == 0:
auth.delete(pc_number) auth.delete(pc_number)
print(str(pc_number) + "の認証処理は成功しました.")
return jsonify({'message': 'ok'}), 200 return jsonify({'message': 'ok'}), 200
else: else:
print(str(pc_number) + "の認証処理は失敗しました.")
return jsonify({'message': 'damedesu'}), 401 return jsonify({'message': 'damedesu'}), 401
@app.route('/stop', methods=['POST'])
def stop():
pc_number = int(request.json.get('pc_number'))
print(str(pc_number) + "の使用停止処理を開始...")
pc_stop = auth.stop(pc_number=pc_number)
if pc_stop["result"] == "ok":
print(str(pc_number) + "の使用停止処理は成功しました.")
return jsonify({'message': 'ok'}), 200
else:
print(str(pc_number) + "の使用停止処理は失敗しました.")
return jsonify({'message': 'error'}), 500
if __name__ == '__main__': if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000, debug=True) app.run(host="0.0.0.0", port=5000, debug=False)

View file

@ -11,22 +11,78 @@ import string
import random import random
import tkinter import tkinter
import threading import threading
import signal
import sys
import shutil
app_name = "Dislocker"
dislocker_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
os.chdir(dislocker_dir)
resource_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "resource")
config_dir_path = "./config/"
client_config_path = config_dir_path + "client.json"
if not os.path.isfile(client_config_path):
if not os.path.isdir(config_dir_path):
os.mkdir(config_dir_path)
client_config = {
"initial": 1,
"auth_host_url": "http://localhost",
"pc_number": 1,
"master_password_hash": "",
"testing": 0
}
elif os.path.isfile(client_config_path):
with open(client_config_path, "r") as r:
client_config = json.load(r)
def init(**kwargs):
sp_startupinfo = subprocess.STARTUPINFO()
sp_startupinfo.dwFlags = subprocess.STARTF_USESHOWWINDOW
sp_startupinfo.wShowWindow = subprocess.SW_HIDE
task_exist = subprocess.run('tasklist /fi "IMAGENAME eq dislocker_client.exe"', startupinfo=sp_startupinfo, stdout=subprocess.PIPE, text=True)
if 'dislocker_client.exe' in task_exist.stdout:
task_count = task_exist.stdout.count("dislocker_client.exe")
if task_count == 1:
pass
else:
return 1
if client_config["initial"] == 1:
master_password = master_password_gen()
msgbox = tkinter.messagebox.showinfo(title=f"{app_name} | 初回起動を検出", message=f"初回起動のようです。\nマスターパスワードを記録しておいてください。\nこれ以降二度と表示されることはないでしょう。\n\n{master_password["password"]}\n\nまた、認証先サーバーの接続先を指定してください。ロックを解除できなくなります。")
client_config["master_password_hash"] = master_password["password_hash"]
client_config["initial"] = 0
if "pc_number" in kwargs:
client_config["pc_number"] = int(kwargs["pc_number"])
else:
client_config["pc_number"] = 1
with open(client_config_path, "w") as w:
json.dump(client_config, w, indent=4)
return 2
else:
return 0
class App(customtkinter.CTk): class App(customtkinter.CTk):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.title(f"{app_name} | ロック中") self.title(f"{app_name} | ロック中")
self.iconbitmap(default=resource_path + '\\icon\\dislocker.ico')
if client_config["testing"] == 1: if client_config["testing"] == 1:
pass pass
else: else:
self.attributes('-fullscreen', True) self.attributes('-fullscreen', True)
self.attributes('-topmost', True) self.attributes('-topmost', True)
self.block_taskmgr()
self.block_key()
self.frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') self.frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent')
self.frame.grid(row=0, column=0, sticky='nsew') self.frame.grid(row=0, column=0, sticky='nsew')
self.block_taskmgr()
self.block_key()
lock = Lock() lock = Lock()
def exit(self): def exit(self):
@ -34,13 +90,44 @@ class App(customtkinter.CTk):
self.toast() self.toast()
self.destroy() self.destroy()
def delete_appdata(self, **kwargs):
process_name = kwargs["process_name"]
dir_path = kwargs["dir_path"]
if not os.path.exists(dir_path):
print(f"エラー: 指定されたディレクトリ {dir_path} が存在しません。")
return 1
try:
# プロセスの終了
subprocess.run(['taskkill', '/f', '/t', '/im', process_name])
print(f"{process_name} を終了しました。")
# ディレクトリの削除
shutil.rmtree(dir_path)
print(f"{dir_path} を削除しました。")
return 0
except subprocess.CalledProcessError as e:
print(f"プロセス終了エラー: {e}")
return 1
except PermissionError as e:
print(f"権限エラー: {e}")
return 1
except Exception as error:
print("エラーが発生しました。\nエラー内容:")
print(f"エラータイプ: {error.__class__.__name__}")
print(f"エラー引数: {error.args}")
print(f"エラーメッセージ: {str(error)}")
return 1
def block_key(self): def block_key(self):
block_keys = ['ctrl', 'alt', 'windows', 'shift', 'delete'] block_keys = ['ctrl', 'alt', 'windows', 'shift', 'delete']
for i in block_keys: for i in block_keys:
keyboard.block_key(i) keyboard.block_key(i)
def block_taskmgr(self): def block_taskmgr(self):
block = subprocess.run(['reg', 'add', 'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', '/v', 'DisableTaskMgr', '/t', 'REG_DWORD', '/d', '1']) block = subprocess.run(['reg', 'add', 'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', '/v', 'DisableTaskMgr', '/t', 'REG_DWORD', '/d', '1', '/f'])
print(block) print(block)
def unlock_taskmgr(self): def unlock_taskmgr(self):
@ -48,7 +135,6 @@ class App(customtkinter.CTk):
print(unlock) print(unlock)
def toast(self): def toast(self):
resource_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "resource")
success = Notification( success = Notification(
app_id='Dislocker', app_id='Dislocker',
title='ご協力ありがとうございます!', title='ご協力ありがとうございます!',
@ -65,47 +151,87 @@ class App(customtkinter.CTk):
class Lock(customtkinter.CTkToplevel): class Lock(customtkinter.CTkToplevel):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.title(f'{app_name} | PC番号 {client_config["pc_number"]} | ロックされています') if client_config["testing"] == 1:
self.geometry('400x200') self.title(f'{app_name} | PC番号 {client_config["pc_number"]} | テストモード')
else:
self.title(f'{app_name} | PC番号 {client_config["pc_number"]} | ロックされています')
self.iconbitmap(default=resource_path + '\\icon\\dislocker.ico')
self.window_width = 600
self.window_height = 320
self.screen_width = self.winfo_screenwidth()
self.screen_height = self.winfo_screenheight()
self.center_x = int(self.screen_width/2 - self.window_width/2)
self.center_y = int(self.screen_height/2 - self.window_height/2)
self.geometry(f"{str(self.window_width)}x{str(self.window_height)}+{str(self.center_x)}+{str(self.center_y)}")
self.resizable(height=False, width=False) self.resizable(height=False, width=False)
self.attributes('-topmost', True) self.attributes('-topmost', True)
self.grab_set() self.grab_set()
self.lift() self.lift()
self.protocol("WM_DELETE_WINDOW", self.handler_close) self.protocol("WM_DELETE_WINDOW", self.handler_close)
self.title_font = customtkinter.CTkFont(family="meiryo", size=24, weight="bold") self.emoji_font = customtkinter.CTkFont(family="Segoe UI Emoji", size=32)
self.general_font = customtkinter.CTkFont(family="meiryo", size=14) self.title_font = customtkinter.CTkFont(family="meiryo", size=32, weight="bold")
self.pc_number_font = customtkinter.CTkFont(family="meiryo", size=64, weight="bold")
self.title_small_font = customtkinter.CTkFont(family="meiryo", size=16)
self.general_font = customtkinter.CTkFont(family="meiryo", size=18)
self.general_small_font = customtkinter.CTkFont(family="meiryo", size=12) self.general_small_font = customtkinter.CTkFont(family="meiryo", size=12)
self.textbox_font = customtkinter.CTkFont(family="meiryo", size=14)
self.button_font = customtkinter.CTkFont(family="meiryo", size=14)
self.msg_title_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') self.msg_title_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent')
self.msg_title_frame.grid(row=0, column=0, sticky="nsew") self.msg_title_frame.grid(row=0, column=0, padx=10, pady=10, sticky="nsew")
self.msg_title_1 = customtkinter.CTkLabel(self.msg_title_frame, text='ちょっと待って!!', font=self.title_font, anchor=tkinter.W) self.icon_title_1 = customtkinter.CTkLabel(self.msg_title_frame, text='😎', font=self.emoji_font, justify="left")
self.msg_title_1.grid(row=0, column=0, padx=10, pady=10) self.icon_title_1.grid(row=0, column=0, padx=10, sticky="w")
self.msg_title_1 = customtkinter.CTkLabel(self.msg_title_frame, text=f'ちょっと待って!! PC番号 | {client_config["pc_number"]}', font=self.title_font, justify="left")
self.msg_title_1.grid(row=0, column=1, padx=10, sticky="w")
self.msg_title_2 = customtkinter.CTkLabel(self.msg_title_frame, text="本当にあなたですか?", font=self.title_small_font, justify="left")
self.msg_title_2.grid(row=1, column=1, padx=10, sticky="w")
self.msg_subtitle_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') self.msg_subtitle_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent')
self.msg_subtitle_frame.grid(row=1, column=0, sticky="nsew") self.msg_subtitle_frame.grid(row=1, column=0, padx=10, pady=10, sticky="nsew")
self.msg_subtitle_frame.grid_columnconfigure(0, weight=1)
self.msg_subtitle_1 = customtkinter.CTkLabel(self.msg_subtitle_frame, text='サインインするには、パスワードが必要です。', font=self.general_font, anchor=tkinter.W) self.msg_subtitle_1 = customtkinter.CTkLabel(self.msg_subtitle_frame, text='サインインするには、Discordのダイレクトメッセージに送信された\nパスワードを入力してください。', font=self.general_font, justify="left")
self.msg_subtitle_1.grid(row=0, column=0, padx=10, pady=10) self.msg_subtitle_1.grid(row=0, column=0, padx=10, sticky="ew")
self.msg_subtitle_2 = customtkinter.CTkLabel(self.msg_subtitle_frame, text='※ パスワードの有効期限は23:59までです。', font=self.general_small_font, justify="left")
self.msg_subtitle_2.grid(row=1, column=0, padx=10, sticky="w")
self.input_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') self.input_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent')
self.input_frame.grid(row=2, column=0, sticky="nsew") self.input_frame.grid(row=2, column=0, padx=10, pady=10, sticky="nsew")
self.input_frame.columnconfigure(0, weight=1)
self.password_entry = customtkinter.CTkEntry(self.input_frame, placeholder_text='パスワード', width=380, show='*', font=self.general_small_font) self.password_entry = customtkinter.CTkEntry(self.input_frame, placeholder_text='パスワード', show='*', font=self.textbox_font)
self.password_entry.grid(row=0, column=0, padx=10, pady=10) self.password_entry.grid(row=0, column=0, padx=10, sticky="ew")
self.password_entry.bind("<Return>", self.auth_start_ev)
self.button_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') self.button_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent')
self.button_frame.grid(row=3, column=0, sticky="nsew") self.button_frame.grid(row=3, column=0, padx=10, pady=10, sticky="nsew")
self.button_frame.columnconfigure(0, weight=3)
self.button_frame.columnconfigure(1, weight=1)
self.button_frame.columnconfigure(2, weight=1)
self.signin_button = customtkinter.CTkButton(self.button_frame, text='サインイン', command=self.auth, font=self.general_font) self.signin_button = customtkinter.CTkButton(self.button_frame, text='サインイン', command=self.auth_start, font=self.button_font)
self.signin_button.grid(row=0, column=1, padx=10, pady=10) self.signin_button.grid(row=0, column=2, padx=10, sticky="e")
self.signin_button.pack(padx=10, anchor=tkinter.E)
self.signout_button = customtkinter.CTkButton(self.button_frame, text='サインアウト', command=self.signout, font=self.button_font)
self.signout_button.grid(row=0, column=1, padx=10, sticky="e")
self.help_button = customtkinter.CTkButton(self.button_frame, text='ヘルプ', command=self.help_dummy, font=self.button_font)
self.help_button.grid(row=0, column=0, padx=10, sticky="w")
self.keyboard_listener_thread = threading.Thread(target=self.keyboard_listener) self.keyboard_listener_thread = threading.Thread(target=self.keyboard_listener)
self.keyboard_listener_thread.daemon = True self.keyboard_listener_thread.daemon = True
self.keyboard_listener_thread.start() self.keyboard_listener_thread.start()
def help_wakeup(self):
help = Help()
def keyboard_listener(self): def keyboard_listener(self):
keyboard.add_hotkey('ctrl+shift+q', self.exit) keyboard.add_hotkey('ctrl+shift+q', self.exit)
@ -113,7 +239,46 @@ class Lock(customtkinter.CTkToplevel):
hashed = hashlib.md5(source.encode()) hashed = hashlib.md5(source.encode())
return hashed.hexdigest() return hashed.hexdigest()
def auth_start(self):
auth_thread = threading.Thread(target=self.auth)
auth_thread.daemon = True
auth_thread.start()
def auth_start_ev(self, event):
auth_thread = threading.Thread(target=self.auth)
auth_thread.daemon = True
auth_thread.start()
def button_disable(self):
self.help_button.configure(state="disabled", fg_color="gray")
self.signin_button.configure(state="disabled", fg_color="gray")
self.signout_button.configure(state="disabled", fg_color="gray")
def button_enable(self):
self.help_button.configure(state="normal", fg_color="#3c8dd0")
self.signin_button.configure(state="normal", fg_color="#3c8dd0")
self.signout_button.configure(state="normal", fg_color="#3c8dd0")
def auth(self): def auth(self):
self.button_disable()
password = str(self.password_entry.get())
if len(password) == 10:
print("マスターパスワードで認証を試行します。")
master_password_hash = self.hash_genarate(str(self.password_entry.get()))
if client_config["master_password_hash"] == master_password_hash:
print("マスターパスワードで認証しました。")
self.exit()
else:
print("マスターパスワードで認証できませんでした。")
self.withdraw()
msgbox = tkinter.messagebox.showinfo(title=f"{app_name} | 誤ったパスワード", message=f"パスワードが間違っています!")
self.msg_subtitle_1.configure(text='パスワードが間違っています! ')
self.button_enable()
self.deiconify()
print("認証サーバーにアクセスします。")
auth_url = client_config["auth_host_url"] + "/verify" auth_url = client_config["auth_host_url"] + "/verify"
auth_json = { auth_json = {
"pc_number": int(client_config["pc_number"]), "pc_number": int(client_config["pc_number"]),
@ -121,29 +286,113 @@ class Lock(customtkinter.CTkToplevel):
} }
try: try:
responce = requests.post(auth_url, json=auth_json) responce = requests.post(auth_url, json=auth_json)
if responce.status_code == 200:
print("認証サーバー経由で認証しました。")
self.exit()
else:
print("認証サーバー経由での認証に失敗しました。")
self.withdraw()
msgbox = tkinter.messagebox.showinfo(title=f"{app_name} | 誤ったパスワード", message=f"パスワードが間違っています!")
self.msg_subtitle_1.configure(text='パスワードが間違っています! ')
self.button_enable()
self.deiconify()
except: except:
print("認証サーバーにアクセスできません。マスターパスワードで認証を試行します。")
master_password_hash = self.hash_genarate(str(self.password_entry.get())) master_password_hash = self.hash_genarate(str(self.password_entry.get()))
if client_config["master_password_hash"] == master_password_hash: if client_config["master_password_hash"] == master_password_hash:
print("マスターパスワードで認証しました。")
self.exit() self.exit()
else: else:
self.msg_subtitle_1.configure(text='サインインするには、パスワードが必要です。\nネットワークエラーが発生しています。\n続行するには、マスターパスワードを入力して下さい。') print("マスターパスワードで認証できませんでした。")
self.withdraw()
msgbox = tkinter.messagebox.showinfo(title=f"{app_name} | ネットワークエラー", message=f"認証サーバーにアクセスできませんでした。\n続行するには、マスターパスワードを入力してください。")
self.msg_subtitle_1.configure(text='ネットワークエラーが発生しています。\n続行するには、マスターパスワードを入力して下さい。 ')
self.button_enable()
self.deiconify()
def signout(self):
app.unlock_taskmgr()
self.destroy()
signout_command = subprocess.run(['shutdown', '/l', '/f'])
print(signout_command)
if responce.status_code == 200:
self.exit()
else:
master_password_hash = self.hash_genarate(str(self.password_entry.get()))
if client_config["master_password_hash"] == master_password_hash:
self.exit()
else:
self.msg_subtitle_1.configure(text='サインインするには、パスワードが必要です。\nパスワードが違います!')
def handler_close(self): def handler_close(self):
pass pass
def help_dummy(self):
self.withdraw()
msgbox = tkinter.messagebox.showinfo(title=f"{app_name} | 未実装", message=f"ヘルプページは製作途中です。\nDiscordサーバーの指示に従って、認証を進めてください。")
self.deiconify()
def exit(self): def exit(self):
self.destroy() self.destroy()
app.exit() app.exit()
class Help(customtkinter.CTkToplevel):
def __init__(self):
super().__init__()
if client_config["testing"] == 1:
self.title(f'{app_name} | ヘルプ | テストモード')
else:
self.title(f'{app_name} | ヘルプ')
self.iconbitmap(default=resource_path + '\\icon\\dislocker.ico')
self.geometry("600x400")
self.resizable(height=False, width=False)
self.attributes('-topmost', True)
self.grab_set()
self.lift()
self.protocol("WM_DELETE_WINDOW", self.handler_close)
msgbox = tkinter.messagebox.showinfo(title=f"{app_name} | 未実装", message=f"ヘルプページは製作途中です。\nDiscordサーバーの指示に従って、認証を進めてください。")
self.destroy()
def handler_close(self):
self.destroy()
class Monitor():
def __init__(self) -> None:
pass
def start(self):
monitor_thread = threading.Thread(target=self.run)
monitor_thread.start()
def signal_handler(self):
print("停止処理を実行。")
appdata_local = os.path.expandvars("%LOCALAPPDATA%")
appdata_roaming = os.path.expandvars("%APPDATA%")
epic_del = app.delete_appdata(process_name="EpicGamesLauncher.exe", dir_path=f"{appdata_local}\\EpicGamesLauncher\\Saved")
chrome_del = app.delete_appdata(process_name="chrome.exe", dir_path=f"{appdata_local}\\Google\\Chrome\\User Data")
discord_del = app.delete_appdata(process_name="discord.exe", dir_path=f"{appdata_roaming}\\discord")
steam_del = app.delete_appdata(process_name="steam.exe", dir_path=f"{appdata_local}\\Steam")
ea_del = app.delete_appdata(process_name="EADesktop.exe", dir_path=f"{appdata_local}\\Electronic Arts")
riot_del = app.delete_appdata(process_name="RiotClientServices.exe", dir_path=f"{appdata_local}\\Riot Games\\Riot Client")
stop_url = client_config["auth_host_url"] + "/stop"
stop_json = {
"pc_number": int(client_config["pc_number"])
}
try:
responce = requests.post(stop_url, json=stop_json)
if responce.status_code == 200:
print("停止処理は成功しました。")
else:
print("内部エラーにより停止処理に失敗しました。")
result_msgbox = tkinter.messagebox.showwarning(title=f"{app_name} | エラー", message=f"内部エラーにより停止処理に失敗しました。\nDiscordサーバーの指示に従って、停止処理を自身で行ってください。")
except:
print("ネットワークエラーにより停止処理に失敗しました。")
result_msgbox = tkinter.messagebox.showwarning(title=f"{app_name} | エラー", message=f"ネットワークエラーにより停止処理に失敗しました。\nDiscordサーバーの指示に従って、停止処理を自身で行ってください。")
finally:
self.shutdown()
def shutdown(self):
shutdown_command = subprocess.run(['shutdown', '/s', '/t', '1'])
def run(self):
signal.signal(signal.SIGTERM, self.signal_handler)
def master_password_gen(): def master_password_gen():
numbers = string.digits # (1) numbers = string.digits # (1)
password = ''.join(random.choice(numbers) for _ in range(10)) # (2) password = ''.join(random.choice(numbers) for _ in range(10)) # (2)
@ -151,39 +400,37 @@ def master_password_gen():
result = {"password": password, "password_hash": password_hash} result = {"password": password, "password_hash": password_hash}
return result return result
app_name = "Dislocker"
config_dir_path = "./config/" if __name__ == '__main__':
client_config_path = config_dir_path + "client.json" args = sys.argv
if not os.path.isfile(client_config_path): if len(args) >= 2:
if not os.path.isdir(config_dir_path): if args[1] == "stop":
os.mkdir(config_dir_path) init_result = init()
if init_result == 1:
client_config = { warning_msgbox = tkinter.messagebox.showwarning(title=f"{app_name} | 多重起動エラー", message=f"もう終了処理を行っています。\nPCがシャットダウンするまで、もう少しお待ちください。")
"initial": 1, elif init_result == 2:
"auth_host_url": "http://localhost", pass
"pc_number": 1, else:
"master_password_hash": "", app = App()
"testing": 0 monitor = Monitor()
monitor.signal_handler()
} elif args[1] == "setup":
with open(client_config_path, "w") as w: init_result = init(pc_number=args[2])
json.dump(client_config, w, indent=4) if init_result == 1:
elif os.path.isfile(client_config_path): warning_msgbox = tkinter.messagebox.showwarning(title=f"{app_name} | 多重起動エラー", message=f"すでに {app_name} は実行されています。\n正常に起動しない場合は、既に起動しているプロセスを終了してから、もう一度起動してみてください。")
with open(client_config_path, "r") as r: elif init_result == 2:
client_config = json.load(r) pass
else:
pass
if client_config["initial"] == 1: else:
master_password = master_password_gen() print("引数エラー。")
msgbox = tkinter.messagebox.showinfo(title=f"{app_name} | 初回起動を検出", message=f"初回起動のようです。\nマスターパスワードを記録しておいてください。\nこれ以降二度と表示されることはないでしょう。\n\n{master_password["password"]}\n\nまた、認証先サーバーの接続先を指定してください。ロックを解除できなくなります。") else:
client_config["master_password_hash"] = master_password["password_hash"] init_result = init()
client_config["initial"] = 0 if init_result == 1:
with open(client_config_path, "w") as w: warning_msgbox = tkinter.messagebox.showwarning(title=f"{app_name} | 多重起動エラー", message=f"すでに {app_name} は実行されています。\n正常に起動しない場合は、既に起動しているプロセスを終了してから、もう一度起動してみてください。")
json.dump(client_config, w, indent=4) elif init_result == 2:
pass
else: else:
if __name__ == '__main__': app = App()
app = App() app.protocol("WM_DELETE_WINDOW", app.handler_close)
app.protocol("WM_DELETE_WINDOW", app.handler_close) app.mainloop()
app.mainloop()

1
docs/pyinstaller.txt Normal file
View file

@ -0,0 +1 @@
pyinstaller .\dislocker_client.py --noconsole --icon .\resource\icon\dislocker.ico --add-data ".\resource:resource"

View file

@ -5,4 +5,7 @@ CREATE TABLE club_member (id SERIAL NOT NULL, name VARCHAR(30) NOT NULL, discord
CREATE TABLE pc_list (pc_number INTEGER NOT NULL, using_user_id INTEGER, password_hash VARCHAR(32), PRIMARY KEY (pc_number), FOREIGN KEY (using_user_id) REFERENCES club_member(id)); CREATE TABLE pc_list (pc_number INTEGER NOT NULL, using_user_id INTEGER, password_hash VARCHAR(32), PRIMARY KEY (pc_number), FOREIGN KEY (using_user_id) REFERENCES club_member(id));
CREATE TABLE pc_usage_history (id SERIAL NOT NULL, member_id INTEGER NOT NULL, pc_number INTEGER NOT NULL, device_number INTEGER NOT NULL, start_use_time TIMESTAMP NOT NULL, end_use_time TIMESTAMP, use_detail VARCHAR(30), PRIMARY KEY (id), FOREIGN KEY (member_id) REFERENCES club_member(id), FOREIGN KEY (pc_number) REFERENCES pc_list(pc_number)); CREATE TABLE pc_usage_history (id SERIAL NOT NULL, member_id INTEGER NOT NULL, pc_number INTEGER NOT NULL, device_number INTEGER NOT NULL, start_use_time TIMESTAMP NOT NULL, end_use_time TIMESTAMP, use_detail VARCHAR(30), PRIMARY KEY (id), FOREIGN KEY (member_id) REFERENCES club_member(id), FOREIGN KEY (pc_number) REFERENCES pc_list(pc_number));
# PC登録
INSERT INTO pc_list (pc_number) VALUES (PC番号をいれる);
INSERTのとき文字列はシングルクォーテーションで囲む INSERTのとき文字列はシングルクォーテーションで囲む

4
requirements_client.txt Normal file
View file

@ -0,0 +1,4 @@
customtkinter
winotify
keyboard
requests

BIN
resource/icon/dislocker.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

BIN
resource/icon/png/128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
resource/icon/png/16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

BIN
resource/icon/png/256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
resource/icon/png/32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
resource/icon/png/512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

BIN
resource/icon/png/64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

21
script/setup.cmd Normal file
View file

@ -0,0 +1,21 @@
@echo off
set dir=%~dp0
cd %dir%
schtasks /create /tn "Dislocker_Client" /tr "%dir%dislocker_client.exe" /sc onlogon /ru "%USERNAME%" /rl highest /f
if %ERRORLEVEL% == 0 (
echo タスクスケジューラーの設定は完了しました。
)
if %ERRORLEVEL% == 1 (
echo タスクスケジューラーの設定でエラーが発生しました。
)
Cscript "%dir%shortcut.vbs" "%dir%" "%dir%" "dislocker_client.exe" "stop" "シャットダウンと停止処理"
if %ERRORLEVEL% == 0 (
Cscript "%dir%shortcut.vbs" "%USERPROFILE%/Desktop" "%dir%" "dislocker_client.exe" "stop" "シャットダウンと停止処理"
echo ショートカットの作成は完了しました。
)
if %ERRORLEVEL% == 1 (
echo ショートカットの作成でエラーが発生しました。
)
set /P pc_number=PC番号を入力
start %dir%dislocker_client.exe setup %pc_number%
pause

8
script/shortcut.vbs Normal file
View file

@ -0,0 +1,8 @@
Set WshShell = CreateObject("WScript.Shell")
Set args = Wscript.Arguments
Set Shortcut = WshShell.CreateShortcut(args(0) & "\シャットダウンと終了処理.lnk")
Shortcut.TargetPath = args(1) & args(2)
Shortcut.Arguments = args(3)
Shortcut.WorkingDirectory = args(1)
Shortcut.Save
WScript.Quit(0)

40
temp/client_playground.py Normal file
View file

@ -0,0 +1,40 @@
import os
import subprocess
import shutil
def delete_appdata(**kwargs):
process_name = kwargs["process_name"]
dir_path = kwargs["dir_path"]
if not os.path.exists(dir_path):
print(f"エラー: 指定されたディレクトリ {dir_path} が存在しません。")
return 1
try:
# プロセスの終了
subprocess.run(['taskkill', '/f', '/t', '/im', process_name])
print(f"{process_name} を終了しました。")
# ディレクトリの削除
shutil.rmtree(dir_path)
print(f"{dir_path} を削除しました。")
return 0
except subprocess.CalledProcessError as e:
print(f"プロセス終了エラー: {e}")
return 1
except PermissionError as e:
print(f"権限エラー: {e}")
return 1
except Exception as error:
print("エラーが発生しました。\nエラー内容:")
print(f"エラータイプ: {error.__class__.__name__}")
print(f"エラー引数: {error.args}")
print(f"エラーメッセージ: {str(error)}")
return 1
appdata_local = os.path.expandvars("%LOCALAPPDATA%")
appdata_roaming = os.path.expandvars("%APPDATA%")
print(appdata_local, appdata_roaming)
print(f"{appdata_local}\\Steam")
steam_del = delete_appdata(process_name="steam.exe", dir_path=f"{appdata_local}\\Steam")

77
temp/csv_test.py Normal file
View file

@ -0,0 +1,77 @@
import psycopg2
import csv
from psycopg2 import sql
from datetime import datetime
def format_datetime(value):
if isinstance(value, datetime):
return value.strftime('%Y-%m-%d %H:%M:%S')
return value
def export_table_to_csv(host, port, database, user, password, main_table, related_table, related_column, csv_file_path):
try:
# データベースに接続
conn = psycopg2.connect(
host=host,
port=port,
database=database,
user=user,
password=password
)
cur = conn.cursor()
# メインテーブルの列情報を取得user_idを除く
cur.execute(sql.SQL("SELECT * FROM {} LIMIT 0").format(sql.Identifier(main_table)))
main_columns = [desc[0] for desc in cur.description if desc[0] != 'member_id']
# クエリを作成(列名を明確に指定)
query = sql.SQL("""
SELECT {main_columns}, {related_table}.name
FROM {main_table}
LEFT JOIN {related_table} ON {main_table}.member_id = {related_table}.id
""").format(
main_columns=sql.SQL(', ').join([sql.SQL("{}.{}").format(sql.Identifier(main_table), sql.Identifier(col)) for col in main_columns]),
main_table=sql.Identifier(main_table),
related_table=sql.Identifier(related_table)
)
cur.execute(query)
# 列名を再構成nameを2番目に配置
column_names = [main_columns[0], 'name'] + main_columns[1:]
rows = cur.fetchall()
with open(csv_file_path, 'w', newline='', encoding='utf-8-sig') as csvfile:
csvwriter = csv.writer(csvfile)
csvwriter.writerow(column_names)
for row in rows:
# nameを2番目に移動
formatted_row = [format_datetime(row[0])] + [row[-1]] + [format_datetime(field) if field is not None else '' for field in row[1:-1]]
csvwriter.writerow(formatted_row)
print(f"テーブル '{main_table}' の内容を '{csv_file_path}' に出力しました。")
except (Exception, psycopg2.Error) as error:
print("エラーが発生しました:", error)
finally:
if conn:
cur.close()
conn.close()
print("データベース接続を閉じました。")
# 使用例
host = "192.168.1.220"
port = "12245"
database = "dislocker"
user = "suti7"
password = "Testing1234"
main_table = "pc_usage_history"
related_table = "club_member"
related_column = "name" # 例: 登録者の名前を表示したい場合
csv_file_path = "output.csv"
export_table_to_csv(host, port, database, user, password, main_table, related_table, related_column, csv_file_path)

97
temp/xcel.py Normal file
View file

@ -0,0 +1,97 @@
import psycopg2
import csv
from psycopg2 import sql
from datetime import datetime
from openpyxl import Workbook
from openpyxl.utils import get_column_letter
def format_datetime(value):
if isinstance(value, datetime):
return value.strftime('%Y-%m-%d %H:%M:%S')
return value
def export_table_to_excel(host, port, database, user, password, main_table, related_table, excel_file_path):
try:
conn = psycopg2.connect(
host=host,
port=port,
database=database,
user=user,
password=password
)
cur = conn.cursor()
# メインテーブルの列情報を取得member_idを除く
cur.execute(sql.SQL("SELECT * FROM {} LIMIT 0").format(sql.Identifier(main_table)))
main_columns = [desc[0] for desc in cur.description if desc[0] != 'member_id']
# クエリを作成(列名を明確に指定)
query = sql.SQL("""
SELECT {main_columns}, {related_table}.name
FROM {main_table}
LEFT JOIN {related_table} ON {main_table}.member_id = {related_table}.id
""").format(
main_columns=sql.SQL(', ').join([sql.SQL("{}.{}").format(sql.Identifier(main_table), sql.Identifier(col)) for col in main_columns]),
main_table=sql.Identifier(main_table),
related_table=sql.Identifier(related_table)
)
cur.execute(query)
# 列名を再構成nameを2番目に配置
column_names = [main_columns[0], 'name'] + main_columns[1:]
rows = cur.fetchall()
# Excelワークブックを作成
wb = Workbook()
ws = wb.active
# 列名を書き込み
ws.append(column_names)
# データを書き込み
for row in rows:
# nameを2番目に移動
formatted_row = [format_datetime(row[0])] + [row[-1]] + [format_datetime(field) if field is not None else '' for field in row[1:-1]]
ws.append(formatted_row)
# 列幅を自動調整
for col in ws.columns:
max_length = 0
column = col[0].column_letter
for cell in col:
try:
if len(str(cell.value)) > max_length:
max_length = len(str(cell.value))
except:
pass
adjusted_width = (max_length + 2) * 1.2
ws.column_dimensions[column].width = adjusted_width
# Excelファイルを保存
wb.save(excel_file_path)
print(f"テーブル '{main_table}' の内容を '{excel_file_path}' に出力しました。")
except (Exception, psycopg2.Error) as error:
print("エラーが発生しました:", error)
finally:
if conn:
cur.close()
conn.close()
print("データベース接続を閉じました。")
# 使用例
host = "192.168.1.220"
port = "12245"
database = "dislocker"
user = "suti7"
password = "Testing1234"
main_table = "pc_usage_history"
related_table = "club_member"
excel_file_path = "output.xlsx"
export_table_to_excel(host, port, database, user, password, main_table, related_table, excel_file_path)