From 03319676e99fd521ccbe3b933c8d8c9c8324e6f4 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 22 Aug 2024 23:53:37 +0900 Subject: [PATCH 001/175] =?UTF-8?q?temp=E3=83=87=E3=82=A3=E3=83=AC?= =?UTF-8?q?=E3=82=AF=E3=83=88=E3=83=AA=E3=82=92=E9=99=A4=E5=A4=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f5c0150..541d26b 100644 --- a/.gitignore +++ b/.gitignore @@ -164,4 +164,5 @@ config/ db/ test.py data/ -export/ \ No newline at end of file +export/ +temp/ \ No newline at end of file From 07fcc298a901dc71d244d5c1479b4846fa9a11d3 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 22 Aug 2024 23:58:31 +0900 Subject: [PATCH 002/175] =?UTF-8?q?=E5=85=A8=E9=83=A8=E5=A4=89=E3=81=88?= =?UTF-8?q?=E3=81=9F=20=E3=81=A0=E3=81=84=E3=81=9F=E3=81=84=E3=81=AE?= =?UTF-8?q?=E9=96=A2=E6=95=B0=E3=81=A7=E8=BF=94=E3=82=8A=E5=80=A4=E3=82=92?= =?UTF-8?q?return=E3=81=A7=E7=9B=B4=E6=8E=A5=E8=BF=94=E3=81=99=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F=20=E8=BF=94=E3=82=8A?= =?UTF-8?q?=E5=80=A4=E3=81=AE=E8=BE=9E=E6=9B=B8=E3=81=AE=E6=A7=8B=E9=80=A0?= =?UTF-8?q?=E5=A4=89=E6=9B=B4(result=E3=81=AF=E3=81=A0=E3=81=84=E3=81=9F?= =?UTF-8?q?=E3=81=84=E3=81=AE=E7=8A=B6=E6=B3=81=E3=81=8C=E3=82=8F=E3=81=8B?= =?UTF-8?q?=E3=82=8B=E7=95=AA=E5=8F=B7=E3=81=A7=E3=80=81about=E3=81=AB?= =?UTF-8?q?=E5=BE=93=E6=9D=A5=E3=81=AEresult=E3=81=A7=E3=81=82=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E7=90=86=E7=94=B1=E3=82=92=E8=A8=98=E8=BC=89)=20?= =?UTF-8?q?=E3=83=87=E3=83=BC=E3=82=BF=E3=83=99=E3=83=BC=E3=82=B9=E6=A7=8B?= =?UTF-8?q?=E9=80=A0=E3=81=AE=E5=A4=89=E6=9B=B4(=E3=82=AD=E3=83=BC?= =?UTF-8?q?=E3=83=9C=E3=83=BC=E3=83=89=E3=80=81=E3=83=9E=E3=82=A6=E3=82=B9?= =?UTF-8?q?=E3=83=AA=E3=82=B9=E3=83=88=E8=BF=BD=E5=8A=A0=E3=80=81=E3=81=9D?= =?UTF-8?q?=E3=81=AE=E4=BB=96=E3=82=AB=E3=83=A9=E3=83=A0=E5=90=8D=E5=A4=89?= =?UTF-8?q?=E6=9B=B4)=20PC=E4=BD=BF=E7=94=A8=E7=99=BB=E9=8C=B2=E6=99=82?= =?UTF-8?q?=E3=81=AB=E3=83=9E=E3=82=A6=E3=82=B9=E3=81=A8=E3=82=AD=E3=83=BC?= =?UTF-8?q?=E3=83=9C=E3=83=BC=E3=83=89=E3=81=AE=E7=95=AA=E5=8F=B7=E3=82=92?= =?UTF-8?q?=E3=82=92=E5=88=A5=E3=81=A7=E5=B0=8B=E3=81=AD=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=20PC=E4=BD=BF=E7=94=A8=E7=99=BB=E9=8C=B2?= =?UTF-8?q?=E6=99=82=E3=81=AB=E3=83=9E=E3=82=A6=E3=82=B9=E3=81=A8=E3=82=AD?= =?UTF-8?q?=E3=83=BC=E3=83=9C=E3=83=BC=E3=83=89=E3=82=92=E8=87=AA=E5=89=8D?= =?UTF-8?q?=E3=81=A7=E6=8C=81=E3=81=A3=E3=81=A6=E3=81=8D=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=82=8B=E5=A0=B4=E5=90=88=E3=81=AE=E3=83=9C=E3=82=BF=E3=83=B3?= =?UTF-8?q?=E3=81=A8=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0=20PC?= =?UTF-8?q?=E7=99=BB=E9=8C=B2=E3=81=AE=E9=96=A2=E6=95=B0=E5=86=85=E3=81=AE?= =?UTF-8?q?=E4=B8=80=E9=83=A8=E3=81=AE=E5=87=A6=E7=90=86=E3=82=92=E5=88=A5?= =?UTF-8?q?=E3=81=AE=E9=96=A2=E6=95=B0=E3=81=AB=E7=A7=BB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 585 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 406 insertions(+), 179 deletions(-) diff --git a/dislocker.py b/dislocker.py index 7da40da..51da894 100644 --- a/dislocker.py +++ b/dislocker.py @@ -74,29 +74,51 @@ class DL(): cursor = self.db.cursor() self.pc_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + self.keyboard_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + self.mouse_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')") + 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))") + cursor.execute("CREATE TABLE club_member (member_id SERIAL NOT NULL, name TEXT NOT NULL, discord_user_name TEXT NOT NULL, discord_user_id TEXT 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')") + 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): + cursor.execute("CREATE TABLE pc_list (pc_number INTEGER NOT NULL, using_member_id INTEGER, password_hash VARCHAR(32), PRIMARY KEY (pc_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") + for i in self.pc_list: print(i) - cursor.execute("INSERT INTO pc_list (pc_number) VALUES (%s)", (i + 1,)) + cursor.execute("INSERT INTO pc_list (pc_number) VALUES (%s)", (i,)) self.db.commit() - cursor.execute("SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'pc_usage_history')") + cursor.execute("SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'keyboard_list')") + find_keyboard_list_table = cursor.fetchall() + print(find_keyboard_list_table) + if find_keyboard_list_table[0][0] == False: + cursor.execute("CREATE TABLE keyboard_list (keyboard_number INTEGER NOT NULL, using_member_id INTEGER, PRIMARY KEY (keyboard_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") + for i in self.keyboard_list: + print(i) + cursor.execute("INSERT INTO keyboard_list (keyboard_number) VALUES (%s)", (i,)) + self.db.commit() + + cursor.execute("SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'mouse_list')") + find_mouse_list_table = cursor.fetchall() + print(find_mouse_list_table) + if find_mouse_list_table[0][0] == False: + cursor.execute("CREATE TABLE mouse_list (mouse_number INTEGER NOT NULL, using_member_id INTEGER, PRIMARY KEY (mouse_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") + for i in self.mouse_list: + print(i) + cursor.execute("INSERT INTO mouse_list (mouse_number) VALUES (%s)", (i,)) + 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))") + cursor.execute("CREATE TABLE pc_usage_history (id SERIAL NOT NULL, member_id INTEGER NOT NULL, pc_number INTEGER NOT NULL, keyboard_number INTEGER, mouse_number INTEGER, start_use_time TIMESTAMP NOT NULL, end_use_time TIMESTAMP, use_detail TEXT, bot_about TEXT, PRIMARY KEY (id), FOREIGN KEY (member_id) REFERENCES club_member(member_id), FOREIGN KEY (pc_number) REFERENCES pc_list(pc_number), FOREIGN KEY (keyboard_number) REFERENCES keyboard_list(keyboard_number), FOREIGN KEY (mouse_number) REFERENCES mouse_list(mouse_number)") self.db.commit() cursor.close() @@ -121,6 +143,127 @@ class Bot(discord.Client): hashed = hashlib.md5(source.encode()) return hashed.hexdigest() + def user_register_check(self, **kwargs): + try: + discord_user_id = str(kwargs["discord_user_id"]) + + cursor = dislocker.db.cursor() + + cursor.execute("SELECT * FROM club_member WHERE discord_user_id = %s", (discord_user_id,)) + user_record = cursor.fetchall() + #ユーザーデータが見つかった場合(登録済みの場合) + if user_record: + member_id = user_record[0][0] + name = user_record[0][1] + discord_user_name = user_record[0][2] + return {"result": 0, "about": "exist", "user_info": {"member_id": member_id, "name": name, "discord_user_name": discord_user_name}} + #ユーザーデータがなかったら(未登録の場合) + else: + return {"result": 1, "about": "user_data_not_found"} + + except Exception as error: + print("キーボードの使用状況を調査中にエラーが発生しました。\nエラー内容") + print(str(error.__class__.__name__)) + print(str(error.args)) + print(str(error)) + return {"result": 1, "about": "error"} + + finally: + cursor.close() + + def pc_used_check(self, **kwargs): + try: + if "pc_number" in kwargs: + pc_number = int(kwargs["pc_number"]) + else: + pc_number = None + if "discord_user_id" in kwargs: + discord_user_id = str(kwargs["discord_user_id"]) + else: + discord_user_id = None + + cursor = dislocker.db.cursor() + + if discord_user_id == None: + # pc_listから探す + cursor.execute("SELECT * FROM pc_list WHERE pc_number=%s", (pc_number,)) + pc_list_record = cursor.fetchall() + if pc_list_record[0][1] == None: + return {"result": 0, "about": "vacent"} + else: + return {"result": 1, "about": "used_by_other"} + else: + #ユーザーIDを指定してPC使用履歴から探す + cursor.execute("SELECT * FROM pc_usage_history WHERE member_id=%s ORDER BY id DESC LIMIT 1", (discord_user_id,)) + pc_usage_history_record = cursor.fetchall() + if pc_usage_history_record: + print("used") + if pc_usage_history_record[0][5] == None: + return {"result": 1, "about": "used_by_you", "pc_usage_history": {"pc_number": str(pc_usage_history_record[0][2]), "keyboard_number": str(pc_usage_history_record[0][3]), "mouse_number": str(pc_usage_history_record[0][4]), "start_time": str(pc_usage_history_record[0][5]), "use_detail": str(pc_usage_history_record[0][7])}} + else: + return {"result": 0, "about": "vacent"} + else: + return {"result": 0, "about": "vacent"} + + except Exception as error: + print("PCの使用状況を調査中にエラーが発生しました。\nエラー内容") + print(str(error.__class__.__name__)) + print(str(error.args)) + print(str(error)) + return {"result": 1, "about": "error"} + + finally: + cursor.close() + + def keyboard_used_check(self, **kwargs): + try: + keyboard_number = int(kwargs["keyboard_number"]) + + cursor = dislocker.db.cursor() + + cursor.execute("SELECT * FROM keyboard_list WHERE keyboard_number=%s", (keyboard_number,)) + keyboard_list_record = cursor.fetchall() + if keyboard_list_record[0][1] == None: + return {"result": 0, "about": "ok"} + else: + return {"result": 1, "about": "keyboard_already_in_use_by_other"} + + + except Exception as error: + print("キーボードの使用状況を調査中にエラーが発生しました。\nエラー内容") + print(str(error.__class__.__name__)) + print(str(error.args)) + print(str(error)) + return {"result": 1, "about": "error"} + + finally: + cursor.close() + + def mouse_used_check(self, **kwargs): + try: + mouse_number = int(kwargs["mouse_number"]) + + cursor = dislocker.db.cursor() + + cursor.execute("SELECT * FROM mouse_list WHERE mouse_number=%s", (mouse_number,)) + mouse_list_record = cursor.fetchall() + if mouse_list_record[0][1] == None: + return {"result": 0, "about": "ok"} + else: + return {"result": 1, "about": "mouse_already_in_use_by_other"} + + + except Exception as error: + print("マウスの使用状況を調査中にエラーが発生しました。\nエラー内容") + print(str(error.__class__.__name__)) + print(str(error.args)) + print(str(error)) + return {"result": 1, "about": "error"} + + finally: + cursor.close() + + def register(self, **kwargs): try: user_info = { @@ -128,90 +271,79 @@ class Bot(discord.Client): "name": str(kwargs["name"]), "display_name": str(kwargs["display_name"]), "pc_number": int(kwargs["pc_number"]), - "device_number": int(kwargs["device_number"]), + "keyboard_number": None, + "mouse_number": None, "detail": None } if "detail" in kwargs: user_info["detail"] = str(kwargs["detail"]) else: pass - #パスワード生成、ハッシュ化 - password = self.password_generate(4) - password_hash = self.hash_genarate(password) - print("password generated") - #メンバーリストと送信者を照合 - cursor = dislocker.db.cursor() - cursor.execute("SELECT * FROM club_member WHERE discord_userid = %s", (user_info["id"],)) - user_record = cursor.fetchall() - #ユーザーデータがなかったら(未登録の場合) - if not user_record: - result = {"result": "user_data_not_found"} - #ユーザーデータが見つかった場合(登録済みの場合) + + if kwargs["keyboard_number"] == "own": + pass else: - print("found user data") - 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: - print("used") - 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: - cursor.execute("SELECT * FROM pc_list WHERE pc_number=%s", (user_info["pc_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"])) - #使用用途があるとき + user_info["keyboard_number"] = int(kwargs["keyboard_number"]) + + if kwargs["mouse_number"] == "own": + pass + else: + user_info["mouse_number"] = int(kwargs["mouse_number"]) + cursor = dislocker.db.cursor() + # ユーザー登録されているかの確認 + user_register = self.user_register_check(discord_user_id=user_info["id"]) + if user_register["result"] == 0: + member_id = user_register["user_info"]["member_id"] + discord_user_name = user_register["user_info"]["discord_user_name"] + # ユーザーがPCを使っているか + pc_check_self = self.pc_used_check(discord_user_id=user_info["id"]) + if pc_check_self["result"] == 0: + # 他の人がそのPCを使っているか + pc_check = self.pc_used_check(pc_number=user_info["pc_number"]) + if pc_check["result"] == 0: + # キーボードは使われているか + keyboard_check = self.keyboard_used_check(keyboard_number=user_info["keyboard_number"]) + if keyboard_check["result"] == 0: + # マウスは使われているか + mouse_check = self.mouse_used_check(mouse_number=user_info["mouse_number"]) + if mouse_check["result"] == 0: + # パスワードとハッシュ作成 + password = self.password_generate(4) + password_hash = self.hash_genarate(password) + # PC使用履歴のテーブルにレコードを挿入 + cursor.execute("INSERT INTO pc_usage_history (member_id, pc_number, keyboard_number, mouse_number, start_use_time, use_detail) VALUES (%s, %s, %s, %s, clock_timestamp(), %s)", (member_id, user_info["pc_number"], user_info["keyboard_number"], user_info["mouse_number"], user_info["detail"])) + # PCリストの該当のレコードを更新 + cursor.execute("UPDATE pc_list SET using_member_id = %s, password_hash = %s WHERE pc_number = %s", (member_id, password_hash, user_info["pc_number"])) + # キーボードリストの該当のレコードを自前(None)だったらスキップ、借りていたら更新 + if user_info["keyboard_number"] == None: + pass + else: + cursor.execute("UPDATE keyboard_list SET using_member_id = %s WHERE keyboard_number = %s", (member_id, user_info["keyboard_number"])) + # マウスも同様に + if user_info["mouse_number"] == None: + pass + else: + cursor.execute("UPDATE mouse_list SET using_member_id = %s WHERE mouse_number = %s", (member_id, user_info["mouse_number"])) + return {"result": 0, "about": "ok", "output": {"password": str(password), "name": str(discord_user_name)}} 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])} + return {"result": 1, "about": "mouse_already_in_use"} + else: + return {"result": 1, "about": "keyboard_already_in_use"} + else: + return {"result": 1, "about": "pc_already_in_use_by_other"} 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: - 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])} - - + return {"result": 1, "about": "pc_already_in_use_by_you", "pc_usage_history": {"pc_number": pc_check_self["pc_usage_history"]["pc_number"], "keyboard_number": pc_check_self["pc_usage_history"]["keyboard_number"], "mouse_number": pc_check_self["pc_usage_history"]["mouse_number"], "start_time": pc_check_self["pc_usage_history"]["start_time"]}, "use_detail": pc_check_self["pc_usage_history"]["use_detail"]} + else: + return {"result": "user_data_not_found"} except Exception as error: print("登録処理中にエラーが発生しました。\nエラー内容") print(str(error.__class__.__name__)) print(str(error.args)) print(str(error)) - result = {"result": "error"} - + return {"result": "error"} finally: cursor.close() - return result + return {"result": 1, "about": "error"} def stop(self, **kwargs): try: @@ -221,34 +353,59 @@ class Bot(discord.Client): else: 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],)) + # ユーザーが登録してるかというよりはデータの取得のため + user_register = self.user_register_check(discord_user_id=discord_user_id) + member_id = user_register["user_info"]["member_id"] + name = user_register["user_info"]["name"] - cursor.execute("UPDATE pc_list SET using_user_id = NULL, password_hash = NULL WHERE pc_number = %s", (pc_usage_history_record[0][2],)) + if user_register["result"] == 0: + cursor.execute("SELECT * FROM pc_usage_history WHERE member_id= %s ORDER BY id DESC LIMIT 1", (member_id,)) + pc_usage_history_record = cursor.fetchall() + + if pc_usage_history_record: + usage_id = pc_usage_history_record[0][0] + pc_number = pc_usage_history_record[0][2] + keyboard_number = pc_usage_history_record[0][3] + mouse_number = pc_usage_history_record[0][4] + end_use_time = pc_usage_history_record[0][6] + + # 使用中のとき (使用停止時間がNoneのとき) + if end_use_time == None: + # 利用停止の理由の有無を判断 + if bot_about == None: + cursor.execute("UPDATE pc_usage_history SET end_use_time = clock_timestamp() WHERE id = %s", (usage_id,)) + else: + cursor.execute("UPDATE pc_usage_history SET end_use_time = clock_timestamp(), bot_about = %s WHERE id = %s", (bot_about, usage_id)) + # pc_listの使用中ユーザーを消す + cursor.execute("UPDATE pc_list SET using_member_id = NULL, password_hash = NULL WHERE pc_number = %s", (pc_number,)) + if keyboard_number == None: + pass + else: + # keyboard_listの使用中ユーザーを消す + cursor.execute("UPDATE keyboard_list SET using_member_id = NULL WHERE keyboard_number = %s", (keyboard_number,)) + if mouse_number == None: + pass + else: + # mouse_listの使用中ユーザーを消す + cursor.execute("UPDATE mouse_list SET using_member_id = NULL WHERE keyboard_number = %s", (mouse_number,)) dislocker.db.commit() - result = {"result": "ok", "pc_number": str(pc_usage_history_record[0][2]), "name": str(user_record[0][1])} + return {"result": 0, "about": "ok", "output_dict": {"pc_number": str(pc_number), "name": str(name)}} + else: + return {"result": 1, "about": "unused"} else: - result = {"result": "unused"} + return {"result": 1, "about": "unused"} else: - result = {"result": "user_data_not_found"} - except: - print("停止処理にエラーが発生しました。") - result = {"result": "error"} + return {"result": 1, "about": "user_data_not_found"} + + except Exception as error: + print("停止処理中にエラーが発生しました。\nエラー内容") + print(str(error.__class__.__name__)) + print(str(error.args)) + print(str(error)) + return {"result": 1, "about": "error"} finally: cursor.close() - return result def user_register(self, **kwargs): try: @@ -256,25 +413,25 @@ class Bot(discord.Client): 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,)) + cursor.execute("SELECT * FROM club_member WHERE discord_user_id = %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)) + cursor.execute("INSERT INTO club_member (name, discord_user_name, discord_user_id) VALUES (%s, %s, %s)", (name, discord_user_name, discord_user_id)) dislocker.db.commit() - result = {"result": "ok"} + return {"result": 0, "about": "ok"} else: - result = {"result": "already_exists"} + return {"result": 1, "about": "already_exists"} except Exception as error: print("ユーザー登録中にエラーが発生しました。\nエラー内容") print(str(error.__class__.__name__)) print(str(error.args)) print(str(error)) - result = {"result": "error"} + return {"result": 1, "about": "error"} finally: cursor.close() - return result + def format_datetime(self, value): if isinstance(value, datetime): @@ -290,20 +447,19 @@ class Bot(discord.Client): if not pc_list: cursor.execute("INSERT INTO pc_list (pc_number) VALUES (%s)", (pc_number,)) dislocker.db.commit() - result = {"result": "ok"} + return {"result": 0, "about": "ok"} else: - result = {"result": "already_exists"} + return {"result": 1, "about": "already_exists"} except Exception as error: print("PCの登録中にエラーが発生しました。\nエラー内容") print(str(error.__class__.__name__)) print(str(error.args)) print(str(error)) - result = {"result": "error"} + return {"result": 1, "about": "error"} finally: cursor.close() - return result def report_export(self, **kwargs): try: @@ -366,50 +522,59 @@ class Bot(discord.Client): wb.save(excel_file_path) print(f"テーブル '{main_table}' の内容を '{excel_file_path}' に出力しました。") - result = {"result": "ok", "file_path": excel_file_path} + return {"result": 0, "about": "ok", "file_path": excel_file_path} + + + except Exception as error: + print("使用履歴のエクスポート中にエラーが発生しました。\nエラー内容") + print(str(error.__class__.__name__)) + print(str(error.args)) + print(str(error)) + return {"result": 1, "about": "error"} - 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"] + cursor = dislocker.db.cursor() 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() - 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 = clock_timestamp(), bot_about = %s WHERE id = %s", (bot_about, pc_usage_history_record[0][0])) - dislocker.db.commit() - result = {"result": "ok"} + pc_using_member_id = pc_list_record[0][1] + pc_password_hash = pc_list_record[0][2] + if pc_using_member_id == None: + return {"result": 1, "about": "not_used"} else: - result = {"result": "not_used"} + cursor.execute("UPDATE pc_list SET using_member_id = NULL WHERE pc_number = %s", (pc_number,)) + if pc_password_hash == None: + pass + else: + 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_using_member_id, pc_number)) + pc_usage_history_record = cursor.fetchall() + pc_usage_history_record_id = pc_usage_history_record[0][0] + keyboard_id = pc_usage_history_record[0][3] + mouse_id = pc_usage_history_record[0][4] + cursor.execute("UPDATE pc_usage_history SET end_use_time = clock_timestamp(), bot_about = %s WHERE id = %s", (bot_about, pc_usage_history_record_id)) + dislocker.db.commit() + return {"result": 0, "about": "ok"} else: - bot_about = None - result = {"result": "bot_about_not_found"} + return {"result": 1, "about": "bot_about_not_found"} + except Exception as error: + print("fstop中にエラーが発生しました。\nエラー内容") + print(str(error.__class__.__name__)) + print(str(error.args)) + print(str(error)) + return {"result": 1, "about": "error"} - except: - result = {"result": "error"} - finally: cursor.close() - return result - async def timeout_notify(self, **kwargs): try: @@ -417,18 +582,15 @@ class Bot(discord.Client): discord_display_name = kwargs["discord_display_name"] 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": "ok"} + return {"result": 0, "result": "ok"} except Exception as error: print("自動停止処理中にエラーが発生しました。\nエラー内容") print(str(error.__class__.__name__)) print(str(error.args)) print(str(error)) - result = {"result": "error"} + return {"result": 1, "about": "error"} - finally: - return result - async def on_ready(self): print("DiscordのBotが起動しました。") @@ -452,6 +614,7 @@ class Bot(discord.Client): pass elif isinstance(message.channel, discord.DMChannel): + """ msg_split = message.content.split() if msg_split[0] == "/password" or msg_split[0] == "/start": #メッセージの要素が2つ以下の場合は拒否 @@ -492,6 +655,8 @@ class Bot(discord.Client): elif stop["result"] == "ok": await message.channel.send(f":white_check_mark: 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"]} の使用を終了しました。') + """ + await message.channel.send("# :warning: DMでの応答は、現在無効化されています。") elif message.channel.id == dislocker.server_config["bot"]["config_channel_id"]: msg_split = message.content.split() @@ -500,9 +665,9 @@ class Bot(discord.Client): 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": + if register["about"] == "ok": await message.channel.send(f"# :white_check_mark: ユーザー情報が登録されました。\n>>> ユーザー名:{message.author.display_name}") - elif register["result"] == "already_exists": + elif register["about"] == "already_exists": await message.channel.send("# :no_entry: 登録できませんでした。\nもう登録されている可能性があります。") else: await message.channel.send("# :no_entry: 登録できませんでした。\n内部エラーが発生しています。") @@ -512,9 +677,9 @@ class Bot(discord.Client): elif len(msg_split) == 4: 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]) - if register["result"] == "ok": + if register["about"] == "ok": 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["about"] == "already_exists": await message.channel.send("# :skull_crossbones: 登録できませんでした。\nそのDiscordアカウントはすでに登録されています。") else: await message.channel.send("# :skull_crossbones: 登録できませんでした。\nDiscordのユーザーIDが不正です。") @@ -523,10 +688,10 @@ class Bot(discord.Client): elif msg_split[0] == "/export": export = self.report_export() - if export["result"] == "ok": + if export["about"] == "ok": await message.channel.send("# :page_facing_up: 使用履歴のレポートです。", file=discord.File(export["file_path"])) pass - elif export["result"] == "export_error": + elif export["about"] == "export_error": await message.channel.send("# :volcano: エクスポートに失敗しました。") elif msg_split[0] == "/fstop": @@ -535,9 +700,9 @@ class Bot(discord.Client): 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": + if fstop["about"] == "ok": await message.channel.send(f"# :white_check_mark: PC番号 {msg_split[1]} の使用登録を解除しました。") - elif fstop["result"] == "not_used": + elif fstop["about"] == "not_used": await message.channel.send("# :exploding_head: 登録を解除できませんでした。\nPCは使用されていないようです...") else: await message.channel.send("# :x: 登録を解除できませんでした。\n内部エラーが発生しています。") @@ -552,9 +717,9 @@ class Bot(discord.Client): 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": + if pc_register["about"] == "ok": await message.channel.send(f"# :white_check_mark: PCを登録しました。\n>>> # PC番号 | {msg_split[1]}") - elif pc_register["result"] == "already_exists": + elif pc_register["about"] == "already_exists": await message.channel.send(f":x: PCを登録できませんでした。\nその番号のPCは既に存在します。") else: await message.channel.send("# :x: PCを登録できませんでした。\n内部エラーが発生しています。") @@ -563,10 +728,30 @@ class Bot(discord.Client): else: await message.channel.send("# :warning: PCを登録できませんでした。\n構文が間違っています。\n-# /pcregister PC番号") + elif msg_split[0] == "/init": + 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) + + 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) + + pc_button_view = View(timeout=None) + for i in dislocker.pc_list: + pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"pcregister_{str(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] == "/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}") + for i in dislocker.pc_list: + pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"pcregister_{str(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) @@ -585,7 +770,6 @@ class Bot(discord.Client): 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() @@ -594,9 +778,9 @@ class Bot(discord.Client): 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": + if register["about"] == "ok": await message.channel.send(f"# :white_check_mark: ユーザー情報が登録されました。\nユーザー名:{message.author.display_name}") - elif register["result"] == "already_exists": + elif register["about"] == "already_exists": await message.channel.send("# :skull_crossbones: 登録できませんでした。\nそのDiscordアカウントはすでに登録されています。") else: await message.channel.send("# :skull_crossbones: 登録できませんでした。\n内部エラーが発生しています。") @@ -609,23 +793,51 @@ class Bot(discord.Client): print(custom_id, custom_id_split[0]) if custom_id_split[0] == "pcregister": - device_register_view = View(timeout=15) + keyboard_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) + for i in dislocker.keyboard_list: + keyboard_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"keyboardregister_{str(pc_number)}_{str(i)}") + keyboard_register_view.add_item(keyboard_register_button) + keyboard_not_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="キーボードは自前", custom_id=f"keyboardregister_{str(pc_number)}_own") + keyboard_register_view.add_item(keyboard_not_register_button) - await interaction.response.send_message(f"# :keyboard: デバイス番号を選んでください!\n>>> # PC番号 | {str(pc_number)}", view=device_register_view, ephemeral=True) + await interaction.response.send_message(f"# :keyboard: キーボードのデバイス番号を選んでください!\n>>> # PC番号 | {str(pc_number)}", view=keyboard_register_view, ephemeral=True) - elif custom_id_split[0] == "deviceregister": + elif custom_id_split[0] == "keyboardregister": + mouse_register_view = View(timeout=15) pc_number = custom_id_split[1] - device_number = custom_id_split[2] + keyboard_number = custom_id_split[2] + if keyboard_number == "own": + keyboard_number_show = "自前" + else: + keyboard_number_show = keyboard_number + print(custom_id_split) + for i in dislocker.mouse_list: + mouse_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_{str(i)}") + mouse_register_view.add_item(mouse_register_button) + mouse_not_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="マウスは自前", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_own") + mouse_register_view.add_item(mouse_not_register_button) + + await interaction.response.send_message(f"# :mouse_three_button: マウスのデバイス番号を選んでください!\n>>> # PC番号 | {str(pc_number)}\n# キーボード番号 | {str(keyboard_number_show)}", view=keyboard_register_view, ephemeral=True) + + elif custom_id_split[0] == "mouseregister": + pc_number = custom_id_split[1] + keyboard_number = custom_id_split[2] + if keyboard_number == "own": + keyboard_number_show = "自前" + else: + keyboard_number_show = keyboard_number + mouse_number = custom_id_split[3] + if mouse_number == "own": + mouse_number_show = "自前" + else: + mouse_number_show = mouse_number 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_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="使用目的を入力する", custom_id=f"reasonregister_{str(pc_number)}_{str(keyboard_number)}_{str(mouse_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) + await interaction.response.send_message(f"# :regional_indicator_q: 使用目的を書いてください!\n>>> # PC番号 | {str(pc_number)}\n# キーボード番号 | {str(keyboard_number_show)}\n# マウス番号 | {str(mouse_number_show)}", view=reason_register_view, ephemeral=True) elif custom_id_split[0] == "reasonregister": pc_number = custom_id_split[1] @@ -638,22 +850,22 @@ class Bot(discord.Client): pc_stop = self.stop(user_id=interaction.user.id) print(pc_stop) stop_view = View(timeout=15) - if pc_stop["result"] == "unused": + if pc_stop["about"] == "unused": await interaction.response.send_message("# :shaking_face: 使用されていないようです...", ephemeral=True) - elif pc_stop["result"] == "user_data_not_found": + elif pc_stop["about"] == "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"]} の使用を終了しました。') + elif pc_stop["about"] == "ok": + await interaction.response.send_message(f":white_check_mark: PC番号 {pc_stop["output_dict"]["pc_number"]} の使用が終了されました。", ephemeral=True) + await self.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':negative_squared_cross_mark: {pc_stop["output_dict"]["name"]} さんがPC {pc_stop["output_dict"]["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": + if user_register["about"] == "ok": await interaction.response.send_message(f"# :white_check_mark: ユーザー情報が登録されました。\n>>> ユーザー名:{interaction.user.display_name}", ephemeral=True) - elif user_register["result"] == "already_exists": + elif user_register["about"] == "already_exists": await interaction.response.send_message("# :no_entry: 登録できませんでした。\nもう登録されている可能性があります。", ephemeral=True) else: await interaction.response.send_message("# :no_entry: 登録できませんでした。\n内部エラーが発生しています。", ephemeral=True) @@ -770,18 +982,33 @@ class Reason(Modal): 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"]) + keyboard_number = custom_id_split[2] + if keyboard_number == "own": + keyboard_number_show = "自前" + else: + keyboard_number_show = keyboard_number + mouse_number = custom_id_split[3] + if mouse_number == "own": + mouse_number_show = "自前" + else: + mouse_number_show = mouse_number - 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": + register = bot.register(user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=self.reason_input_form.value) + print(register) + + if register["about"] == "ok": + await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["password"]}\n## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\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} の使用を開始しました。\n>>> ## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\n## 使用目的 | {self.reason_input_form.value}') + elif register["about"] == "pc_already_in_use_by_you": + pc_usage_history = register["pc_usage_history"] + await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {pc_usage_history["keyboard_number"]}\n# マウス番号 | {pc_usage_history["mouse_number"]}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) + elif register["about"] == "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": + elif register["about"] == "keyboard_already_in_use": + await interaction.response.send_message(f"# :man_gesturing_no: そのキーボードは他のメンバーによって使用されています。\n別のキーボードのデバイス番号を指定して、再度お試しください。", ephemeral=True) + elif register["about"] == "mouse_already_in_use": + await interaction.response.send_message(f"# :man_gesturing_no: そのマウスは他のメンバーによって使用されています。\n別のマウスのデバイス番号を指定して、再度お試しください。", ephemeral=True) + elif register["about"] == "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) From c107400ed252088a242b59d0de2817b9f8b3d066 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 00:17:51 +0900 Subject: [PATCH 003/175] =?UTF-8?q?fstop=E5=81=B4=E3=81=AE=E3=83=9E?= =?UTF-8?q?=E3=82=A6=E3=82=B9=E3=80=81=E3=82=AD=E3=83=BC=E3=83=9C=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=81=AE=E4=BD=BF=E7=94=A8=E5=81=9C=E6=AD=A2=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/dislocker.py b/dislocker.py index 51da894..1273cc0 100644 --- a/dislocker.py +++ b/dislocker.py @@ -162,7 +162,7 @@ class Bot(discord.Client): return {"result": 1, "about": "user_data_not_found"} except Exception as error: - print("キーボードの使用状況を調査中にエラーが発生しました。\nエラー内容") + print("ユーザーの登録状態を調査中にエラーが発生しました。\nエラー内容") print(str(error.__class__.__name__)) print(str(error.args)) print(str(error)) @@ -546,7 +546,6 @@ class Bot(discord.Client): pc_list_record = cursor.fetchall() pc_using_member_id = pc_list_record[0][1] pc_password_hash = pc_list_record[0][2] - if pc_using_member_id == None: return {"result": 1, "about": "not_used"} else: @@ -558,8 +557,18 @@ class Bot(discord.Client): cursor.execute("SELECT * FROM pc_usage_history WHERE member_id = %s AND pc_number = %s ORDER BY id DESC LIMIT 1", (pc_using_member_id, pc_number)) pc_usage_history_record = cursor.fetchall() pc_usage_history_record_id = pc_usage_history_record[0][0] - keyboard_id = pc_usage_history_record[0][3] - mouse_id = pc_usage_history_record[0][4] + keyboard_number = pc_usage_history_record[0][3] + mouse_number = pc_usage_history_record[0][4] + if keyboard_number == None: + pass + else: + # keyboard_listの使用中ユーザーを消す + cursor.execute("UPDATE keyboard_list SET using_member_id = NULL WHERE keyboard_number = %s", (keyboard_number,)) + if mouse_number == None: + pass + else: + # mouse_listの使用中ユーザーを消す + cursor.execute("UPDATE mouse_list SET using_member_id = NULL WHERE keyboard_number = %s", (mouse_number,)) cursor.execute("UPDATE pc_usage_history SET end_use_time = clock_timestamp(), bot_about = %s WHERE id = %s", (bot_about, pc_usage_history_record_id)) dislocker.db.commit() return {"result": 0, "about": "ok"} From 8e635eb92c414bb4e386fb01f0a36c04b07c638b Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 00:18:07 +0900 Subject: [PATCH 004/175] =?UTF-8?q?Bot=E3=81=AE=E4=BB=95=E6=A7=98=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E3=81=AB=E5=90=88=E3=82=8F=E3=81=9B=E3=81=A6=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_auth.py | 103 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 80 insertions(+), 23 deletions(-) diff --git a/dislocker_auth.py b/dislocker_auth.py index a16ab4d..d9c5585 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -45,10 +45,11 @@ class Auth(): cursor = self.db.cursor() cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s AND password_hash = %s", (pc_number, password)) pc_info = cursor.fetchall() - if not pc_info: - return 1 + if pc_info: + return {"result": 0, "about": "ok"} else: - return 0 + return {"result": 1, "about": "unregistered_pc"} + finally: cursor.close() @@ -57,36 +58,91 @@ class Auth(): cursor = self.db.cursor() cursor.execute("UPDATE pc_list SET password_hash = NULL WHERE pc_number = %s", (pc_number,)) self.db.commit() + return {"result": 0, "about": "ok"} + + except Exception as error: + print("パスワードの削除中にエラーが発生しました。\nエラー内容") + print(str(error.__class__.__name__)) + print(str(error.args)) + print(str(error)) + return {"result": 1, "about": "error"} + + finally: + cursor.close() + + def user_register_check(self, **kwargs): + try: + discord_user_id = str(kwargs["discord_user_id"]) + + cursor = self.db.cursor() + + cursor.execute("SELECT * FROM club_member WHERE discord_user_id = %s", (discord_user_id,)) + user_record = cursor.fetchall() + #ユーザーデータが見つかった場合(登録済みの場合) + if user_record: + member_id = user_record[0][0] + name = user_record[0][1] + discord_user_name = user_record[0][2] + return {"result": 0, "about": "exist", "user_info": {"member_id": member_id, "name": name, "discord_user_name": discord_user_name}} + #ユーザーデータがなかったら(未登録の場合) + else: + return {"result": 1, "about": "user_data_not_found"} + + except Exception as error: + print("ユーザーの登録状況を調査中にエラーが発生しました。\nエラー内容") + print(str(error.__class__.__name__)) + print(str(error.args)) + print(str(error)) + return {"result": 1, "about": "error"} + finally: cursor.close() def stop(self, **kwargs): + # bot側のfstopを基に try: - pc_number = int(kwargs["pc_number"]) + pc_number = 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"} - + pc_using_member_id = pc_list_record[0][1] + pc_password_hash = pc_list_record[0][2] + if pc_using_member_id == None: + return {"result": 1, "about": "not_used"} else: - result = {"result": "not_used"} + cursor.execute("UPDATE pc_list SET using_member_id = NULL WHERE pc_number = %s", (pc_number,)) + if pc_password_hash == None: + pass + else: + 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_using_member_id, pc_number)) + pc_usage_history_record = cursor.fetchall() + pc_usage_history_record_id = pc_usage_history_record[0][0] + keyboard_number = pc_usage_history_record[0][3] + mouse_number = pc_usage_history_record[0][4] + if keyboard_number == None: + pass + else: + # keyboard_listの使用中ユーザーを消す + cursor.execute("UPDATE keyboard_list SET using_member_id = NULL WHERE keyboard_number = %s", (keyboard_number,)) + if mouse_number == None: + pass + else: + # mouse_listの使用中ユーザーを消す + cursor.execute("UPDATE mouse_list SET using_member_id = NULL WHERE keyboard_number = %s", (mouse_number,)) + cursor.execute("UPDATE pc_usage_history SET end_use_time = clock_timestamp(), bot_about = %s WHERE id = %s", (bot_about, pc_usage_history_record_id)) + self.db.commit() + return {"result": 0, "about": "ok"} + + except Exception as error: + print("停止処理中にエラーが発生しました。\nエラー内容") + print(str(error.__class__.__name__)) + print(str(error.args)) + print(str(error)) + return {"result": 1, "about": "error"} - except: - result = {"result": "error"} - finally: cursor.close() - return result @@ -98,8 +154,9 @@ def verify(): pc_number = int(request.json.get('pc_number')) password = request.json.get('password') print(str(pc_number) + "の認証処理を開始...") + pc_auth = auth.check(pc_number, password) - if auth.check(pc_number, password) == 0: + if pc_auth["result"] == 0: auth.delete(pc_number) print(str(pc_number) + "の認証処理は成功しました.") return jsonify({'message': 'ok'}), 200 @@ -112,7 +169,7 @@ 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": + if pc_stop["result"] == 0: print(str(pc_number) + "の使用停止処理は成功しました.") return jsonify({'message': 'ok'}), 200 else: From db55479da9bb9bf49c985dbe34c10f5eb655dc47 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 00:19:33 +0900 Subject: [PATCH 005/175] =?UTF-8?q?=E3=81=A1=E3=82=87=E3=81=A3=E3=81=A8?= =?UTF-8?q?=E3=83=90=E3=82=B0=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker_auth.py b/dislocker_auth.py index d9c5585..6194034 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -130,7 +130,7 @@ class Auth(): else: # mouse_listの使用中ユーザーを消す cursor.execute("UPDATE mouse_list SET using_member_id = NULL WHERE keyboard_number = %s", (mouse_number,)) - cursor.execute("UPDATE pc_usage_history SET end_use_time = clock_timestamp(), bot_about = %s WHERE id = %s", (bot_about, pc_usage_history_record_id)) + cursor.execute("UPDATE pc_usage_history SET end_use_time = clock_timestamp() WHERE id = %s", (pc_usage_history_record_id,)) self.db.commit() return {"result": 0, "about": "ok"} From 55b55046df8339a9e4726a0707a61ba4a17bedd5 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 00:22:01 +0900 Subject: [PATCH 006/175] =?UTF-8?q?=E3=82=B9=E3=83=9A=E3=83=BC=E3=82=B9?= =?UTF-8?q?=E3=81=8C=E3=81=AA=E3=81=8B=E3=81=A3=E3=81=9F=E3=81=AE=E3=81=8C?= =?UTF-8?q?=E6=B0=97=E3=81=AB=E3=81=AA=E3=81=A3=E3=81=9F=E3=81=A0=E3=81=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dislocker.py b/dislocker.py index 1273cc0..1e9dd7e 100644 --- a/dislocker.py +++ b/dislocker.py @@ -186,7 +186,7 @@ class Bot(discord.Client): if discord_user_id == None: # pc_listから探す - cursor.execute("SELECT * FROM pc_list WHERE pc_number=%s", (pc_number,)) + cursor.execute("SELECT * FROM pc_list WHERE pc_number= %s", (pc_number,)) pc_list_record = cursor.fetchall() if pc_list_record[0][1] == None: return {"result": 0, "about": "vacent"} @@ -194,7 +194,7 @@ class Bot(discord.Client): return {"result": 1, "about": "used_by_other"} else: #ユーザーIDを指定してPC使用履歴から探す - cursor.execute("SELECT * FROM pc_usage_history WHERE member_id=%s ORDER BY id DESC LIMIT 1", (discord_user_id,)) + cursor.execute("SELECT * FROM pc_usage_history WHERE member_id= %s ORDER BY id DESC LIMIT 1", (discord_user_id,)) pc_usage_history_record = cursor.fetchall() if pc_usage_history_record: print("used") From 829184a91a42290cf7e884d90c80c832f69d7f0b Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 00:26:42 +0900 Subject: [PATCH 007/175] =?UTF-8?q?club=5Fmember=E3=83=86=E3=83=BC?= =?UTF-8?q?=E3=83=96=E3=83=AB=E3=81=AE=E3=82=AB=E3=83=A9=E3=83=A0=E5=90=8D?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker.py b/dislocker.py index 1e9dd7e..0aeb769 100644 --- a/dislocker.py +++ b/dislocker.py @@ -81,7 +81,7 @@ class DL(): 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 (member_id SERIAL NOT NULL, name TEXT NOT NULL, discord_user_name TEXT NOT NULL, discord_user_id TEXT NOT NULL, PRIMARY KEY (id))") + cursor.execute("CREATE TABLE club_member (member_id SERIAL NOT NULL, name TEXT NOT NULL, discord_user_name TEXT NOT NULL, discord_user_id TEXT NOT NULL, PRIMARY KEY (member_id))") self.db.commit() cursor.execute("SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'pc_list')") From ee4113f95d389a6635edadc362c58bcf32548e00 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 00:34:07 +0900 Subject: [PATCH 008/175] =?UTF-8?q?=E7=84=A1=E9=A7=84=E3=81=AA=E3=81=8B?= =?UTF-8?q?=E3=81=A3=E3=81=93=E3=81=A8=E3=81=98=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dislocker.py b/dislocker.py index 0aeb769..233b52e 100644 --- a/dislocker.py +++ b/dislocker.py @@ -81,14 +81,14 @@ class DL(): 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 (member_id SERIAL NOT NULL, name TEXT NOT NULL, discord_user_name TEXT NOT NULL, discord_user_id TEXT NOT NULL, PRIMARY KEY (member_id))") + cursor.execute("CREATE TABLE club_member (member_id SERIAL NOT NULL, name TEXT NOT NULL, discord_user_name TEXT NOT NULL, discord_user_id TEXT NOT NULL, PRIMARY KEY (member_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_member_id INTEGER, password_hash VARCHAR(32), PRIMARY KEY (pc_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") + cursor.execute("CREATE TABLE pc_list (pc_number INTEGER NOT NULL, using_member_id INTEGER, password_hash VARCHAR(32), PRIMARY KEY (pc_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id)") for i in self.pc_list: print(i) cursor.execute("INSERT INTO pc_list (pc_number) VALUES (%s)", (i,)) @@ -98,7 +98,7 @@ class DL(): find_keyboard_list_table = cursor.fetchall() print(find_keyboard_list_table) if find_keyboard_list_table[0][0] == False: - cursor.execute("CREATE TABLE keyboard_list (keyboard_number INTEGER NOT NULL, using_member_id INTEGER, PRIMARY KEY (keyboard_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") + cursor.execute("CREATE TABLE keyboard_list (keyboard_number INTEGER NOT NULL, using_member_id INTEGER, PRIMARY KEY (keyboard_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id)") for i in self.keyboard_list: print(i) cursor.execute("INSERT INTO keyboard_list (keyboard_number) VALUES (%s)", (i,)) @@ -108,7 +108,7 @@ class DL(): find_mouse_list_table = cursor.fetchall() print(find_mouse_list_table) if find_mouse_list_table[0][0] == False: - cursor.execute("CREATE TABLE mouse_list (mouse_number INTEGER NOT NULL, using_member_id INTEGER, PRIMARY KEY (mouse_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") + cursor.execute("CREATE TABLE mouse_list (mouse_number INTEGER NOT NULL, using_member_id INTEGER, PRIMARY KEY (mouse_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id)") for i in self.mouse_list: print(i) cursor.execute("INSERT INTO mouse_list (mouse_number) VALUES (%s)", (i,)) From 4b4455694e5e154c8e3ce260dd04e74d1c60bb34 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 00:39:34 +0900 Subject: [PATCH 009/175] =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E9=96=89?= =?UTF-8?q?=E3=81=98=E5=BF=85=E8=A6=81=E3=81=A0=E3=81=A3=E3=81=9F=E3=81=AE?= =?UTF-8?q?=E3=81=A7=E3=83=AD=E3=83=BC=E3=83=AB=E3=83=90=E3=83=83=E3=82=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dislocker.py b/dislocker.py index 233b52e..35b6f0d 100644 --- a/dislocker.py +++ b/dislocker.py @@ -81,14 +81,14 @@ class DL(): 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 (member_id SERIAL NOT NULL, name TEXT NOT NULL, discord_user_name TEXT NOT NULL, discord_user_id TEXT NOT NULL, PRIMARY KEY (member_id)") + cursor.execute("CREATE TABLE club_member (member_id SERIAL NOT NULL, name TEXT NOT NULL, discord_user_name TEXT NOT NULL, discord_user_id TEXT NOT NULL, PRIMARY KEY (member_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_member_id INTEGER, password_hash VARCHAR(32), PRIMARY KEY (pc_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id)") + cursor.execute("CREATE TABLE pc_list (pc_number INTEGER NOT NULL, using_member_id INTEGER, password_hash VARCHAR(32), PRIMARY KEY (pc_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") for i in self.pc_list: print(i) cursor.execute("INSERT INTO pc_list (pc_number) VALUES (%s)", (i,)) @@ -98,7 +98,7 @@ class DL(): find_keyboard_list_table = cursor.fetchall() print(find_keyboard_list_table) if find_keyboard_list_table[0][0] == False: - cursor.execute("CREATE TABLE keyboard_list (keyboard_number INTEGER NOT NULL, using_member_id INTEGER, PRIMARY KEY (keyboard_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id)") + cursor.execute("CREATE TABLE keyboard_list (keyboard_number INTEGER NOT NULL, using_member_id INTEGER, PRIMARY KEY (keyboard_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") for i in self.keyboard_list: print(i) cursor.execute("INSERT INTO keyboard_list (keyboard_number) VALUES (%s)", (i,)) @@ -108,7 +108,7 @@ class DL(): find_mouse_list_table = cursor.fetchall() print(find_mouse_list_table) if find_mouse_list_table[0][0] == False: - cursor.execute("CREATE TABLE mouse_list (mouse_number INTEGER NOT NULL, using_member_id INTEGER, PRIMARY KEY (mouse_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id)") + cursor.execute("CREATE TABLE mouse_list (mouse_number INTEGER NOT NULL, using_member_id INTEGER, PRIMARY KEY (mouse_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") for i in self.mouse_list: print(i) cursor.execute("INSERT INTO mouse_list (mouse_number) VALUES (%s)", (i,)) @@ -118,7 +118,7 @@ class DL(): 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, keyboard_number INTEGER, mouse_number INTEGER, start_use_time TIMESTAMP NOT NULL, end_use_time TIMESTAMP, use_detail TEXT, bot_about TEXT, PRIMARY KEY (id), FOREIGN KEY (member_id) REFERENCES club_member(member_id), FOREIGN KEY (pc_number) REFERENCES pc_list(pc_number), FOREIGN KEY (keyboard_number) REFERENCES keyboard_list(keyboard_number), FOREIGN KEY (mouse_number) REFERENCES mouse_list(mouse_number)") + cursor.execute("CREATE TABLE pc_usage_history (id SERIAL NOT NULL, member_id INTEGER NOT NULL, pc_number INTEGER NOT NULL, keyboard_number INTEGER, mouse_number INTEGER, start_use_time TIMESTAMP NOT NULL, end_use_time TIMESTAMP, use_detail TEXT, bot_about TEXT, PRIMARY KEY (id), FOREIGN KEY (member_id) REFERENCES club_member(member_id), FOREIGN KEY (pc_number) REFERENCES pc_list(pc_number), FOREIGN KEY (keyboard_number) REFERENCES keyboard_list(keyboard_number), FOREIGN KEY (mouse_number) REFERENCES mouse_list(mouse_number))") self.db.commit() cursor.close() From 7d7cc17193f27a0f62e297555a6c051de733cad6 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 00:44:57 +0900 Subject: [PATCH 010/175] =?UTF-8?q?=E3=83=9E=E3=82=A6=E3=82=B9=E3=82=92?= =?UTF-8?q?=E9=81=B8=E3=81=B6=E3=83=9C=E3=82=BF=E3=83=B3=E3=81=AE=E3=81=A8?= =?UTF-8?q?=E3=81=93=E3=82=8D=E3=81=AEview=E3=81=AE=E5=90=8D=E5=89=8D?= =?UTF-8?q?=E3=81=8C=E9=96=93=E9=81=95=E3=81=A3=E3=81=A6=E3=81=9F=E3=81=AE?= =?UTF-8?q?=E3=81=A7=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker.py b/dislocker.py index 35b6f0d..e496f30 100644 --- a/dislocker.py +++ b/dislocker.py @@ -828,7 +828,7 @@ class Bot(discord.Client): mouse_not_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="マウスは自前", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_own") mouse_register_view.add_item(mouse_not_register_button) - await interaction.response.send_message(f"# :mouse_three_button: マウスのデバイス番号を選んでください!\n>>> # PC番号 | {str(pc_number)}\n# キーボード番号 | {str(keyboard_number_show)}", view=keyboard_register_view, ephemeral=True) + await interaction.response.send_message(f"# :mouse_three_button: マウスのデバイス番号を選んでください!\n>>> # PC番号 | {str(pc_number)}\n# キーボード番号 | {str(keyboard_number_show)}", view=mouse_register_view, ephemeral=True) elif custom_id_split[0] == "mouseregister": pc_number = custom_id_split[1] From 926944105147fe3b69e70752af8800102851dbe7 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 00:53:03 +0900 Subject: [PATCH 011/175] =?UTF-8?q?custom=5Fid=E3=81=A7=E8=A8=80=E3=81=86?= =?UTF-8?q?=E3=81=A8=E3=81=93=E3=82=8D=E3=81=AEreasonregister=E3=81=82?= =?UTF-8?q?=E3=81=9F=E3=82=8A=E3=81=AE=E5=87=A6=E7=90=86=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dislocker.py b/dislocker.py index e496f30..5926cfd 100644 --- a/dislocker.py +++ b/dislocker.py @@ -850,8 +850,9 @@ class Bot(discord.Client): 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)) + keyboard_number = custom_id_split[2] + mouse_number = custom_id_split[3] + reason_input_form = Reason(title="Dislocker | 登録", pc_number=str(pc_number), keyboard_number=str(keyboard_number), mouse_number=str(mouse_number)) await interaction.response.send_modal(reason_input_form) elif custom_id_split[0] == "stop": @@ -979,11 +980,10 @@ class Monitor(): class Reason(Modal): - def __init__(self, title: str, pc_number: str, device_number: str, timeout=15) -> None: + def __init__(self, title: str, pc_number: str, keyboard_number: str, mouse_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}") + print(pc_number, keyboard_number, mouse_number) + self.reason_input_form = TextInput(label="使用目的を入力してください", style=TextStyle.short, custom_id=f"register_{pc_number}_{keyboard_number}_{mouse_number}") self.add_item(self.reason_input_form) async def on_submit(self, interaction: Interaction) -> None: From edd01a0d4d6da3146e77e2a8d88dd867a90134bd Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 00:55:49 +0900 Subject: [PATCH 012/175] =?UTF-8?q?=E7=99=BB=E9=8C=B2=E3=81=AE=E3=81=A8?= =?UTF-8?q?=E3=81=8D=E3=81=AB=E3=81=A9=E3=81=86=E3=81=97=E3=81=A6=E3=82=82?= =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E3=81=AB=E3=81=AA=E3=82=8B=E3=83=90?= =?UTF-8?q?=E3=82=B0=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dislocker.py b/dislocker.py index 5926cfd..2572a2e 100644 --- a/dislocker.py +++ b/dislocker.py @@ -340,10 +340,10 @@ class Bot(discord.Client): print(str(error.__class__.__name__)) print(str(error.args)) print(str(error)) - return {"result": "error"} + return {"result": 1, "about": "error"} finally: cursor.close() - return {"result": 1, "about": "error"} + def stop(self, **kwargs): try: From 69dbdd0bc37ff2d8289ed6e31e6ad532de9b8f40 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 00:58:38 +0900 Subject: [PATCH 013/175] =?UTF-8?q?PC=E4=BD=BF=E7=94=A8=E7=99=BB=E9=8C=B2?= =?UTF-8?q?=E6=99=82=E3=81=AB=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC=E3=81=8C?= =?UTF-8?q?=E7=99=BB=E9=8C=B2=E3=81=95=E3=82=8C=E3=81=A6=E3=81=84=E3=81=AA?= =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=9F=E3=81=A8=E3=81=8D=E3=81=AE=E8=BF=94?= =?UTF-8?q?=E3=82=8A=E5=80=A4=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker.py b/dislocker.py index 2572a2e..a02027c 100644 --- a/dislocker.py +++ b/dislocker.py @@ -334,7 +334,7 @@ class Bot(discord.Client): else: return {"result": 1, "about": "pc_already_in_use_by_you", "pc_usage_history": {"pc_number": pc_check_self["pc_usage_history"]["pc_number"], "keyboard_number": pc_check_self["pc_usage_history"]["keyboard_number"], "mouse_number": pc_check_self["pc_usage_history"]["mouse_number"], "start_time": pc_check_self["pc_usage_history"]["start_time"]}, "use_detail": pc_check_self["pc_usage_history"]["use_detail"]} else: - return {"result": "user_data_not_found"} + return {"result": 1, "about": "user_data_not_found"} except Exception as error: print("登録処理中にエラーが発生しました。\nエラー内容") print(str(error.__class__.__name__)) From cd6461641c5d892aa04ae95d3e024df1c0a88c01 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 01:09:00 +0900 Subject: [PATCH 014/175] =?UTF-8?q?PC=E3=81=AE=E4=BD=BF=E7=94=A8=E3=83=81?= =?UTF-8?q?=E3=82=A7=E3=83=83=E3=82=AF=E3=81=ABmember=5Fid=E3=81=A7?= =?UTF-8?q?=E7=A2=BA=E8=AA=8D=E3=81=A7=E3=81=8D=E3=82=8B=E5=87=A6=E7=90=86?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0=20PC=E4=BD=BF=E7=94=A8=E7=99=BB?= =?UTF-8?q?=E9=8C=B2=E6=99=82=E3=81=ABmember=5Fid=E3=82=92=E4=BD=BF?= =?UTF-8?q?=E3=81=A3=E3=81=A6PC=E3=81=AE=E4=BD=BF=E7=94=A8=E7=A2=BA?= =?UTF-8?q?=E8=AA=8D=E3=82=92=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/dislocker.py b/dislocker.py index a02027c..cd2c787 100644 --- a/dislocker.py +++ b/dislocker.py @@ -181,20 +181,42 @@ class Bot(discord.Client): discord_user_id = str(kwargs["discord_user_id"]) else: discord_user_id = None + if "member_id" in kwargs: + member_id = str(kwargs["member_id"]) + else: + member_id = None cursor = dislocker.db.cursor() - if discord_user_id == None: - # pc_listから探す + if pc_number != None: + # pc番号を指定してpc_listから探す cursor.execute("SELECT * FROM pc_list WHERE pc_number= %s", (pc_number,)) pc_list_record = cursor.fetchall() if pc_list_record[0][1] == None: return {"result": 0, "about": "vacent"} else: return {"result": 1, "about": "used_by_other"} - else: + elif discord_user_id != None: #ユーザーIDを指定してPC使用履歴から探す - cursor.execute("SELECT * FROM pc_usage_history WHERE member_id= %s ORDER BY id DESC LIMIT 1", (discord_user_id,)) + cursor.execute("SELECT * FROM club_member WHERE discord_user_id = %s", (discord_user_id,)) + user_record = cursor.fetchall() + #ユーザーデータが見つかった場合(登録済みの場合) + if user_record: + member_id = user_record[0][0] + cursor.execute("SELECT * FROM pc_usage_history WHERE member_id = %s ORDER BY id DESC LIMIT 1", (member_id,)) + pc_usage_history_record = cursor.fetchall() + if pc_usage_history_record: + print("used") + if pc_usage_history_record[0][5] == None: + return {"result": 1, "about": "used_by_you", "pc_usage_history": {"pc_number": str(pc_usage_history_record[0][2]), "keyboard_number": str(pc_usage_history_record[0][3]), "mouse_number": str(pc_usage_history_record[0][4]), "start_time": str(pc_usage_history_record[0][5]), "use_detail": str(pc_usage_history_record[0][7])}} + else: + return {"result": 0, "about": "vacent"} + else: + return {"result": 0, "about": "vacent"} + else: + return {"result": 1, "about": "user_data_not_found"} + elif member_id != None: + cursor.execute("SELECT * FROM pc_usage_history WHERE member_id = %s ORDER BY id DESC LIMIT 1", (member_id,)) pc_usage_history_record = cursor.fetchall() if pc_usage_history_record: print("used") @@ -204,6 +226,8 @@ class Bot(discord.Client): return {"result": 0, "about": "vacent"} else: return {"result": 0, "about": "vacent"} + else: + return {"result": 1, "about": "search_options_error"} except Exception as error: print("PCの使用状況を調査中にエラーが発生しました。\nエラー内容") @@ -296,7 +320,7 @@ class Bot(discord.Client): member_id = user_register["user_info"]["member_id"] discord_user_name = user_register["user_info"]["discord_user_name"] # ユーザーがPCを使っているか - pc_check_self = self.pc_used_check(discord_user_id=user_info["id"]) + pc_check_self = self.pc_used_check(member_id=member_id) if pc_check_self["result"] == 0: # 他の人がそのPCを使っているか pc_check = self.pc_used_check(pc_number=user_info["pc_number"]) From 125ca3e60189555bf354e2e3287764dee86fbd73 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 01:15:21 +0900 Subject: [PATCH 015/175] =?UTF-8?q?=E3=82=AD=E3=83=BC=E3=83=9C=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=80=81=E3=83=9E=E3=82=A6=E3=82=B9=E3=81=AE=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E7=A2=BA=E8=AA=8D=E9=96=A2=E6=95=B0=E3=81=AB=E8=87=AA?= =?UTF-8?q?=E5=89=8D=E3=81=AE=E3=82=82=E3=81=AE=E3=81=8B=E3=82=92=E5=88=A4?= =?UTF-8?q?=E5=AE=9A=E3=81=99=E3=82=8B=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/dislocker.py b/dislocker.py index cd2c787..1dab5b8 100644 --- a/dislocker.py +++ b/dislocker.py @@ -241,17 +241,19 @@ class Bot(discord.Client): def keyboard_used_check(self, **kwargs): try: - keyboard_number = int(kwargs["keyboard_number"]) - - cursor = dislocker.db.cursor() - - cursor.execute("SELECT * FROM keyboard_list WHERE keyboard_number=%s", (keyboard_number,)) - keyboard_list_record = cursor.fetchall() - if keyboard_list_record[0][1] == None: + if kwargs["keyboard_number"] == None: return {"result": 0, "about": "ok"} else: - return {"result": 1, "about": "keyboard_already_in_use_by_other"} + keyboard_number = int(kwargs["keyboard_number"]) + + cursor = dislocker.db.cursor() + cursor.execute("SELECT * FROM keyboard_list WHERE keyboard_number=%s", (keyboard_number,)) + keyboard_list_record = cursor.fetchall() + if keyboard_list_record[0][1] == None: + return {"result": 0, "about": "ok"} + else: + return {"result": 1, "about": "keyboard_already_in_use_by_other"} except Exception as error: print("キーボードの使用状況を調査中にエラーが発生しました。\nエラー内容") @@ -265,17 +267,19 @@ class Bot(discord.Client): def mouse_used_check(self, **kwargs): try: - mouse_number = int(kwargs["mouse_number"]) - - cursor = dislocker.db.cursor() - - cursor.execute("SELECT * FROM mouse_list WHERE mouse_number=%s", (mouse_number,)) - mouse_list_record = cursor.fetchall() - if mouse_list_record[0][1] == None: + if kwargs["mouse_number"] == None: return {"result": 0, "about": "ok"} else: - return {"result": 1, "about": "mouse_already_in_use_by_other"} + mouse_number = int(kwargs["mouse_number"]) + + cursor = dislocker.db.cursor() + cursor.execute("SELECT * FROM mouse_list WHERE mouse_number=%s", (mouse_number,)) + mouse_list_record = cursor.fetchall() + if mouse_list_record[0][1] == None: + return {"result": 0, "about": "ok"} + else: + return {"result": 1, "about": "mouse_already_in_use_by_other"} except Exception as error: print("マウスの使用状況を調査中にエラーが発生しました。\nエラー内容") From 2d5ea6a48d2227a7ff43d05b1782edcaacbb552f Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 01:18:02 +0900 Subject: [PATCH 016/175] =?UTF-8?q?PC=E3=81=AE=E4=BD=BF=E7=94=A8=E7=99=BB?= =?UTF-8?q?=E9=8C=B2=E3=81=A7=E3=83=87=E3=83=BC=E3=82=BF=E3=83=99=E3=83=BC?= =?UTF-8?q?=E3=82=B9=E3=81=AE=E3=82=B3=E3=83=9F=E3=83=83=E3=83=88=E3=82=92?= =?UTF-8?q?=E5=BF=98=E3=82=8C=E3=81=A6=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dislocker.py b/dislocker.py index 1dab5b8..b348162 100644 --- a/dislocker.py +++ b/dislocker.py @@ -352,6 +352,7 @@ class Bot(discord.Client): pass else: cursor.execute("UPDATE mouse_list SET using_member_id = %s WHERE mouse_number = %s", (member_id, user_info["mouse_number"])) + dislocker.db.commit() return {"result": 0, "about": "ok", "output": {"password": str(password), "name": str(discord_user_name)}} else: return {"result": 1, "about": "mouse_already_in_use"} From 1e8ef35e3fc9f18f86995144f671bfa853688126 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 01:30:58 +0900 Subject: [PATCH 017/175] =?UTF-8?q?cursor=E3=81=8C=E8=A6=8B=E3=81=A4?= =?UTF-8?q?=E3=81=8B=E3=82=89=E3=81=AA=E3=81=84=E3=82=A8=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3=E3=81=97=E3=81=9F=E3=81=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/dislocker.py b/dislocker.py index b348162..7f5cf34 100644 --- a/dislocker.py +++ b/dislocker.py @@ -237,7 +237,8 @@ class Bot(discord.Client): return {"result": 1, "about": "error"} finally: - cursor.close() + if cursor: + cursor.close() def keyboard_used_check(self, **kwargs): try: @@ -263,7 +264,8 @@ class Bot(discord.Client): return {"result": 1, "about": "error"} finally: - cursor.close() + if cursor: + cursor.close() def mouse_used_check(self, **kwargs): try: @@ -289,11 +291,13 @@ class Bot(discord.Client): return {"result": 1, "about": "error"} finally: - cursor.close() + if cursor: + cursor.close() def register(self, **kwargs): try: + cursor = None user_info = { "id": str(kwargs["user_id"]), "name": str(kwargs["name"]), @@ -317,7 +321,6 @@ class Bot(discord.Client): pass else: user_info["mouse_number"] = int(kwargs["mouse_number"]) - cursor = dislocker.db.cursor() # ユーザー登録されているかの確認 user_register = self.user_register_check(discord_user_id=user_info["id"]) if user_register["result"] == 0: @@ -339,6 +342,7 @@ class Bot(discord.Client): password = self.password_generate(4) password_hash = self.hash_genarate(password) # PC使用履歴のテーブルにレコードを挿入 + cursor = dislocker.db.cursor() cursor.execute("INSERT INTO pc_usage_history (member_id, pc_number, keyboard_number, mouse_number, start_use_time, use_detail) VALUES (%s, %s, %s, %s, clock_timestamp(), %s)", (member_id, user_info["pc_number"], user_info["keyboard_number"], user_info["mouse_number"], user_info["detail"])) # PCリストの該当のレコードを更新 cursor.execute("UPDATE pc_list SET using_member_id = %s, password_hash = %s WHERE pc_number = %s", (member_id, password_hash, user_info["pc_number"])) @@ -371,7 +375,8 @@ class Bot(discord.Client): print(str(error)) return {"result": 1, "about": "error"} finally: - cursor.close() + if cursor: + cursor.close() def stop(self, **kwargs): @@ -434,7 +439,8 @@ class Bot(discord.Client): return {"result": 1, "about": "error"} finally: - cursor.close() + if cursor: + cursor.close() def user_register(self, **kwargs): try: @@ -459,7 +465,8 @@ class Bot(discord.Client): return {"result": 1, "about": "error"} finally: - cursor.close() + if cursor: + cursor.close() def format_datetime(self, value): @@ -488,7 +495,8 @@ class Bot(discord.Client): return {"result": 1, "about": "error"} finally: - cursor.close() + if cursor: + cursor.close() def report_export(self, **kwargs): try: @@ -562,7 +570,8 @@ class Bot(discord.Client): return {"result": 1, "about": "error"} finally: - cursor.close() + if cursor: + cursor.close() def force_stop(self, **kwargs): @@ -612,7 +621,8 @@ class Bot(discord.Client): return {"result": 1, "about": "error"} finally: - cursor.close() + if cursor: + cursor.close() async def timeout_notify(self, **kwargs): try: @@ -1002,7 +1012,8 @@ class Monitor(): dislocker.db.rollback() finally: - cursor.close() + if cursor: + cursor.close() print(result["result"]) return result From ca6ff5d05044ba852cb248f053a8e50388533425 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 01:38:54 +0900 Subject: [PATCH 018/175] =?UTF-8?q?=E3=81=93=E3=81=93=E3=83=AD=E3=82=B0?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dislocker.py b/dislocker.py index 7f5cf34..aac1a0b 100644 --- a/dislocker.py +++ b/dislocker.py @@ -342,6 +342,7 @@ class Bot(discord.Client): password = self.password_generate(4) password_hash = self.hash_genarate(password) # PC使用履歴のテーブルにレコードを挿入 + print("ここ") cursor = dislocker.db.cursor() cursor.execute("INSERT INTO pc_usage_history (member_id, pc_number, keyboard_number, mouse_number, start_use_time, use_detail) VALUES (%s, %s, %s, %s, clock_timestamp(), %s)", (member_id, user_info["pc_number"], user_info["keyboard_number"], user_info["mouse_number"], user_info["detail"])) # PCリストの該当のレコードを更新 From aae08d470190a9852f1831475735d0f0a8cdfd25 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 01:42:02 +0900 Subject: [PATCH 019/175] =?UTF-8?q?=E3=81=93=E3=81=93=E3=82=8D=E3=81=90?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A02?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dislocker.py b/dislocker.py index aac1a0b..9ee34f7 100644 --- a/dislocker.py +++ b/dislocker.py @@ -322,6 +322,7 @@ class Bot(discord.Client): else: user_info["mouse_number"] = int(kwargs["mouse_number"]) # ユーザー登録されているかの確認 + print("ここ1") user_register = self.user_register_check(discord_user_id=user_info["id"]) if user_register["result"] == 0: member_id = user_register["user_info"]["member_id"] @@ -331,18 +332,21 @@ class Bot(discord.Client): if pc_check_self["result"] == 0: # 他の人がそのPCを使っているか pc_check = self.pc_used_check(pc_number=user_info["pc_number"]) + print("ここ2") if pc_check["result"] == 0: # キーボードは使われているか keyboard_check = self.keyboard_used_check(keyboard_number=user_info["keyboard_number"]) if keyboard_check["result"] == 0: # マウスは使われているか mouse_check = self.mouse_used_check(mouse_number=user_info["mouse_number"]) + print("ここ3") if mouse_check["result"] == 0: + # パスワードとハッシュ作成 password = self.password_generate(4) password_hash = self.hash_genarate(password) # PC使用履歴のテーブルにレコードを挿入 - print("ここ") + cursor = dislocker.db.cursor() cursor.execute("INSERT INTO pc_usage_history (member_id, pc_number, keyboard_number, mouse_number, start_use_time, use_detail) VALUES (%s, %s, %s, %s, clock_timestamp(), %s)", (member_id, user_info["pc_number"], user_info["keyboard_number"], user_info["mouse_number"], user_info["detail"])) # PCリストの該当のレコードを更新 From 7bade8c366fd8cfe63a5af6c415e0d01c643b6e8 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 01:48:01 +0900 Subject: [PATCH 020/175] =?UTF-8?q?printf=E6=94=BB=E6=92=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/dislocker.py b/dislocker.py index 9ee34f7..aec219a 100644 --- a/dislocker.py +++ b/dislocker.py @@ -297,7 +297,7 @@ class Bot(discord.Client): def register(self, **kwargs): try: - cursor = None + cursor = dislocker.db.cursor() user_info = { "id": str(kwargs["user_id"]), "name": str(kwargs["name"]), @@ -322,7 +322,6 @@ class Bot(discord.Client): else: user_info["mouse_number"] = int(kwargs["mouse_number"]) # ユーザー登録されているかの確認 - print("ここ1") user_register = self.user_register_check(discord_user_id=user_info["id"]) if user_register["result"] == 0: member_id = user_register["user_info"]["member_id"] @@ -332,22 +331,21 @@ class Bot(discord.Client): if pc_check_self["result"] == 0: # 他の人がそのPCを使っているか pc_check = self.pc_used_check(pc_number=user_info["pc_number"]) - print("ここ2") + print(pc_check) if pc_check["result"] == 0: # キーボードは使われているか keyboard_check = self.keyboard_used_check(keyboard_number=user_info["keyboard_number"]) + print(keyboard_check) if keyboard_check["result"] == 0: # マウスは使われているか mouse_check = self.mouse_used_check(mouse_number=user_info["mouse_number"]) - print("ここ3") + print(mouse_check) if mouse_check["result"] == 0: - # パスワードとハッシュ作成 password = self.password_generate(4) password_hash = self.hash_genarate(password) # PC使用履歴のテーブルにレコードを挿入 - cursor = dislocker.db.cursor() cursor.execute("INSERT INTO pc_usage_history (member_id, pc_number, keyboard_number, mouse_number, start_use_time, use_detail) VALUES (%s, %s, %s, %s, clock_timestamp(), %s)", (member_id, user_info["pc_number"], user_info["keyboard_number"], user_info["mouse_number"], user_info["detail"])) # PCリストの該当のレコードを更新 cursor.execute("UPDATE pc_list SET using_member_id = %s, password_hash = %s WHERE pc_number = %s", (member_id, password_hash, user_info["pc_number"])) From 3160f1c079b6928507eb45012965afb6c6d7ee59 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 01:53:09 +0900 Subject: [PATCH 021/175] =?UTF-8?q?=E5=BF=B5=E3=81=AE=E7=82=BA=E3=80=81?= =?UTF-8?q?=E3=83=9E=E3=82=A6=E3=82=B9=E3=81=A8=E3=82=AD=E3=83=BC=E3=83=9C?= =?UTF-8?q?=E3=83=BC=E3=83=89=E4=BD=BF=E7=94=A8=E7=A2=BA=E8=AA=8D=E3=81=AE?= =?UTF-8?q?cursor=E3=82=92=E4=B8=8A=E3=81=AB=E6=8C=81=E3=81=A3=E3=81=A6?= =?UTF-8?q?=E3=81=84=E3=81=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dislocker.py b/dislocker.py index aec219a..f0ae3b9 100644 --- a/dislocker.py +++ b/dislocker.py @@ -242,12 +242,11 @@ class Bot(discord.Client): def keyboard_used_check(self, **kwargs): try: + cursor = dislocker.db.cursor() if kwargs["keyboard_number"] == None: return {"result": 0, "about": "ok"} else: keyboard_number = int(kwargs["keyboard_number"]) - - cursor = dislocker.db.cursor() cursor.execute("SELECT * FROM keyboard_list WHERE keyboard_number=%s", (keyboard_number,)) keyboard_list_record = cursor.fetchall() @@ -269,12 +268,11 @@ class Bot(discord.Client): def mouse_used_check(self, **kwargs): try: + cursor = dislocker.db.cursor() if kwargs["mouse_number"] == None: return {"result": 0, "about": "ok"} else: mouse_number = int(kwargs["mouse_number"]) - - cursor = dislocker.db.cursor() cursor.execute("SELECT * FROM mouse_list WHERE mouse_number=%s", (mouse_number,)) mouse_list_record = cursor.fetchall() From ef0bd4b5090d697027fa96cfbfaf7ca159a2daa8 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 02:04:41 +0900 Subject: [PATCH 022/175] =?UTF-8?q?Monitor=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/dislocker.py b/dislocker.py index f0ae3b9..e298416 100644 --- a/dislocker.py +++ b/dislocker.py @@ -358,7 +358,7 @@ class Bot(discord.Client): else: cursor.execute("UPDATE mouse_list SET using_member_id = %s WHERE mouse_number = %s", (member_id, user_info["mouse_number"])) dislocker.db.commit() - return {"result": 0, "about": "ok", "output": {"password": str(password), "name": str(discord_user_name)}} + return {"result": 0, "about": "ok", "output_dict": {"password": str(password), "name": str(discord_user_name)}} else: return {"result": 1, "about": "mouse_already_in_use"} else: @@ -948,20 +948,20 @@ class Monitor(): 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,)) + member_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", (member_id,)) pc_usage = cursor.fetchall() print(pc_usage) - start_time = pc_usage[0][4] + start_time = pc_usage[0][5] 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,)) + cursor.execute("SELECT * FROM club_member WHERE id = %s", (member_id,)) user_info = cursor.fetchall() - stop = bot.stop(user_id=user_info[0][3], bot_about="パスワードのタイムアウトでBotによる強制停止。") + stop = bot.stop(discord_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)} @@ -972,19 +972,19 @@ class Monitor(): elif len(pc_list) >= 2: for i in pc_list: print(i) - 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,)) + member_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", (member_id,)) pc_usage = cursor.fetchall() print(pc_usage) - start_time = pc_usage[0][4] + start_time = pc_usage[0][5] 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,)) + cursor.execute("SELECT * FROM club_member WHERE id = %s", (member_id,)) user_info = cursor.fetchall() - stop = bot.stop(user_id=user_info[0][3], bot_about="タイムアウトでBotによる強制停止。") + stop = bot.stop(discord_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)} @@ -1047,8 +1047,8 @@ class Reason(Modal): print(register) if register["about"] == "ok": - await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["password"]}\n## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\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} の使用を開始しました。\n>>> ## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\n## 使用目的 | {self.reason_input_form.value}') + await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\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["output_dict"]["name"]} さんがPC {pc_number} の使用を開始しました。\n>>> ## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\n## 使用目的 | {self.reason_input_form.value}') elif register["about"] == "pc_already_in_use_by_you": pc_usage_history = register["pc_usage_history"] await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {pc_usage_history["keyboard_number"]}\n# マウス番号 | {pc_usage_history["mouse_number"]}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) From 698e6290635e1b32470093d6f00a70610f2ea64a Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 02:09:13 +0900 Subject: [PATCH 023/175] =?UTF-8?q?export=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker.py b/dislocker.py index e298416..1dbc164 100644 --- a/dislocker.py +++ b/dislocker.py @@ -515,7 +515,7 @@ class Bot(discord.Client): query = sql.SQL(""" SELECT {main_columns}, {related_table}.name FROM {main_table} - LEFT JOIN {related_table} ON {main_table}.member_id = {related_table}.id + LEFT JOIN {related_table} ON {main_table}.member_id = {related_table}.member_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]), From 668af84ca898587d62b6936d82d02111aa106a99 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 02:12:10 +0900 Subject: [PATCH 024/175] =?UTF-8?q?PC=E4=BD=BF=E7=94=A8=E9=96=8B=E5=A7=8B?= =?UTF-8?q?=E6=99=82=E3=81=AB=E7=AE=A1=E7=90=86=E8=80=85=E5=81=B4=E3=81=AB?= =?UTF-8?q?=E9=80=81=E3=82=8B=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker.py b/dislocker.py index 1dbc164..1b6b84c 100644 --- a/dislocker.py +++ b/dislocker.py @@ -1048,7 +1048,7 @@ class Reason(Modal): if register["about"] == "ok": await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\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["output_dict"]["name"]} さんがPC {pc_number} の使用を開始しました。\n>>> ## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\n## 使用目的 | {self.reason_input_form.value}') + await bot.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':white_check_mark: {register["output_dict"]["name"]} さんがPC {pc_number} の使用を開始しました。\n>>> ## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\n## 使用目的 | {self.reason_input_form.value}') elif register["about"] == "pc_already_in_use_by_you": pc_usage_history = register["pc_usage_history"] await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {pc_usage_history["keyboard_number"]}\n# マウス番号 | {pc_usage_history["mouse_number"]}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) From 35ba7e0dff4fd5e051ef5b7131258a72fac06641 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 02:17:10 +0900 Subject: [PATCH 025/175] =?UTF-8?q?=E3=81=9D=E3=82=8C=E3=81=9E=E3=82=8C?= =?UTF-8?q?=E5=8F=82=E7=85=A7=E3=81=99=E3=82=8B=E3=82=AB=E3=83=A9=E3=83=A0?= =?UTF-8?q?=E5=90=8D=E3=81=AE=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 8 ++++---- dislocker_auth.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dislocker.py b/dislocker.py index 1b6b84c..a4ffa29 100644 --- a/dislocker.py +++ b/dislocker.py @@ -422,7 +422,7 @@ class Bot(discord.Client): pass else: # mouse_listの使用中ユーザーを消す - cursor.execute("UPDATE mouse_list SET using_member_id = NULL WHERE keyboard_number = %s", (mouse_number,)) + cursor.execute("UPDATE mouse_list SET using_member_id = NULL WHERE mouse_number = %s", (mouse_number,)) dislocker.db.commit() return {"result": 0, "about": "ok", "output_dict": {"pc_number": str(pc_number), "name": str(name)}} else: @@ -607,7 +607,7 @@ class Bot(discord.Client): pass else: # mouse_listの使用中ユーザーを消す - cursor.execute("UPDATE mouse_list SET using_member_id = NULL WHERE keyboard_number = %s", (mouse_number,)) + cursor.execute("UPDATE mouse_list SET using_member_id = NULL WHERE mouse_number = %s", (mouse_number,)) cursor.execute("UPDATE pc_usage_history SET end_use_time = clock_timestamp(), bot_about = %s WHERE id = %s", (bot_about, pc_usage_history_record_id)) dislocker.db.commit() return {"result": 0, "about": "ok"} @@ -959,7 +959,7 @@ class Monitor(): 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", (member_id,)) + cursor.execute("SELECT * FROM club_member WHERE member_id = %s", (member_id,)) user_info = cursor.fetchall() stop = bot.stop(discord_user_id=user_info[0][3], bot_about="パスワードのタイムアウトでBotによる強制停止。") @@ -982,7 +982,7 @@ class Monitor(): 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", (member_id,)) + cursor.execute("SELECT * FROM club_member WHERE member_id = %s", (member_id,)) user_info = cursor.fetchall() stop = bot.stop(discord_user_id=user_info[0][3], bot_about="タイムアウトでBotによる強制停止。") diff --git a/dislocker_auth.py b/dislocker_auth.py index 6194034..57149b4 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -129,7 +129,7 @@ class Auth(): pass else: # mouse_listの使用中ユーザーを消す - cursor.execute("UPDATE mouse_list SET using_member_id = NULL WHERE keyboard_number = %s", (mouse_number,)) + cursor.execute("UPDATE mouse_list SET using_member_id = NULL WHERE mouse_number = %s", (mouse_number,)) cursor.execute("UPDATE pc_usage_history SET end_use_time = clock_timestamp() WHERE id = %s", (pc_usage_history_record_id,)) self.db.commit() return {"result": 0, "about": "ok"} From 26f5269ce555625103db6140a99935869fbe8317 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 02:24:42 +0900 Subject: [PATCH 026/175] =?UTF-8?q?PC=E4=BD=BF=E7=94=A8=E7=99=BB=E9=8C=B2?= =?UTF-8?q?=E6=99=82=E7=AE=A1=E7=90=86=E8=80=85=E5=81=B4=E3=81=AE=E3=83=A1?= =?UTF-8?q?=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=81=A7Discord=E3=81=AE?= =?UTF-8?q?=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC=E5=90=8D=E3=81=8C=E8=A6=8B?= =?UTF-8?q?=E3=81=88=E3=82=8B=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dislocker.py b/dislocker.py index a4ffa29..0470ee0 100644 --- a/dislocker.py +++ b/dislocker.py @@ -323,7 +323,7 @@ class Bot(discord.Client): user_register = self.user_register_check(discord_user_id=user_info["id"]) if user_register["result"] == 0: member_id = user_register["user_info"]["member_id"] - discord_user_name = user_register["user_info"]["discord_user_name"] + name = user_register["user_info"]["name"] # ユーザーがPCを使っているか pc_check_self = self.pc_used_check(member_id=member_id) if pc_check_self["result"] == 0: @@ -358,7 +358,7 @@ class Bot(discord.Client): else: cursor.execute("UPDATE mouse_list SET using_member_id = %s WHERE mouse_number = %s", (member_id, user_info["mouse_number"])) dislocker.db.commit() - return {"result": 0, "about": "ok", "output_dict": {"password": str(password), "name": str(discord_user_name)}} + return {"result": 0, "about": "ok", "output_dict": {"password": str(password), "name": str(name)}} else: return {"result": 1, "about": "mouse_already_in_use"} else: From d433f38d94baa3a2aa960fd2ef41e3d1de037e9a Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 02:28:23 +0900 Subject: [PATCH 027/175] =?UTF-8?q?stop=E3=81=AEKeyError=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/dislocker.py b/dislocker.py index 0470ee0..47f9e8d 100644 --- a/dislocker.py +++ b/dislocker.py @@ -382,12 +382,13 @@ class Bot(discord.Client): def stop(self, **kwargs): try: - discord_user_id = str(kwargs["user_id"]) + cursor = dislocker.db.cursor() + discord_user_id = str(kwargs["discord_user_id"]) if "bot_about" in kwargs: bot_about = kwargs["bot_about"] else: bot_about = None - cursor = dislocker.db.cursor() + # ユーザーが登録してるかというよりはデータの取得のため user_register = self.user_register_check(discord_user_id=discord_user_id) member_id = user_register["user_info"]["member_id"] @@ -398,7 +399,7 @@ class Bot(discord.Client): pc_usage_history_record = cursor.fetchall() if pc_usage_history_record: - usage_id = pc_usage_history_record[0][0] + pc_usage_history_id = pc_usage_history_record[0][0] pc_number = pc_usage_history_record[0][2] keyboard_number = pc_usage_history_record[0][3] mouse_number = pc_usage_history_record[0][4] @@ -408,9 +409,9 @@ class Bot(discord.Client): if end_use_time == None: # 利用停止の理由の有無を判断 if bot_about == None: - cursor.execute("UPDATE pc_usage_history SET end_use_time = clock_timestamp() WHERE id = %s", (usage_id,)) + cursor.execute("UPDATE pc_usage_history SET end_use_time = clock_timestamp() WHERE id = %s", (pc_usage_history_id,)) else: - cursor.execute("UPDATE pc_usage_history SET end_use_time = clock_timestamp(), bot_about = %s WHERE id = %s", (bot_about, usage_id)) + cursor.execute("UPDATE pc_usage_history SET end_use_time = clock_timestamp(), bot_about = %s WHERE id = %s", (bot_about, pc_usage_history_id)) # pc_listの使用中ユーザーを消す cursor.execute("UPDATE pc_list SET using_member_id = NULL, password_hash = NULL WHERE pc_number = %s", (pc_number,)) if keyboard_number == None: From 2ddcc6626a5d770d6228c7b728f85feee9e800bf Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 23 Aug 2024 02:33:17 +0900 Subject: [PATCH 028/175] =?UTF-8?q?user=5Fid=E3=82=92discord=5Fuser=5Fid?= =?UTF-8?q?=E3=81=AB=E5=85=A8=E9=9D=A2=E7=9A=84=E3=81=AB=E6=94=B9=E3=82=81?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dislocker.py b/dislocker.py index 47f9e8d..8065d33 100644 --- a/dislocker.py +++ b/dislocker.py @@ -297,7 +297,7 @@ class Bot(discord.Client): try: cursor = dislocker.db.cursor() user_info = { - "id": str(kwargs["user_id"]), + "id": str(kwargs["discord_user_id"]), "name": str(kwargs["name"]), "display_name": str(kwargs["display_name"]), "pc_number": int(kwargs["pc_number"]), @@ -898,7 +898,7 @@ class Bot(discord.Client): elif custom_id_split[0] == "stop": print("STOP running") - pc_stop = self.stop(user_id=interaction.user.id) + pc_stop = self.stop(discord_user_id=interaction.user.id) print(pc_stop) stop_view = View(timeout=15) if pc_stop["about"] == "unused": @@ -1044,7 +1044,7 @@ class Reason(Modal): else: mouse_number_show = mouse_number - register = bot.register(user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=self.reason_input_form.value) + register = bot.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=self.reason_input_form.value) print(register) if register["about"] == "ok": From bf1dc817ab3b7ac82dc8afd8b7f7bc0524ddbeb9 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 25 Aug 2024 23:28:36 +0900 Subject: [PATCH 029/175] =?UTF-8?q?=E3=82=AF=E3=83=A9=E3=82=A4=E3=82=A2?= =?UTF-8?q?=E3=83=B3=E3=83=88=E7=99=BB=E9=8C=B2=E6=99=82=E3=81=AE=E3=83=AF?= =?UTF-8?q?=E3=83=B3=E3=82=BF=E3=82=A4=E3=83=A0=E3=83=91=E3=82=B9=E3=83=AF?= =?UTF-8?q?=E3=83=BC=E3=83=89=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 27 ++++++- dislocker_auth.py | 106 +++++++++++++++++++++--- dislocker_client.py | 191 ++++++++++++++++++++++++++++---------------- 3 files changed, 241 insertions(+), 83 deletions(-) diff --git a/dislocker.py b/dislocker.py index 8065d33..782b287 100644 --- a/dislocker.py +++ b/dislocker.py @@ -18,6 +18,7 @@ class DL(): self.config_dir_path = "./config/" self.export_dir_path = "./export/" self.server_config_path = self.config_dir_path + "server.json" + self.onetime_config_path = self.config_dir_path + "onetime.json" try: if not os.path.isdir(self.config_dir_path): print("config ディレクトリが見つかりません... 作成します。") @@ -48,7 +49,8 @@ class DL(): "search_frequency": 1, "allowable_time": 180, "fstop_time": "21:00:00" - } + }, + "admin_user_id": "TYPE HERE CHANNEL ID (YOU MUST USE INT !!!!)" } } @@ -88,7 +90,7 @@ class DL(): 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_member_id INTEGER, password_hash VARCHAR(32), PRIMARY KEY (pc_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") + cursor.execute("CREATE TABLE pc_list (pc_number INTEGER NOT NULL, using_member_id INTEGER, password_hash VARCHAR(32), pc_uuid UUID, pc_token VARCHAR(36), PRIMARY KEY (pc_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") for i in self.pc_list: print(i) cursor.execute("INSERT INTO pc_list (pc_number) VALUES (%s)", (i,)) @@ -664,8 +666,27 @@ class Bot(discord.Client): pass elif isinstance(message.channel, discord.DMChannel): + + if message.author.id == dislocker.server_config["bot"]["admin_user_id"]: + msg_split = message.content.split() + if msg_split[0] == "/pcreg": + if os.path.isfile(dislocker.onetime_config_path): + with open(dislocker.onetime_config_path, "r") as r: + onetime_config = json.load(r) + onetime = str(onetime_config["onetime"]) + await message.channel.send(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {onetime}") + + else: + onetime = str(self.password_generate()) + onetime_config = { + "onetime": str(onetime) + } + with open(dislocker.onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) + await message.channel.send(f"# :dizzy_face: PC登録時のワンタイムパスワードを発行します。\n# パスワード | {onetime}") + + """ - msg_split = message.content.split() if msg_split[0] == "/password" or msg_split[0] == "/start": #メッセージの要素が2つ以下の場合は拒否 if len(msg_split) <= 2: diff --git a/dislocker_auth.py b/dislocker_auth.py index 57149b4..b2f10ba 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -2,9 +2,13 @@ import psycopg2 import os import json from flask import Flask, request, jsonify, render_template +import uuid +import string +import random config_dir_path = "./config/" server_config_path = config_dir_path + "server.json" +onetime_config_path = config_dir_path + "onetime.json" if not os.path.isfile(server_config_path): if not os.path.isdir(config_dir_path): os.mkdir(config_dir_path) @@ -40,16 +44,38 @@ class Auth(): def __init__(self, host, db, port, user, password): self.db = psycopg2.connect(f"host={host} dbname={db} port={port} user={user} password={password}") - def check(self, pc_number, password): + def token_generate(self, length): + letters = string.ascii_letters + string.digits + password = ''.join(random.choice(letters) for _ in range(length)) + return password + + def check(self, **kwargs): try: cursor = self.db.cursor() - cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s AND password_hash = %s", (pc_number, password)) - pc_info = cursor.fetchall() + pc_number = int(kwargs["pc_number"]) + pc_uuid = uuid.UUID(kwargs["pc_uuid"]) + pc_token = str(kwargs["pc_token"]) + + if "password_hash" in kwargs: + password_hash = str(kwargs["password_hash"]) + cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s AND pc_uuid = %s, AND pc_token = %s", (pc_number, pc_uuid, pc_token)) + pc_info = cursor.fetchall() + else: + cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s AND password_hash = %s AND pc_uuid = %s, AND pc_token = %s", (pc_number, password_hash, pc_uuid, pc_token)) + pc_info = cursor.fetchall() + if pc_info: return {"result": 0, "about": "ok"} else: return {"result": 1, "about": "unregistered_pc"} + except Exception as error: + print("PCの登録状況を調査中にエラーが発生しました。\nエラー内容") + print(str(error.__class__.__name__)) + print(str(error.args)) + print(str(error)) + return {"result": 1, "about": "error"} + finally: cursor.close() @@ -144,17 +170,64 @@ class Auth(): finally: cursor.close() + def register(self, **kwargs): + try: + cursor = self.db.cursor() + pc_number = int(kwargs["pc_number"]) + pc_uuid = uuid.UUID(kwargs["pc_uuid"]) + cursor.execute("SELECT uuid FROM pc_list WHERE pc_number = %s", (pc_number,)) + pc_record = cursor.fetchall() + pc_record_uuid = pc_record[0][0] + if pc_record_uuid == None: + return {"result": 1, "about": "exist"} + else: + pc_token = self.token_generate(36) + cursor.execute("UPDATE pc_list SET pc_uuid = %s, pc_token = %s WHERE pc_number = %s", (pc_uuid, pc_token, pc_number)) + self.db.commit() + os.remove(onetime_config_path) + return {"result": 0, "about": "ok", "output_dict": {"pc_token": pc_token}} + + except Exception as error: + print("停止処理中にエラーが発生しました。\nエラー内容") + print(str(error.__class__.__name__)) + print(str(error.args)) + print(str(error)) + return {"result": 1, "about": "error"} + + finally: + cursor.close() app = Flask(__name__, static_folder="./resource/") 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('/register', methods=['POST']) +def register(): + pc_number = int(request.json.get('pc_number')) + pc_uuid = str(request.json.get('pc_uuid')) + onetime_password = int(request.json.get('onetime')) + + if os.path.isfile(onetime_config_path): + with open(onetime_config_path, "r") as r: + onetime_config = json.load(r) + + if onetime_password == onetime_config["onetime"]: + register_result = auth.register(pc_number=pc_number, pc_uuid=pc_uuid) + pc_token = register_result["output_dict"]["pc_token"] + return jsonify({'message': 'ok', 'pc_token': pc_token}), 200 + else: + return jsonify({'message': 'damedesu'}), 401 + else: + return jsonify({'message': 'damedesu'}), 401 + @app.route('/verify', methods=['POST']) def verify(): pc_number = int(request.json.get('pc_number')) - password = request.json.get('password') + password_hash = request.json.get('password') + pc_uuid = request.json.get('pc_uuid') + pc_token = request.json.get('pc_token') print(str(pc_number) + "の認証処理を開始...") - pc_auth = auth.check(pc_number, password) + pc_auth = auth.check(pc_number=pc_number, password_hash=password_hash, pc_uuid=pc_uuid, pc_token=pc_token) if pc_auth["result"] == 0: auth.delete(pc_number) @@ -166,16 +239,23 @@ def verify(): @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"] == 0: - print(str(pc_number) + "の使用停止処理は成功しました.") - return jsonify({'message': 'ok'}), 200 - else: - print(str(pc_number) + "の使用停止処理は失敗しました.") - return jsonify({'message': 'error'}), 500 + pc_number = int(request.json.get('pc_number')) + pc_uuid = request.json.get('pc_uuid') + pc_token = request.json.get('pc_token') + pc_auth = auth.check(pc_number=pc_number, pc_uuid=pc_uuid, pc_token=pc_token) + + if pc_auth["result"] == 0: + pc_stop = auth.stop(pc_number=pc_number) + if pc_stop["result"] == 0: + print(str(pc_number) + "の使用停止処理は成功しました.") + return jsonify({'message': 'ok'}), 200 + else: + print(str(pc_number) + "の使用停止処理は失敗しました.") + return jsonify({'message': 'error'}), 500 + else: + return jsonify({'message': 'damedesu'}), 401 if __name__ == '__main__': diff --git a/dislocker_client.py b/dislocker_client.py index 79c5bd7..e6be117 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -11,9 +11,10 @@ import string import random import tkinter import threading -import signal import sys import shutil +import uuid +import time app_name = "Dislocker" dislocker_dir = os.path.dirname(os.path.abspath(sys.argv[0])) @@ -32,13 +33,22 @@ if not os.path.isfile(client_config_path): "auth_host_url": "http://localhost", "pc_number": 1, "master_password_hash": "", - "testing": 0 + "testing": 0, + "pc_uuid": "", + "pc_token": "" } elif os.path.isfile(client_config_path): with open(client_config_path, "r") as r: client_config = json.load(r) +def master_password_gen(): + numbers = string.digits # (1) + password = ''.join(random.choice(numbers) for _ in range(10)) # (2) + password_hash = hashlib.md5(password.encode()).hexdigest() + result = {"password": password, "password_hash": password_hash} + return result + def init(**kwargs): sp_startupinfo = subprocess.STARTUPINFO() sp_startupinfo.dwFlags = subprocess.STARTF_USESHOWWINDOW @@ -52,17 +62,46 @@ def init(**kwargs): 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 + pc_uuid = uuid.uuid4() + client_config["pc_uuid"] = str(pc_uuid) + 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 + tkinter.messagebox.showerror(title=f"{app_name} | 登録時にエラー", message=f"登録時にエラーが発生しました。\nPC番号が指定されていません。1個目の引数にPC番号、2個目の引数にワンタイムパスワードを指定して、もう一度お試しください。") + return 1 + + if "onetime" in kwargs: + onetime = str(kwargs["onetime"]) + else: + tkinter.messagebox.showerror(title=f"{app_name} | 登録時にエラー", message=f"登録時にエラーが発生しました。\nワンタイムパスワードが指定されていません。1個目の引数にPC番号、2個目の引数にワンタイムパスワードを指定して、もう一度お試しください。") + return 1 + + register_url = client_config["auth_host_url"] + "/register" + register_json = { + "pc_number": int(client_config["pc_number"]), + "pc_uuid": str(pc_uuid), + "onetime": onetime + } + + responce = requests.post(register_url, json=register_json) + + if responce.status_code == 200: + print("PCの情報が登録されました。") + pc_token = str(responce.json["pc_token"]) + client_config["pc_token"] = pc_token + + 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 + + with open(client_config_path, "w") as w: + json.dump(client_config, w, indent=4) + return 2 + else: + msgbox = tkinter.messagebox.showerror(title=f"{app_name} | 登録時にエラー", message=f"登録時にエラーが発生しました。\nワンタイムパスワードが間違っている可能性があります。") + return 1 else: return 0 @@ -89,37 +128,6 @@ class App(customtkinter.CTk): self.unlock_taskmgr() self.toast() 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): block_keys = ['ctrl', 'alt', 'windows', 'shift', 'delete'] @@ -263,6 +271,7 @@ class Lock(customtkinter.CTkToplevel): 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())) @@ -282,6 +291,8 @@ class Lock(customtkinter.CTkToplevel): auth_url = client_config["auth_host_url"] + "/verify" auth_json = { "pc_number": int(client_config["pc_number"]), + "pc_uuid": str(client_config["pc_uuid"]), + "pc_token": str(client_config["pc_token"]), "password": self.hash_genarate(str(self.password_entry.get())) } try: @@ -349,17 +360,73 @@ class Help(customtkinter.CTkToplevel): def handler_close(self): self.destroy() - -class Monitor(): + + +class Stop(): def __init__(self) -> None: pass - def start(self): - monitor_thread = threading.Thread(target=self.run) - monitor_thread.start() + def run(self): + stop_thread = threading.Thread(target=self.stop) + stop_thread.run() + run_notify = Notification( + app_id='Dislocker', + title='終了処理を実行中', + msg='終了処理を実行しています。\nPCがシャットダウンするまで、そのままでお待ち下さい。', + icon=resource_path + r'\success.png' + ) + run_notify.set_audio(audio.Default, loop=False) + run_notify.show() - def signal_handler(self): + 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} を終了しました。") + + time.sleep(0.1) + + # ディレクトリの削除 + i = 1 + ic = 0 + while i == 1: + shutil.rmtree(dir_path) + if os.path.isdir(dir_path): + ic += 1 + if ic == 10: + i = 0 + else: + i = 0 + 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 shutdown(self): + shutdown_command = subprocess.run(['shutdown', '/s', '/t', '1']) + + def stop(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") @@ -370,12 +437,17 @@ class Monitor(): 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"]) + "pc_number": int(client_config["pc_number"]), + "pc_uuid": str(client_config["pc_uuid"]), + "pc_token": str(client_config["pc_token"]) } try: responce = requests.post(stop_url, json=stop_json) if responce.status_code == 200: print("停止処理は成功しました。") + elif responce.status_code == 401: + print("認証に失敗しました。") + tkinter.messagebox.showwarning(title=f"{app_name} | エラー", message=f"認証に失敗しました。\nDiscordサーバーの指示に従って、停止処理を自身で行ってください。") else: print("内部エラーにより停止処理に失敗しました。") result_msgbox = tkinter.messagebox.showwarning(title=f"{app_name} | エラー", message=f"内部エラーにより停止処理に失敗しました。\nDiscordサーバーの指示に従って、停止処理を自身で行ってください。") @@ -385,21 +457,6 @@ class Monitor(): 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(): - numbers = string.digits # (1) - password = ''.join(random.choice(numbers) for _ in range(10)) # (2) - password_hash = hashlib.md5(password.encode()).hexdigest() - result = {"password": password, "password_hash": password_hash} - return result - if __name__ == '__main__': args = sys.argv @@ -411,11 +468,11 @@ if __name__ == '__main__': elif init_result == 2: pass else: - app = App() - monitor = Monitor() - monitor.signal_handler() + stop = Stop() + stop.run() + elif args[1] == "setup": - init_result = init(pc_number=args[2]) + init_result = init(pc_number=args[2], onetime=args[3]) if init_result == 1: warning_msgbox = tkinter.messagebox.showwarning(title=f"{app_name} | 多重起動エラー", message=f"すでに {app_name} は実行されています。\n正常に起動しない場合は、既に起動しているプロセスを終了してから、もう一度起動してみてください。") elif init_result == 2: From de8412e4eb5bc8501c25c95e72df7ead9f62db55 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 25 Aug 2024 23:35:02 +0900 Subject: [PATCH 030/175] =?UTF-8?q?=E3=83=AF=E3=83=B3=E3=82=BF=E3=82=A4?= =?UTF-8?q?=E3=83=A0=E3=83=91=E3=82=B9=E3=83=AF=E3=83=BC=E3=83=89=E3=81=AE?= =?UTF-8?q?=E9=95=B7=E3=81=95=E3=82=92=E6=B1=BA=E5=AE=9A(8=E6=96=87?= =?UTF-8?q?=E5=AD=97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dislocker.py b/dislocker.py index 782b287..3309753 100644 --- a/dislocker.py +++ b/dislocker.py @@ -666,7 +666,6 @@ class Bot(discord.Client): pass elif isinstance(message.channel, discord.DMChannel): - if message.author.id == dislocker.server_config["bot"]["admin_user_id"]: msg_split = message.content.split() if msg_split[0] == "/pcreg": @@ -677,14 +676,17 @@ class Bot(discord.Client): await message.channel.send(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {onetime}") else: - onetime = str(self.password_generate()) + onetime = str(self.password_generate(8)) onetime_config = { "onetime": str(onetime) } with open(dislocker.onetime_config_path, "w") as w: json.dump(onetime_config, w, indent=4) await message.channel.send(f"# :dizzy_face: PC登録時のワンタイムパスワードを発行します。\n# パスワード | {onetime}") - + else: + await message.channel.send("# :warning: DMでの応答は、現在無効化されています。") + else: + await message.channel.send("# :warning: DMでの応答は、現在無効化されています。") """ if msg_split[0] == "/password" or msg_split[0] == "/start": @@ -727,7 +729,7 @@ class Bot(discord.Client): await message.channel.send(f":white_check_mark: 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"]} の使用を終了しました。') """ - await message.channel.send("# :warning: DMでの応答は、現在無効化されています。") + elif message.channel.id == dislocker.server_config["bot"]["config_channel_id"]: msg_split = message.content.split() From bd1aa2243cfb1dd12e9de21344a9ddbf59e24645 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 25 Aug 2024 23:47:14 +0900 Subject: [PATCH 031/175] =?UTF-8?q?=E3=83=AF=E3=83=B3=E3=82=BF=E3=82=A4?= =?UTF-8?q?=E3=83=A0=E3=83=91=E3=82=B9=E3=83=AF=E3=83=BC=E3=83=89=E3=82=92?= =?UTF-8?q?=E5=85=A5=E5=8A=9B=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/setup.cmd | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/script/setup.cmd b/script/setup.cmd index bfbfa42..62c2e0f 100644 --- a/script/setup.cmd +++ b/script/setup.cmd @@ -16,6 +16,7 @@ if %ERRORLEVEL% == 0 ( if %ERRORLEVEL% == 1 ( echo V[gJbg̍쐬ŃG[܂B ) -set /P pc_number=PCԍ -start %dir%dislocker_client.exe setup %pc_number% +set /P pc_number=PCԍ +set /P onetime=^CpX[h +start %dir%dislocker_client.exe setup %pc_number% %onetime% pause \ No newline at end of file From 665b53c28d922ac2390123cb865c44062b6ffe8e Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 25 Aug 2024 23:58:53 +0900 Subject: [PATCH 032/175] =?UTF-8?q?register=E3=81=AE=E3=83=AF=E3=83=B3?= =?UTF-8?q?=E3=82=BF=E3=82=A4=E3=83=A0=E3=83=91=E3=82=B9=E3=83=AF=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=82=92str=E5=9E=8B=E3=81=AB=E6=8C=87=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker_auth.py b/dislocker_auth.py index b2f10ba..a3f227a 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -205,7 +205,7 @@ auth = Auth(server_config["db"]["host"], server_config["db"]["db_name"], server_ def register(): pc_number = int(request.json.get('pc_number')) pc_uuid = str(request.json.get('pc_uuid')) - onetime_password = int(request.json.get('onetime')) + onetime_password = str(request.json.get('onetime')) if os.path.isfile(onetime_config_path): with open(onetime_config_path, "r") as r: From b6b34ce3153443db91e5b3ff5a653668b0ed7737 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Mon, 26 Aug 2024 00:02:24 +0900 Subject: [PATCH 033/175] =?UTF-8?q?=E5=8F=82=E7=85=A7=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=82=AB=E3=83=A9=E3=83=A0=E5=90=8D=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker_auth.py b/dislocker_auth.py index a3f227a..a722cff 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -175,7 +175,7 @@ class Auth(): cursor = self.db.cursor() pc_number = int(kwargs["pc_number"]) pc_uuid = uuid.UUID(kwargs["pc_uuid"]) - cursor.execute("SELECT uuid FROM pc_list WHERE pc_number = %s", (pc_number,)) + cursor.execute("SELECT pc_uuid FROM pc_list WHERE pc_number = %s", (pc_number,)) pc_record = cursor.fetchall() pc_record_uuid = pc_record[0][0] if pc_record_uuid == None: From 27361d40db28251aa487ef2e6d4f9e96a052ce2f Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Mon, 26 Aug 2024 00:04:39 +0900 Subject: [PATCH 034/175] =?UTF-8?q?return=E3=82=B3=E3=83=BC=E3=83=89?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dislocker_client.py b/dislocker_client.py index e6be117..4145871 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -69,13 +69,13 @@ def init(**kwargs): client_config["pc_number"] = int(kwargs["pc_number"]) else: tkinter.messagebox.showerror(title=f"{app_name} | 登録時にエラー", message=f"登録時にエラーが発生しました。\nPC番号が指定されていません。1個目の引数にPC番号、2個目の引数にワンタイムパスワードを指定して、もう一度お試しください。") - return 1 + return 2 if "onetime" in kwargs: onetime = str(kwargs["onetime"]) else: tkinter.messagebox.showerror(title=f"{app_name} | 登録時にエラー", message=f"登録時にエラーが発生しました。\nワンタイムパスワードが指定されていません。1個目の引数にPC番号、2個目の引数にワンタイムパスワードを指定して、もう一度お試しください。") - return 1 + return 2 register_url = client_config["auth_host_url"] + "/register" register_json = { @@ -101,7 +101,7 @@ def init(**kwargs): return 2 else: msgbox = tkinter.messagebox.showerror(title=f"{app_name} | 登録時にエラー", message=f"登録時にエラーが発生しました。\nワンタイムパスワードが間違っている可能性があります。") - return 1 + return 2 else: return 0 From c6c602a34bd75fa99efe7f595ca69938546ac8c9 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Mon, 26 Aug 2024 00:16:53 +0900 Subject: [PATCH 035/175] =?UTF-8?q?if=E6=96=87=E3=81=AE=E3=81=82=E3=81=9F?= =?UTF-8?q?=E3=82=8A=E3=81=AE=E3=83=9F=E3=82=B9=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_auth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dislocker_auth.py b/dislocker_auth.py index a722cff..297f65f 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -179,13 +179,13 @@ class Auth(): pc_record = cursor.fetchall() pc_record_uuid = pc_record[0][0] if pc_record_uuid == None: - return {"result": 1, "about": "exist"} - else: pc_token = self.token_generate(36) cursor.execute("UPDATE pc_list SET pc_uuid = %s, pc_token = %s WHERE pc_number = %s", (pc_uuid, pc_token, pc_number)) self.db.commit() os.remove(onetime_config_path) return {"result": 0, "about": "ok", "output_dict": {"pc_token": pc_token}} + else: + return {"result": 1, "about": "exist"} except Exception as error: print("停止処理中にエラーが発生しました。\nエラー内容") From 25f3ae640409bce86b9a4dbbd0e250df99668890 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Mon, 26 Aug 2024 00:19:53 +0900 Subject: [PATCH 036/175] =?UTF-8?q?UUID=E5=9E=8B=E3=82=92str(VARCHAR)?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 2 +- dislocker_auth.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dislocker.py b/dislocker.py index 3309753..dc4160f 100644 --- a/dislocker.py +++ b/dislocker.py @@ -90,7 +90,7 @@ class DL(): 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_member_id INTEGER, password_hash VARCHAR(32), pc_uuid UUID, pc_token VARCHAR(36), PRIMARY KEY (pc_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") + cursor.execute("CREATE TABLE pc_list (pc_number INTEGER NOT NULL, using_member_id INTEGER, password_hash VARCHAR(32), pc_uuid VARCHAR(36), pc_token VARCHAR(36), PRIMARY KEY (pc_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") for i in self.pc_list: print(i) cursor.execute("INSERT INTO pc_list (pc_number) VALUES (%s)", (i,)) diff --git a/dislocker_auth.py b/dislocker_auth.py index 297f65f..843fb06 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -53,7 +53,7 @@ class Auth(): try: cursor = self.db.cursor() pc_number = int(kwargs["pc_number"]) - pc_uuid = uuid.UUID(kwargs["pc_uuid"]) + pc_uuid = str(kwargs["pc_uuid"]) pc_token = str(kwargs["pc_token"]) if "password_hash" in kwargs: @@ -174,7 +174,7 @@ class Auth(): try: cursor = self.db.cursor() pc_number = int(kwargs["pc_number"]) - pc_uuid = uuid.UUID(kwargs["pc_uuid"]) + pc_uuid = str(kwargs["pc_uuid"]) cursor.execute("SELECT pc_uuid FROM pc_list WHERE pc_number = %s", (pc_number,)) pc_record = cursor.fetchall() pc_record_uuid = pc_record[0][0] From 4366aa06cae52cbcf50684c7e28cef54d99f7fbc Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Mon, 26 Aug 2024 00:25:04 +0900 Subject: [PATCH 037/175] =?UTF-8?q?json=E5=87=A6=E7=90=86=E3=81=82?= =?UTF-8?q?=E3=81=9F=E3=82=8A=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dislocker_client.py b/dislocker_client.py index 4145871..a690366 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -88,7 +88,8 @@ def init(**kwargs): if responce.status_code == 200: print("PCの情報が登録されました。") - pc_token = str(responce.json["pc_token"]) + responce_json = responce.json() + pc_token = str(responce_json["pc_token"]) client_config["pc_token"] = pc_token master_password = master_password_gen() From b3687e8a2d2a7b7f244748f46b4393fd058b8405 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Mon, 26 Aug 2024 00:35:18 +0900 Subject: [PATCH 038/175] =?UTF-8?q?AND=E3=81=AE,=E3=82=92=E6=B6=88?= =?UTF-8?q?=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_auth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dislocker_auth.py b/dislocker_auth.py index 843fb06..153ded6 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -58,10 +58,10 @@ class Auth(): if "password_hash" in kwargs: password_hash = str(kwargs["password_hash"]) - cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s AND pc_uuid = %s, AND pc_token = %s", (pc_number, pc_uuid, pc_token)) + cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s AND pc_uuid = %s AND pc_token = %s", (pc_number, pc_uuid, pc_token)) pc_info = cursor.fetchall() else: - cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s AND password_hash = %s AND pc_uuid = %s, AND pc_token = %s", (pc_number, password_hash, pc_uuid, pc_token)) + cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s AND password_hash = %s AND pc_uuid = %s AND pc_token = %s", (pc_number, password_hash, pc_uuid, pc_token)) pc_info = cursor.fetchall() if pc_info: From 3086f689ad46cfe3ca87620016f3ee4ada99663c Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Mon, 26 Aug 2024 00:40:20 +0900 Subject: [PATCH 039/175] =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E3=81=AE=E6=B6=88=E5=8E=BB=E3=82=92=E8=A8=AD=E5=AE=9A=E3=81=A7?= =?UTF-8?q?=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/dislocker_client.py b/dislocker_client.py index a690366..f8bd131 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -34,6 +34,7 @@ if not os.path.isfile(client_config_path): "pc_number": 1, "master_password_hash": "", "testing": 0, + "eraser": 1, "pc_uuid": "", "pc_token": "" } @@ -427,15 +428,17 @@ class Stop(): def stop(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") + if client_config["eraser"] == 1: + 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") + else: + print("削除処理をスキップ。") stop_url = client_config["auth_host_url"] + "/stop" stop_json = { "pc_number": int(client_config["pc_number"]), From 631fd990cbf9829be5080652358dacd537cdfdf2 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Mon, 26 Aug 2024 00:46:15 +0900 Subject: [PATCH 040/175] =?UTF-8?q?print=E3=81=AE=E5=AE=9F=E8=A1=8C?= =?UTF-8?q?=E9=A0=86=E7=95=AA=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker_auth.py b/dislocker_auth.py index 153ded6..64b4f65 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -239,10 +239,10 @@ def verify(): @app.route('/stop', methods=['POST']) def stop(): - print(str(pc_number) + "の使用停止処理を開始...") pc_number = int(request.json.get('pc_number')) pc_uuid = request.json.get('pc_uuid') pc_token = request.json.get('pc_token') + print(str(pc_number) + "の使用停止処理を開始...") pc_auth = auth.check(pc_number=pc_number, pc_uuid=pc_uuid, pc_token=pc_token) From 86a4ef87669c257a342ab459646a65810ff83448 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Mon, 26 Aug 2024 00:47:39 +0900 Subject: [PATCH 041/175] =?UTF-8?q?=E5=9E=8B=E3=82=92=E6=98=8E=E7=A4=BA?= =?UTF-8?q?=E7=9A=84=E3=81=AB=E6=8C=87=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_auth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dislocker_auth.py b/dislocker_auth.py index 64b4f65..72bcce6 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -240,8 +240,8 @@ def verify(): @app.route('/stop', methods=['POST']) def stop(): pc_number = int(request.json.get('pc_number')) - pc_uuid = request.json.get('pc_uuid') - pc_token = request.json.get('pc_token') + pc_uuid = str(request.json.get('pc_uuid')) + pc_token = str(request.json.get('pc_token')) print(str(pc_number) + "の使用停止処理を開始...") pc_auth = auth.check(pc_number=pc_number, pc_uuid=pc_uuid, pc_token=pc_token) From e585e9d5964f0bfb3c9e18317ec8882f34ae1f5d Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Mon, 26 Aug 2024 00:51:31 +0900 Subject: [PATCH 042/175] =?UTF-8?q?password=5Fhash=E3=81=8C=E3=81=82?= =?UTF-8?q?=E3=81=A3=E3=81=9F=E3=81=A8=E3=81=8D=E3=81=AE=E5=87=A6=E7=90=86?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_auth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dislocker_auth.py b/dislocker_auth.py index 72bcce6..3c458e8 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -58,10 +58,10 @@ class Auth(): if "password_hash" in kwargs: password_hash = str(kwargs["password_hash"]) - cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s AND pc_uuid = %s AND pc_token = %s", (pc_number, pc_uuid, pc_token)) + cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s AND password_hash = %s AND pc_uuid = %s AND pc_token = %s", (pc_number, password_hash, pc_uuid, pc_token)) pc_info = cursor.fetchall() else: - cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s AND password_hash = %s AND pc_uuid = %s AND pc_token = %s", (pc_number, password_hash, pc_uuid, pc_token)) + cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s AND pc_uuid = %s AND pc_token = %s", (pc_number, pc_uuid, pc_token)) pc_info = cursor.fetchall() if pc_info: From 77ada9a5e27354a5825fe92ceca5d5d760737f82 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 30 Aug 2024 23:29:50 +0900 Subject: [PATCH 043/175] =?UTF-8?q?=E5=89=8A=E9=99=A4=E5=87=A6=E7=90=86?= =?UTF-8?q?=E3=81=AE=E7=B9=B0=E3=82=8A=E8=BF=94=E3=81=97=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 54 +++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/dislocker_client.py b/dislocker_client.py index f8bd131..32e10ea 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -388,40 +388,42 @@ class Stop(): print(f"エラー: 指定されたディレクトリ {dir_path} が存在しません。") return 1 - try: - # プロセスの終了 - subprocess.run(['taskkill', '/f', '/t', '/im', process_name]) - print(f"{process_name} を終了しました。") + i = 0 + i_max = 10 + result = 1 + while i != i_max: + i += 1 + try: + # プロセスの終了 + subprocess.run(['taskkill', '/f', '/t', '/im', process_name]) + print(f"{process_name} を終了しました。") - time.sleep(0.1) + time.sleep(0.1) - # ディレクトリの削除 - i = 1 - ic = 0 - while i == 1: + # ディレクトリの削除 + shutil.rmtree(dir_path) if os.path.isdir(dir_path): - ic += 1 - if ic == 10: - i = 0 + pass else: - i = 0 print(f"{dir_path} を削除しました。") + result = 0 + i = i_max - return 0 + except subprocess.CalledProcessError as e: + print(f"プロセス終了エラー: {e}") + + except PermissionError as e: + print(f"権限エラー: {e}") + + except Exception as error: + print("エラーが発生しました。\nエラー内容:") + print(f"エラータイプ: {error.__class__.__name__}") + print(f"エラー引数: {error.args}") + print(f"エラーメッセージ: {str(error)}") + + return result - 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 shutdown(self): shutdown_command = subprocess.run(['shutdown', '/s', '/t', '1']) From 518eea20612822ffbdfdc80e22c3102c01d19784 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 30 Aug 2024 23:35:32 +0900 Subject: [PATCH 044/175] =?UTF-8?q?#6=20=E3=81=AE=E3=83=90=E3=82=B0?= =?UTF-8?q?=E3=81=B8=E3=81=AE=E5=AF=BE=E7=AD=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/dislocker_client.py b/dislocker_client.py index 32e10ea..40c06fb 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -397,11 +397,8 @@ class Stop(): # プロセスの終了 subprocess.run(['taskkill', '/f', '/t', '/im', process_name]) print(f"{process_name} を終了しました。") - time.sleep(0.1) - # ディレクトリの削除 - shutil.rmtree(dir_path) if os.path.isdir(dir_path): pass @@ -416,11 +413,11 @@ class Stop(): except PermissionError as e: print(f"権限エラー: {e}") - except Exception as error: + except Exception as e: print("エラーが発生しました。\nエラー内容:") - print(f"エラータイプ: {error.__class__.__name__}") - print(f"エラー引数: {error.args}") - print(f"エラーメッセージ: {str(error)}") + print(f"エラータイプ: {e.__class__.__name__}") + print(f"エラー引数: {e.args}") + print(f"エラーメッセージ: {str(e)}") return result From 5384cc966857155251ca30b74c036388d4e13e3a Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 31 Aug 2024 16:13:25 +0900 Subject: [PATCH 045/175] =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E7=94=A8?= =?UTF-8?q?=E3=82=AF=E3=83=A9=E3=82=A4=E3=82=A2=E3=83=B3=E3=83=88=E3=81=AE?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client_universal.py | 441 ++++++++++++++++++++++++++++++++++ 1 file changed, 441 insertions(+) create mode 100644 dislocker_client_universal.py diff --git a/dislocker_client_universal.py b/dislocker_client_universal.py new file mode 100644 index 0000000..3867471 --- /dev/null +++ b/dislocker_client_universal.py @@ -0,0 +1,441 @@ +import os +import json +import tkinter.messagebox +import customtkinter +import subprocess +import requests +import hashlib +import string +import random +import tkinter +import threading +import sys +import shutil +import uuid +import time + +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, + "eraser": 1, + "pc_uuid": "", + "pc_token": "" + } + +elif os.path.isfile(client_config_path): + with open(client_config_path, "r") as r: + client_config = json.load(r) + +def master_password_gen(): + numbers = string.digits # (1) + password = ''.join(random.choice(numbers) for _ in range(10)) # (2) + password_hash = hashlib.md5(password.encode()).hexdigest() + result = {"password": password, "password_hash": password_hash} + return result + +def init(**kwargs): + if client_config["initial"] == 1: + pc_uuid = uuid.uuid4() + client_config["pc_uuid"] = str(pc_uuid) + + if "pc_number" in kwargs: + client_config["pc_number"] = int(kwargs["pc_number"]) + else: + tkinter.messagebox.showerror(title=f"{app_name} | 登録時にエラー", message=f"登録時にエラーが発生しました。\nPC番号が指定されていません。1個目の引数にPC番号、2個目の引数にワンタイムパスワードを指定して、もう一度お試しください。") + return 2 + + if "onetime" in kwargs: + onetime = str(kwargs["onetime"]) + else: + tkinter.messagebox.showerror(title=f"{app_name} | 登録時にエラー", message=f"登録時にエラーが発生しました。\nワンタイムパスワードが指定されていません。1個目の引数にPC番号、2個目の引数にワンタイムパスワードを指定して、もう一度お試しください。") + return 2 + + register_url = client_config["auth_host_url"] + "/register" + register_json = { + "pc_number": int(client_config["pc_number"]), + "pc_uuid": str(pc_uuid), + "onetime": onetime + } + + responce = requests.post(register_url, json=register_json) + + if responce.status_code == 200: + print("PCの情報が登録されました。") + responce_json = responce.json() + pc_token = str(responce_json["pc_token"]) + client_config["pc_token"] = pc_token + + 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 + + with open(client_config_path, "w") as w: + json.dump(client_config, w, indent=4) + return 2 + else: + msgbox = tkinter.messagebox.showerror(title=f"{app_name} | 登録時にエラー", message=f"登録時にエラーが発生しました。\nワンタイムパスワードが間違っている可能性があります。") + return 2 + else: + return 0 + + +class App(customtkinter.CTk): + def __init__(self): + super().__init__() + self.title(f"{app_name} | ロック中") + if client_config["testing"] == 1: + pass + else: + self.attributes('-fullscreen', True) + self.attributes('-topmost', True) + + self.frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') + self.frame.grid(row=0, column=0, sticky='nsew') + + lock = Lock() + + def exit(self): + self.unlock_taskmgr() + self.toast() + self.destroy() + + def handler_close(self): + pass + + +class Lock(customtkinter.CTkToplevel): + def __init__(self): + super().__init__() + if client_config["testing"] == 1: + self.title(f'{app_name} | PC番号 {client_config["pc_number"]} | テストモード') + else: + self.title(f'{app_name} | PC番号 {client_config["pc_number"]} | ロックされています') + + 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.attributes('-topmost', True) + self.grab_set() + self.lift() + self.protocol("WM_DELETE_WINDOW", self.handler_close) + + self.emoji_font = customtkinter.CTkFont(family="Noto Color Emoji", size=32) + self.title_font = customtkinter.CTkFont(family="Noto Sans CJK JP", size=32, weight="bold") + self.pc_number_font = customtkinter.CTkFont(family="Noto Sans CJK JP", size=64, weight="bold") + self.title_small_font = customtkinter.CTkFont(family="Noto Sans CJK JP", size=16) + self.general_font = customtkinter.CTkFont(family="Noto Sans CJK JP", size=18) + self.general_small_font = customtkinter.CTkFont(family="Noto Sans CJK JP", size=12) + self.textbox_font = customtkinter.CTkFont(family="Noto Sans CJK JP", size=14) + self.button_font = customtkinter.CTkFont(family="Noto Sans CJK JP", size=14) + + + self.msg_title_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') + self.msg_title_frame.grid(row=0, column=0, padx=10, pady=10, sticky="nsew") + + self.icon_title_1 = customtkinter.CTkLabel(self.msg_title_frame, text='😎', font=self.emoji_font, justify="left") + 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.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='サインインするには、Discordのダイレクトメッセージに送信された\nパスワードを入力してください。', font=self.general_font, justify="left") + 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.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='パスワード', show='*', font=self.textbox_font) + self.password_entry.grid(row=0, column=0, padx=10, sticky="ew") + self.password_entry.bind("", self.auth_start_ev) + + self.button_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') + 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_start, font=self.button_font) + self.signin_button.grid(row=0, column=2, padx=10, sticky="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") + + def help_wakeup(self): + help = Help() + + def hash_genarate(self, source): + hashed = hashlib.md5(source.encode()) + 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): + 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_json = { + "pc_number": int(client_config["pc_number"]), + "pc_uuid": str(client_config["pc_uuid"]), + "pc_token": str(client_config["pc_token"]), + "password": self.hash_genarate(str(self.password_entry.get())) + } + try: + 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: + 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"認証サーバーにアクセスできませんでした。\n続行するには、マスターパスワードを入力してください。") + self.msg_subtitle_1.configure(text='ネットワークエラーが発生しています。\n続行するには、マスターパスワードを入力して下さい。 ') + self.button_enable() + self.deiconify() + + def signout(self): + self.withdraw() + msgbox = tkinter.messagebox.showinfo(title=f"{app_name} | 未実装", message=f"ログアウトは未実装です。") + self.deiconify() + + def handler_close(self): + pass + + def help_dummy(self): + self.withdraw() + msgbox = tkinter.messagebox.showinfo(title=f"{app_name} | 未実装", message=f"ヘルプページは製作途中です。\nDiscordサーバーの指示に従って、認証を進めてください。") + self.deiconify() + + def exit(self): + self.destroy() + 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.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 Stop(): + def __init__(self) -> None: + pass + + def run(self): + print("停止処理を実行中...") + stop_thread = threading.Thread(target=self.stop) + stop_thread.run() + + + 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} を終了しました。") + + time.sleep(0.1) + + # ディレクトリの削除 + i = 1 + ic = 0 + while i == 1: + shutil.rmtree(dir_path) + if os.path.isdir(dir_path): + ic += 1 + if ic == 10: + i = 0 + else: + i = 0 + 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 shutdown(self): + shutdown_command = subprocess.run(['shutdown', '/s', '/t', '1']) + + def stop(self): + print("停止処理を実行。") + if client_config["eraser"] == 1: + #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") + pass + else: + print("削除処理をスキップ。") + stop_url = client_config["auth_host_url"] + "/stop" + stop_json = { + "pc_number": int(client_config["pc_number"]), + "pc_uuid": str(client_config["pc_uuid"]), + "pc_token": str(client_config["pc_token"]) + } + try: + responce = requests.post(stop_url, json=stop_json) + if responce.status_code == 200: + print("停止処理は成功しました。") + elif responce.status_code == 401: + print("認証に失敗しました。") + tkinter.messagebox.showwarning(title=f"{app_name} | エラー", message=f"認証に失敗しました。\nDiscordサーバーの指示に従って、停止処理を自身で行ってください。") + 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() + + +if __name__ == '__main__': + args = sys.argv + if len(args) >= 2: + if args[1] == "stop": + init_result = init() + if init_result == 1: + warning_msgbox = tkinter.messagebox.showwarning(title=f"{app_name} | 多重起動エラー", message=f"もう終了処理を行っています。\nPCがシャットダウンするまで、もう少しお待ちください。") + elif init_result == 2: + pass + else: + stop = Stop() + stop.run() + + elif args[1] == "setup": + init_result = init(pc_number=args[2], onetime=args[3]) + if init_result == 1: + warning_msgbox = tkinter.messagebox.showwarning(title=f"{app_name} | 多重起動エラー", message=f"すでに {app_name} は実行されています。\n正常に起動しない場合は、既に起動しているプロセスを終了してから、もう一度起動してみてください。") + elif init_result == 2: + pass + else: + pass + else: + print("引数エラー。") + else: + init_result = init() + if init_result == 1: + warning_msgbox = tkinter.messagebox.showwarning(title=f"{app_name} | 多重起動エラー", message=f"すでに {app_name} は実行されています。\n正常に起動しない場合は、既に起動しているプロセスを終了してから、もう一度起動してみてください。") + elif init_result == 2: + pass + else: + app = App() + app.protocol("WM_DELETE_WINDOW", app.handler_close) + app.mainloop() From cf0013e53a93db3f81641e534f00af22a48e40be Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Wed, 4 Sep 2024 19:42:39 +0900 Subject: [PATCH 046/175] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=90=86=E7=94=B1?= =?UTF-8?q?=E3=81=AE=E3=83=97=E3=83=AA=E3=82=BB=E3=83=83=E3=83=88=E3=82=92?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=20=E3=83=AD=E3=82=B0=E3=83=95=E3=82=A1=E3=82=A4?= =?UTF-8?q?=E3=83=AB=E3=82=92=E4=BD=9C=E6=88=90=E3=81=99=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=20=E3=81=84=E3=81=8F=E3=81=A4=E3=81=8B?= =?UTF-8?q?=E8=AA=AC=E6=98=8E=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 210 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 127 insertions(+), 83 deletions(-) diff --git a/dislocker.py b/dislocker.py index dc4160f..d0c96a3 100644 --- a/dislocker.py +++ b/dislocker.py @@ -17,8 +17,10 @@ class DL(): def __init__(self): self.config_dir_path = "./config/" self.export_dir_path = "./export/" + self.log_dir_path = "./log/" self.server_config_path = self.config_dir_path + "server.json" self.onetime_config_path = self.config_dir_path + "onetime.json" + self.log_path = self.log_dir_path + "dislocker.txt" try: if not os.path.isdir(self.config_dir_path): print("config ディレクトリが見つかりません... 作成します。") @@ -50,7 +52,9 @@ class DL(): "allowable_time": 180, "fstop_time": "21:00:00" }, - "admin_user_id": "TYPE HERE CHANNEL ID (YOU MUST USE INT !!!!)" + "preset_games": ["TEST1", "TEST2", "TEST3", "TEST4", "TEST5"], + "admin_user_id": "TYPE HERE CHANNEL ID (YOU MUST USE INT !!!!)", + "debug": False } } @@ -67,6 +71,10 @@ class DL(): if not os.path.isdir(self.export_dir_path): print("export ディレクトリが見つかりません... 作成します。") os.mkdir(self.export_dir_path) + + if not os.path.isdir(self.log_dir_path): + print("log ディレクトリが見つかりません... 作成します。") + os.mkdir(self.log_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型で記述して、起動してください。") @@ -78,6 +86,8 @@ class DL(): self.pc_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] self.keyboard_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] self.mouse_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + self.preset_games = self.server_config["bot"]["preset_games"] + self.debug = self.server_config["bot"]["debug"] cursor.execute("SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'club_member')") find_club_member_table = cursor.fetchall() @@ -132,6 +142,53 @@ class DL(): finally: pass + + def log(self, **kwargs): + if self.debug == True: + flag = 1 + else: + if "flag" in kwargs: + if kwargs["flag"] == 1: + flag = 1 + else: + flag = 0 + else: + flag = 0 + + if flag == 1: + title = str(kwargs["title"]) + if "message" in kwargs: + message = str(kwargs["message"]) + else: + message = None + + current_datetime = str(datetime.now()) + + if message == None: + detail = f"{current_datetime} | {title}" + else: + detail = f"{current_datetime} | {title}\n{message}" + + print(detail) + + if os.path.isfile(self.log_path): + try: + with open(self.log_path, "a", encoding="utf-8") as a: + a.write(detail) + except: + print("LOGGING ERROR mode a") + else: + try: + with open(self.log_path, "w", encoding="utf-8") as w: + w.write(detail) + except: + print("LOGGING ERROR mode w") + + + + + + class Bot(discord.Client): @@ -164,10 +221,7 @@ class Bot(discord.Client): return {"result": 1, "about": "user_data_not_found"} except Exception as error: - print("ユーザーの登録状態を調査中にエラーが発生しました。\nエラー内容") - print(str(error.__class__.__name__)) - print(str(error.args)) - print(str(error)) + dislocker.log(title=f"[ERROR] ユーザーの登録状態を調査中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) return {"result": 1, "about": "error"} finally: @@ -208,7 +262,6 @@ class Bot(discord.Client): cursor.execute("SELECT * FROM pc_usage_history WHERE member_id = %s ORDER BY id DESC LIMIT 1", (member_id,)) pc_usage_history_record = cursor.fetchall() if pc_usage_history_record: - print("used") if pc_usage_history_record[0][5] == None: return {"result": 1, "about": "used_by_you", "pc_usage_history": {"pc_number": str(pc_usage_history_record[0][2]), "keyboard_number": str(pc_usage_history_record[0][3]), "mouse_number": str(pc_usage_history_record[0][4]), "start_time": str(pc_usage_history_record[0][5]), "use_detail": str(pc_usage_history_record[0][7])}} else: @@ -221,7 +274,6 @@ class Bot(discord.Client): cursor.execute("SELECT * FROM pc_usage_history WHERE member_id = %s ORDER BY id DESC LIMIT 1", (member_id,)) pc_usage_history_record = cursor.fetchall() if pc_usage_history_record: - print("used") if pc_usage_history_record[0][5] == None: return {"result": 1, "about": "used_by_you", "pc_usage_history": {"pc_number": str(pc_usage_history_record[0][2]), "keyboard_number": str(pc_usage_history_record[0][3]), "mouse_number": str(pc_usage_history_record[0][4]), "start_time": str(pc_usage_history_record[0][5]), "use_detail": str(pc_usage_history_record[0][7])}} else: @@ -232,10 +284,7 @@ class Bot(discord.Client): return {"result": 1, "about": "search_options_error"} except Exception as error: - print("PCの使用状況を調査中にエラーが発生しました。\nエラー内容") - print(str(error.__class__.__name__)) - print(str(error.args)) - print(str(error)) + dislocker.log(title=f"[ERROR] PCの使用状況を調査中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) return {"result": 1, "about": "error"} finally: @@ -258,10 +307,7 @@ class Bot(discord.Client): return {"result": 1, "about": "keyboard_already_in_use_by_other"} except Exception as error: - print("キーボードの使用状況を調査中にエラーが発生しました。\nエラー内容") - print(str(error.__class__.__name__)) - print(str(error.args)) - print(str(error)) + dislocker.log(title=f"[ERROR] キーボードの使用状況を調査中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) return {"result": 1, "about": "error"} finally: @@ -284,10 +330,7 @@ class Bot(discord.Client): return {"result": 1, "about": "mouse_already_in_use_by_other"} except Exception as error: - print("マウスの使用状況を調査中にエラーが発生しました。\nエラー内容") - print(str(error.__class__.__name__)) - print(str(error.args)) - print(str(error)) + dislocker.log(title=f"[ERROR] マウスの使用状況を調査中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) return {"result": 1, "about": "error"} finally: @@ -331,15 +374,12 @@ class Bot(discord.Client): if pc_check_self["result"] == 0: # 他の人がそのPCを使っているか pc_check = self.pc_used_check(pc_number=user_info["pc_number"]) - print(pc_check) if pc_check["result"] == 0: # キーボードは使われているか keyboard_check = self.keyboard_used_check(keyboard_number=user_info["keyboard_number"]) - print(keyboard_check) if keyboard_check["result"] == 0: # マウスは使われているか mouse_check = self.mouse_used_check(mouse_number=user_info["mouse_number"]) - print(mouse_check) if mouse_check["result"] == 0: # パスワードとハッシュ作成 password = self.password_generate(4) @@ -372,10 +412,7 @@ class Bot(discord.Client): else: return {"result": 1, "about": "user_data_not_found"} except Exception as error: - print("登録処理中にエラーが発生しました。\nエラー内容") - print(str(error.__class__.__name__)) - print(str(error.args)) - print(str(error)) + dislocker.log(title=f"[ERROR] PCの使用登録中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) return {"result": 1, "about": "error"} finally: if cursor: @@ -436,10 +473,7 @@ class Bot(discord.Client): return {"result": 1, "about": "user_data_not_found"} except Exception as error: - print("停止処理中にエラーが発生しました。\nエラー内容") - print(str(error.__class__.__name__)) - print(str(error.args)) - print(str(error)) + dislocker.log(title=f"[ERROR] PCの使用停止処理中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) return {"result": 1, "about": "error"} finally: @@ -462,10 +496,7 @@ class Bot(discord.Client): return {"result": 1, "about": "already_exists"} except Exception as error: - print("ユーザー登録中にエラーが発生しました。\nエラー内容") - print(str(error.__class__.__name__)) - print(str(error.args)) - print(str(error)) + dislocker.log(title=f"[ERROR] ユーザー情報の登録中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) return {"result": 1, "about": "error"} finally: @@ -492,10 +523,7 @@ class Bot(discord.Client): return {"result": 1, "about": "already_exists"} except Exception as error: - print("PCの登録中にエラーが発生しました。\nエラー内容") - print(str(error.__class__.__name__)) - print(str(error.args)) - print(str(error)) + dislocker.log(title=f"[ERROR] PCの情報を登録中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) return {"result": 1, "about": "error"} finally: @@ -561,16 +589,12 @@ class Bot(discord.Client): # Excelファイルを保存 wb.save(excel_file_path) - - print(f"テーブル '{main_table}' の内容を '{excel_file_path}' に出力しました。") + dislocker.log(title=f"[SUCCESS] PCの使用履歴をエクスポートしました。", message=f"ファイルパス | {excel_file_path}", flag=0) return {"result": 0, "about": "ok", "file_path": excel_file_path} except Exception as error: - print("使用履歴のエクスポート中にエラーが発生しました。\nエラー内容") - print(str(error.__class__.__name__)) - print(str(error.args)) - print(str(error)) + dislocker.log(title=f"[ERROR] PCの使用履歴をエクスポート中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) return {"result": 1, "about": "error"} finally: @@ -618,10 +642,7 @@ class Bot(discord.Client): return {"result": 1, "about": "bot_about_not_found"} except Exception as error: - print("fstop中にエラーが発生しました。\nエラー内容") - print(str(error.__class__.__name__)) - print(str(error.args)) - print(str(error)) + dislocker.log(title=f"[ERROR] fstop中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) return {"result": 1, "about": "error"} finally: @@ -645,7 +666,7 @@ class Bot(discord.Client): async def on_ready(self): - print("DiscordのBotが起動しました。") + dislocker.log(title=f"[SUCCESS] DiscordのBotが起動しました。", flag=1) dislocker_activity = discord.Activity( name=dislocker.server_config["bot"]["activity"]["name"], type=discord.ActivityType.competing, @@ -734,16 +755,17 @@ class Bot(discord.Client): elif message.channel.id == dislocker.server_config["bot"]["config_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["about"] == "ok": await message.channel.send(f"# :white_check_mark: ユーザー情報が登録されました。\n>>> ユーザー名:{message.author.display_name}") + dislocker.log(title=f"[INFO] ユーザー情報が登録されました。", message=f"名前 | {message.author.display_name}, DiscordユーザーID | {message.author.id}, Discordユーザーネーム | {message.author.name}", flag=0) elif register["about"] == "already_exists": await message.channel.send("# :no_entry: 登録できませんでした。\nもう登録されている可能性があります。") + dislocker.log(title=f"[INFO] 既存のユーザー情報は登録されませんでした。", message=f"名前 | {message.author.display_name}, DiscordユーザーID | {message.author.id}, Discordユーザーネーム | {message.author.name}", flag=0) else: await message.channel.send("# :no_entry: 登録できませんでした。\n内部エラーが発生しています。") + dislocker.log(title=f"[INFO] 登録できなかったユーザーの情報です。", message=f"名前 | {message.author.display_name}, DiscordユーザーID | {message.author.id}, Discordユーザーネーム | {message.author.name}", flag=1) elif len(msg_split) <= 3: await message.channel.send("# :japanese_goblin: 入力内容に不備があります。\n名前、Discordのユーザー名、DiscordのユーザーIDのいずれかが入力されていません。") @@ -801,6 +823,7 @@ class Bot(discord.Client): else: await message.channel.send("# :warning: PCを登録できませんでした。\n構文が間違っています。\n-# /pcregister PC番号") + # /init ボタン月のメッセージ一式を指定チャンネルに送信 elif msg_split[0] == "/init": user_register_button_view = View(timeout=None) user_register_button = discord.ui.Button(style=discord.ButtonStyle.green, label="ユーザー登録", custom_id="user_register") @@ -820,7 +843,9 @@ class Bot(discord.Client): 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) + dislocker.log(title=f"[INFO] サーバーで初回処理を実行しました。", flag=0) + # /registerbutton PCの選択ボタンを指定チャンネルに送信 elif msg_split[0] == "/registerbutton": pc_button_view = View(timeout=None) for i in dislocker.pc_list: @@ -828,29 +853,32 @@ class Bot(discord.Client): 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) + dislocker.log(title=f"[INFO] サーバーでPC番号のボタンを送信しました。", flag=0) + # /stopbutton 利用停止のボタンを指定チャンネルに送信 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) + dislocker.log(title=f"[INFO] サーバーでPCの使用停止ボタンを送信しました。", flag=0) + # /userbutton ユーザー登録のボタンを指定チャンネルに送信 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) + dislocker.log(title=f"[INFO] サーバーでユーザー登録ボタンを送信しました。", flag=0) 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["about"] == "ok": await message.channel.send(f"# :white_check_mark: ユーザー情報が登録されました。\nユーザー名:{message.author.display_name}") elif register["about"] == "already_exists": @@ -863,12 +891,11 @@ class Bot(discord.Client): 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]) + dislocker.log(title=f"[INFO] ボタンが押されました。", message=f"custom_id | {custom_id}, DiscordユーザーID | {interaction.user.id}", flag=0) if custom_id_split[0] == "pcregister": keyboard_register_view = View(timeout=15) pc_number = custom_id_split[1] - print(custom_id_split) for i in dislocker.keyboard_list: keyboard_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"keyboardregister_{str(pc_number)}_{str(i)}") keyboard_register_view.add_item(keyboard_register_button) @@ -885,7 +912,6 @@ class Bot(discord.Client): keyboard_number_show = "自前" else: keyboard_number_show = keyboard_number - print(custom_id_split) for i in dislocker.mouse_list: mouse_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_{str(i)}") mouse_register_view.add_item(mouse_register_button) @@ -907,6 +933,9 @@ class Bot(discord.Client): else: mouse_number_show = mouse_number reason_register_view = View(timeout=15) + for i in dislocker.preset_games: + reason_quick_button = reason_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"reasonregister_{str(pc_number)}_{str(keyboard_number)}_{str(mouse_number)}_quick_{str(i)}") + reason_register_view.add_item(reason_quick_button) reason_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="使用目的を入力する", custom_id=f"reasonregister_{str(pc_number)}_{str(keyboard_number)}_{str(mouse_number)}") reason_register_view.add_item(reason_button) @@ -915,14 +944,41 @@ class Bot(discord.Client): elif custom_id_split[0] == "reasonregister": pc_number = custom_id_split[1] keyboard_number = custom_id_split[2] + if keyboard_number == "own": + keyboard_number_show = "自前" + else: + keyboard_number_show = keyboard_number mouse_number = custom_id_split[3] - reason_input_form = Reason(title="Dislocker | 登録", pc_number=str(pc_number), keyboard_number=str(keyboard_number), mouse_number=str(mouse_number)) - await interaction.response.send_modal(reason_input_form) + if mouse_number == "own": + mouse_number_show = "自前" + else: + mouse_number_show = mouse_number + if custom_id_split[4] == "quick": + reason = custom_id_split[5] + register = bot.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=reason) + if register["about"] == "ok": + await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\n## 使用目的 | {reason}", ephemeral=True) + await bot.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':white_check_mark: {register["output_dict"]["name"]} さんがPC {pc_number} の使用を開始しました。\n>>> ## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\n## 使用目的 | {reason}') + dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, キーボード番号 | {keyboard_number_show}, マウス番号 | {mouse_number_show}, 使用目的 | {reason}", flag=0) + elif register["about"] == "pc_already_in_use_by_you": + pc_usage_history = register["pc_usage_history"] + await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {pc_usage_history["keyboard_number"]}\n# マウス番号 | {pc_usage_history["mouse_number"]}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) + elif register["about"] == "pc_already_in_use_by_other": + await interaction.response.send_message(f"# :man_gesturing_no: そのPCは他のメンバーによって使用されています。\n別のPC番号を指定して、再度お試しください。", ephemeral=True) + elif register["about"] == "keyboard_already_in_use": + await interaction.response.send_message(f"# :man_gesturing_no: そのキーボードは他のメンバーによって使用されています。\n別のキーボードのデバイス番号を指定して、再度お試しください。", ephemeral=True) + elif register["about"] == "mouse_already_in_use": + await interaction.response.send_message(f"# :man_gesturing_no: そのマウスは他のメンバーによって使用されています。\n別のマウスのデバイス番号を指定して、再度お試しください。", ephemeral=True) + elif register["about"] == "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) + else: + reason_input_form = Reason(title="Dislocker | 登録", pc_number=str(pc_number), keyboard_number=str(keyboard_number), mouse_number=str(mouse_number)) + await interaction.response.send_modal(reason_input_form) elif custom_id_split[0] == "stop": - print("STOP running") pc_stop = self.stop(discord_user_id=interaction.user.id) - print(pc_stop) stop_view = View(timeout=15) if pc_stop["about"] == "unused": await interaction.response.send_message("# :shaking_face: 使用されていないようです...", ephemeral=True) @@ -935,7 +991,6 @@ class Bot(discord.Client): 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["about"] == "ok": await interaction.response.send_message(f"# :white_check_mark: ユーザー情報が登録されました。\n>>> ユーザー名:{interaction.user.display_name}", ephemeral=True) @@ -966,28 +1021,27 @@ class Monitor(): current_datetime = datetime.now() fstop_time = self.fstop_time if current_datetime.time().strftime("%H:%M:%S") == fstop_time: + dislocker.log(title=f"[INFO] 定期のPCの使用停止処理を開始します。", flag=0) for i in dislocker.pc_list: stop = bot.force_stop(pc_number=i, bot_about="使用停止忘れによるBotによる強制停止。") result = {"result": "FSTOP"} + dislocker.log(title=f"[SUCCESS] 定期のPCの使用停止処理は完了しました。", flag=0) else: if pc_list: if len(pc_list) == 1: member_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", (member_id,)) pc_usage = cursor.fetchall() - print(pc_usage) start_time = pc_usage[0][5] - 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) + dislocker.log(title=f"[INFO] 現在確認されているパスワード未使用のユーザー", message=f"レコード | {str(pc_usage)}, 経過時間(Sec) | {time_difference.seconds}/{timedelta(seconds=self.allowable_time).seconds}", flag=0) if time_difference.seconds >= timedelta(seconds=self.allowable_time).seconds: cursor.execute("SELECT * FROM club_member WHERE member_id = %s", (member_id,)) user_info = cursor.fetchall() stop = bot.stop(discord_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]) + #bot.timeout_notify(pc_number=pc_list[0][0], discord_display_name=user_info[0][1]) + dislocker.log(title=f"[INFO] パスワードのタイムアウト時間に達したため、強制停止されました。", flag=0) result = {"result": "STOP", "details": str(pc_usage)} else: result = {"result": "BUT SAFE", "details": str(pc_usage)} @@ -995,22 +1049,19 @@ class Monitor(): elif len(pc_list) >= 2: for i in pc_list: - print(i) member_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", (member_id,)) pc_usage = cursor.fetchall() - print(pc_usage) start_time = pc_usage[0][5] - print(start_time) - print(type(start_time)) time_difference = current_datetime - start_time - print(time_difference.seconds, timedelta(seconds=self.allowable_time).seconds) + dislocker.log(title=f"[INFO] 現在確認されているパスワード未使用のユーザー", message=f"レコード | {str(pc_usage)}, 経過時間(Sec) | {time_difference.seconds}/{timedelta(seconds=self.allowable_time).seconds}", flag=0) if time_difference.seconds >= timedelta(seconds=self.allowable_time).seconds: cursor.execute("SELECT * FROM club_member WHERE member_id = %s", (member_id,)) user_info = cursor.fetchall() stop = bot.stop(discord_user_id=user_info[0][3], bot_about="タイムアウトでBotによる強制停止。") - bot.timeout_notify(pc_number=i[0], discord_display_name=user_info[0][1]) + #bot.timeout_notify(pc_number=i[0], discord_display_name=user_info[0][1]) + dislocker.log(title=f"[INFO] パスワードのタイムアウト時間に達したため、強制停止されました。", flag=0) result = {"result": "STOP", "details": str(pc_usage)} else: result = {"result": "BUT SAFE", "details": str(pc_usage)} @@ -1023,23 +1074,18 @@ class Monitor(): if result["result"] == "NONE": pass else: - print(current_datetime) - print(result["result"]) + pass time.sleep(self.search_frequency) except Exception as error: - print("自動停止処理中にエラーが発生しました。\nエラー内容") - print(str(error.__class__.__name__)) - print(str(error.args)) - print(str(error)) + dislocker.log(title=f"[ERROR] 自動停止処理中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) result = {"result": "error"} dislocker.db.rollback() finally: if cursor: cursor.close() - print(result["result"]) return result @@ -1047,13 +1093,11 @@ class Monitor(): class Reason(Modal): def __init__(self, title: str, pc_number: str, keyboard_number: str, mouse_number: str, timeout=15) -> None: super().__init__(title=title, timeout=timeout) - print(pc_number, keyboard_number, mouse_number) self.reason_input_form = TextInput(label="使用目的を入力してください", style=TextStyle.short, custom_id=f"register_{pc_number}_{keyboard_number}_{mouse_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] keyboard_number = custom_id_split[2] @@ -1068,11 +1112,11 @@ class Reason(Modal): mouse_number_show = mouse_number register = bot.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=self.reason_input_form.value) - print(register) if register["about"] == "ok": await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\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["output_dict"]["name"]} さんがPC {pc_number} の使用を開始しました。\n>>> ## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\n## 使用目的 | {self.reason_input_form.value}') + dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, キーボード番号 | {keyboard_number_show}, マウス番号 | {mouse_number_show}, 使用目的 | {self.reason_input_form.value}", flag=0) elif register["about"] == "pc_already_in_use_by_you": pc_usage_history = register["pc_usage_history"] await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {pc_usage_history["keyboard_number"]}\n# マウス番号 | {pc_usage_history["mouse_number"]}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) From 55f989d36524eefb1d07246c0a5474f613361240 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Wed, 4 Sep 2024 19:47:58 +0900 Subject: [PATCH 047/175] =?UTF-8?q?=E3=83=AD=E3=82=B0=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=82=A4=E3=83=AB=E3=81=8C=E8=A6=8B=E3=81=9A=E3=82=89=E3=81=84?= =?UTF-8?q?=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dislocker.py b/dislocker.py index d0c96a3..72510bd 100644 --- a/dislocker.py +++ b/dislocker.py @@ -165,9 +165,9 @@ class DL(): current_datetime = str(datetime.now()) if message == None: - detail = f"{current_datetime} | {title}" + detail = f"{current_datetime} | {title}\n" else: - detail = f"{current_datetime} | {title}\n{message}" + detail = f"{current_datetime} | {title}\n{message}\n" print(detail) From e50a5748c99f25e60bd448c92e04eb952153b61b Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 5 Sep 2024 10:38:17 +0900 Subject: [PATCH 048/175] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=9B=AE=E7=9A=84?= =?UTF-8?q?=E3=81=8C=E6=9B=B8=E3=81=91=E3=81=AA=E3=81=8F=E3=81=AA=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E3=83=90=E3=82=B0=E3=81=B8=E3=81=AE=E5=AF=BE=E5=87=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/dislocker.py b/dislocker.py index 72510bd..c321cd3 100644 --- a/dislocker.py +++ b/dislocker.py @@ -953,24 +953,27 @@ class Bot(discord.Client): mouse_number_show = "自前" else: mouse_number_show = mouse_number - if custom_id_split[4] == "quick": - reason = custom_id_split[5] - register = bot.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=reason) - if register["about"] == "ok": - await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\n## 使用目的 | {reason}", ephemeral=True) - await bot.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':white_check_mark: {register["output_dict"]["name"]} さんがPC {pc_number} の使用を開始しました。\n>>> ## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\n## 使用目的 | {reason}') - dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, キーボード番号 | {keyboard_number_show}, マウス番号 | {mouse_number_show}, 使用目的 | {reason}", flag=0) - elif register["about"] == "pc_already_in_use_by_you": - pc_usage_history = register["pc_usage_history"] - await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {pc_usage_history["keyboard_number"]}\n# マウス番号 | {pc_usage_history["mouse_number"]}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) - elif register["about"] == "pc_already_in_use_by_other": - await interaction.response.send_message(f"# :man_gesturing_no: そのPCは他のメンバーによって使用されています。\n別のPC番号を指定して、再度お試しください。", ephemeral=True) - elif register["about"] == "keyboard_already_in_use": - await interaction.response.send_message(f"# :man_gesturing_no: そのキーボードは他のメンバーによって使用されています。\n別のキーボードのデバイス番号を指定して、再度お試しください。", ephemeral=True) - elif register["about"] == "mouse_already_in_use": - await interaction.response.send_message(f"# :man_gesturing_no: そのマウスは他のメンバーによって使用されています。\n別のマウスのデバイス番号を指定して、再度お試しください。", ephemeral=True) - elif register["about"] == "user_data_not_found": - await interaction.response.send_message("# :dizzy_face: ユーザーとして登録されていないようです。\n最初にサーバーで登録を行ってください。", ephemeral=True) + if custom_id_split[4]: + if custom_id_split[4] == "quick": + reason = custom_id_split[5] + register = bot.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=reason) + if register["about"] == "ok": + await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\n## 使用目的 | {reason}", ephemeral=True) + await bot.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':white_check_mark: {register["output_dict"]["name"]} さんがPC {pc_number} の使用を開始しました。\n>>> ## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\n## 使用目的 | {reason}') + dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, キーボード番号 | {keyboard_number_show}, マウス番号 | {mouse_number_show}, 使用目的 | {reason}", flag=0) + elif register["about"] == "pc_already_in_use_by_you": + pc_usage_history = register["pc_usage_history"] + await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {pc_usage_history["keyboard_number"]}\n# マウス番号 | {pc_usage_history["mouse_number"]}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) + elif register["about"] == "pc_already_in_use_by_other": + await interaction.response.send_message(f"# :man_gesturing_no: そのPCは他のメンバーによって使用されています。\n別のPC番号を指定して、再度お試しください。", ephemeral=True) + elif register["about"] == "keyboard_already_in_use": + await interaction.response.send_message(f"# :man_gesturing_no: そのキーボードは他のメンバーによって使用されています。\n別のキーボードのデバイス番号を指定して、再度お試しください。", ephemeral=True) + elif register["about"] == "mouse_already_in_use": + await interaction.response.send_message(f"# :man_gesturing_no: そのマウスは他のメンバーによって使用されています。\n別のマウスのデバイス番号を指定して、再度お試しください。", ephemeral=True) + elif register["about"] == "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) else: await interaction.response.send_message("# :skull_crossbones: 登録できませんでした。\n内部エラーが発生しています。", ephemeral=True) else: From 036e1c0b0b33bf2d40db2bbd606203c438d1b6ef Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 5 Sep 2024 10:43:34 +0900 Subject: [PATCH 049/175] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=9B=AE=E7=9A=84?= =?UTF-8?q?=E3=81=8B=E3=81=91=E3=81=AA=E3=81=84=E3=83=90=E3=82=B0=E5=AF=BE?= =?UTF-8?q?=E5=87=A6=E3=81=9D=E3=81=AE=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker.py b/dislocker.py index c321cd3..71b330d 100644 --- a/dislocker.py +++ b/dislocker.py @@ -953,7 +953,7 @@ class Bot(discord.Client): mouse_number_show = "自前" else: mouse_number_show = mouse_number - if custom_id_split[4]: + if len(custom_id_split) == 5: if custom_id_split[4] == "quick": reason = custom_id_split[5] register = bot.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=reason) From a6c856cf489e5c9fc424bac081070ad4057093bd Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 5 Sep 2024 21:03:39 +0900 Subject: [PATCH 050/175] =?UTF-8?q?temp=E3=81=AE=E4=B8=AD=E3=82=92?= =?UTF-8?q?=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- temp/client_playground.py | 40 ---------------- temp/csv_test.py | 77 ------------------------------- temp/kill.py | 22 --------- temp/password.py | 16 ------- temp/password.txt | 0 temp/powershell.py | 9 ---- temp/test.py | 41 ----------------- temp/xcel.py | 97 --------------------------------------- 9 files changed, 1 insertion(+), 304 deletions(-) delete mode 100644 temp/client_playground.py delete mode 100644 temp/csv_test.py delete mode 100644 temp/kill.py delete mode 100644 temp/password.py delete mode 100644 temp/password.txt delete mode 100644 temp/powershell.py delete mode 100644 temp/test.py delete mode 100644 temp/xcel.py diff --git a/.gitignore b/.gitignore index 541d26b..f5c0150 100644 --- a/.gitignore +++ b/.gitignore @@ -164,5 +164,4 @@ config/ db/ test.py data/ -export/ -temp/ \ No newline at end of file +export/ \ No newline at end of file diff --git a/temp/client_playground.py b/temp/client_playground.py deleted file mode 100644 index f0a4879..0000000 --- a/temp/client_playground.py +++ /dev/null @@ -1,40 +0,0 @@ -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") \ No newline at end of file diff --git a/temp/csv_test.py b/temp/csv_test.py deleted file mode 100644 index 9b30aa8..0000000 --- a/temp/csv_test.py +++ /dev/null @@ -1,77 +0,0 @@ -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) diff --git a/temp/kill.py b/temp/kill.py deleted file mode 100644 index 2c68cbf..0000000 --- a/temp/kill.py +++ /dev/null @@ -1,22 +0,0 @@ -import wmi -import asyncio - -async def watcher(): - c=wmi.WMI() - cre_process_watcher=c.Win32_Process.watch_for("creation") - del_process_watcher=c.Win32_Process.watch_for("deletion") - - while True: - new_cre_process=cre_process_watcher() - new_del_process=del_process_watcher() - print(new_cre_process.Caption+" has been created") - print(new_del_process.Caption+" has been deleted ") - -async def main(): - kill_task = asyncio.TaskGroup(watcher()) - print('hello world') - await asyncio.sleep(1) - print('1sec') - await kill_task - -asyncio.run(main()) diff --git a/temp/password.py b/temp/password.py deleted file mode 100644 index 6d1c9d7..0000000 --- a/temp/password.py +++ /dev/null @@ -1,16 +0,0 @@ -import random -import string -# パスワードをファイルから読み込む - -with open('password.txt', 'r') as f: - secret = f.read() - -check=int(input("password :")) - -if check==int(secret): - print("ok") - secret=0 - with open('password.txt', 'w') as file: - file.write('') -else: - print("out") \ No newline at end of file diff --git a/temp/password.txt b/temp/password.txt deleted file mode 100644 index e69de29..0000000 diff --git a/temp/powershell.py b/temp/powershell.py deleted file mode 100644 index 97f65f6..0000000 --- a/temp/powershell.py +++ /dev/null @@ -1,9 +0,0 @@ -import os - -# PowerShellコマンドを実行する関数 -def run_powershell_command(command): - # PowerShellコマンドを実行 - os.system(f"powershell -Command {command}") - -# 例としてGet-Dateコマンドを実行して現在の日時を取得 -run_powershell_command("Get-Date") diff --git a/temp/test.py b/temp/test.py deleted file mode 100644 index 03e0e7b..0000000 --- a/temp/test.py +++ /dev/null @@ -1,41 +0,0 @@ - -import discord -import string -import random - -# Intentsの設定 -intents = discord.Intents.default() -intents.message_content = True - -# 接続に必要なオブジェクトを生成 -client = discord.Client(intents=intents) - -# 起動時に動作する処理 -@client.event -async def on_ready(): - # 起動したらターミナルにログイン通知が表示される - print('ログインしました') - -# メッセージ受信時に動作する処理 -@client.event -async def on_message(message): - # メッセージ送信者がBotだった場合は無視する - if message.author.bot: - return - # 「/neko」と発言したら「にゃーん」が返る処理 - if message.content == "/neko": - await message.channel.send('にゃーん') - - if message.content == '/password': - secret=password_generator_n(4) - await message.channel.send(str(secret)) - with open('password.txt', 'a') as file: - file.write(secret) - -def password_generator_n(length): - numbers = string.digits # (1) - password = ''.join(random.choice(numbers) for _ in range(length)) # (2) - return password - -# Botの起動とDiscordサーバーへの接続 -client.run('MTI0NzA1Mzc1NzUxOTM2NDEyNw.Gh5gIt.kz1acBMxphff9mEZLLWrEdEoVD4RJwgBW5P14o') diff --git a/temp/xcel.py b/temp/xcel.py deleted file mode 100644 index 684cfc7..0000000 --- a/temp/xcel.py +++ /dev/null @@ -1,97 +0,0 @@ -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) From b1994c5def13cc573bfb8e2c83fed93034b40084 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 5 Sep 2024 21:04:09 +0900 Subject: [PATCH 051/175] =?UTF-8?q?temp=E3=83=87=E3=82=A3=E3=83=AC?= =?UTF-8?q?=E3=82=AF=E3=83=88=E3=83=AA=E3=82=92=E5=86=8D=E5=BA=A6=E9=99=A4?= =?UTF-8?q?=E5=A4=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f5c0150..541d26b 100644 --- a/.gitignore +++ b/.gitignore @@ -164,4 +164,5 @@ config/ db/ test.py data/ -export/ \ No newline at end of file +export/ +temp/ \ No newline at end of file From e41324eae484ec5783408a2c54ad76c6527d31a0 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 5 Sep 2024 21:11:34 +0900 Subject: [PATCH 052/175] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=9B=AE=E7=9A=84?= =?UTF-8?q?=E3=81=AE=E3=83=97=E3=83=AA=E3=82=BB=E3=83=83=E3=83=88=E3=81=8C?= =?UTF-8?q?=E4=BD=BF=E3=81=88=E3=81=AA=E3=81=8F=E3=81=AA=E3=82=8B=E3=83=90?= =?UTF-8?q?=E3=82=B0=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/dislocker.py b/dislocker.py index 71b330d..dc5ff67 100644 --- a/dislocker.py +++ b/dislocker.py @@ -185,12 +185,6 @@ class DL(): print("LOGGING ERROR mode w") - - - - - - class Bot(discord.Client): def password_generate(self, length): @@ -953,7 +947,7 @@ class Bot(discord.Client): mouse_number_show = "自前" else: mouse_number_show = mouse_number - if len(custom_id_split) == 5: + if len(custom_id_split) >= 5: if custom_id_split[4] == "quick": reason = custom_id_split[5] register = bot.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=reason) From 550cf83e86ae3cc8a72c448fff142b4321e27007 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 6 Sep 2024 00:09:37 +0900 Subject: [PATCH 053/175] =?UTF-8?q?=E3=83=8F=E3=83=83=E3=82=B7=E3=83=A5?= =?UTF-8?q?=E3=82=92SHA256=E3=81=B8=E5=A4=89=E6=9B=B4=20=E3=83=9E=E3=82=B9?= =?UTF-8?q?=E3=82=BF=E3=83=BC=E3=83=91=E3=82=B9=E3=83=AF=E3=83=BC=E3=83=89?= =?UTF-8?q?=E3=81=AE=E7=AE=A1=E7=90=86=E3=82=92=E3=83=87=E3=83=BC=E3=82=BF?= =?UTF-8?q?=E3=83=99=E3=83=BC=E3=82=B9=E3=81=A7=E8=A1=8C=E3=81=86=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=20=E3=83=86=E3=83=BC=E3=83=96=E3=83=AB?= =?UTF-8?q?=E3=81=AE=E4=B8=80=E9=83=A8=E3=82=AB=E3=83=A9=E3=83=A0=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 8 ++++---- dislocker_auth.py | 24 +++++++++++++++++++----- dislocker_client.py | 13 +++++++------ 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/dislocker.py b/dislocker.py index dc5ff67..e8d88d5 100644 --- a/dislocker.py +++ b/dislocker.py @@ -100,7 +100,7 @@ class DL(): 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_member_id INTEGER, password_hash VARCHAR(32), pc_uuid VARCHAR(36), pc_token VARCHAR(36), PRIMARY KEY (pc_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") + cursor.execute("CREATE TABLE pc_list (pc_number INTEGER NOT NULL, using_member_id INTEGER, password_hash VARCHAR(64), pc_uuid VARCHAR(36), pc_token VARCHAR(36), master_password VARCHAR(16), detail TEXT, PRIMARY KEY (pc_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") for i in self.pc_list: print(i) cursor.execute("INSERT INTO pc_list (pc_number) VALUES (%s)", (i,)) @@ -110,7 +110,7 @@ class DL(): find_keyboard_list_table = cursor.fetchall() print(find_keyboard_list_table) if find_keyboard_list_table[0][0] == False: - cursor.execute("CREATE TABLE keyboard_list (keyboard_number INTEGER NOT NULL, using_member_id INTEGER, PRIMARY KEY (keyboard_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") + cursor.execute("CREATE TABLE keyboard_list (keyboard_number INTEGER NOT NULL, using_member_id INTEGER, device_id TEXT, device_name TEXT, detail TEXT, PRIMARY KEY (keyboard_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") for i in self.keyboard_list: print(i) cursor.execute("INSERT INTO keyboard_list (keyboard_number) VALUES (%s)", (i,)) @@ -120,7 +120,7 @@ class DL(): find_mouse_list_table = cursor.fetchall() print(find_mouse_list_table) if find_mouse_list_table[0][0] == False: - cursor.execute("CREATE TABLE mouse_list (mouse_number INTEGER NOT NULL, using_member_id INTEGER, PRIMARY KEY (mouse_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") + cursor.execute("CREATE TABLE mouse_list (mouse_number INTEGER NOT NULL, using_member_id INTEGER, device_id TEXT, device_name TEXT, detail TEXT, PRIMARY KEY (mouse_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") for i in self.mouse_list: print(i) cursor.execute("INSERT INTO mouse_list (mouse_number) VALUES (%s)", (i,)) @@ -193,7 +193,7 @@ class Bot(discord.Client): return password def hash_genarate(self, source): - hashed = hashlib.md5(source.encode()) + hashed = hashlib.sha256(source.encode()) return hashed.hexdigest() def user_register_check(self, **kwargs): diff --git a/dislocker_auth.py b/dislocker_auth.py index 3c458e8..7c2c99b 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -5,6 +5,7 @@ from flask import Flask, request, jsonify, render_template import uuid import string import random +import hashlib config_dir_path = "./config/" server_config_path = config_dir_path + "server.json" @@ -46,8 +47,17 @@ class Auth(): def token_generate(self, length): letters = string.ascii_letters + string.digits - password = ''.join(random.choice(letters) for _ in range(length)) - return password + token = ''.join(random.choice(letters) for _ in range(length)) + return token + + def master_password_generate(self, length): + characters = string.ascii_letters + string.digits + string.punctuation + master_password = ''.join(random.choice(characters) for _ in range(length)) + return master_password + + def hash_genarate(self, source): + hashed = hashlib.sha256(source.encode()) + return hashed.hexdigest() def check(self, **kwargs): try: @@ -180,10 +190,12 @@ class Auth(): pc_record_uuid = pc_record[0][0] if pc_record_uuid == None: pc_token = self.token_generate(36) - cursor.execute("UPDATE pc_list SET pc_uuid = %s, pc_token = %s WHERE pc_number = %s", (pc_uuid, pc_token, pc_number)) + master_password = self.master_password_generate(16) + master_password_hash = self.hash_genarate(master_password) + cursor.execute("UPDATE pc_list SET pc_uuid = %s, pc_token = %s, master_password = %s WHERE pc_number = %s", (pc_uuid, pc_token, master_password, pc_number)) self.db.commit() os.remove(onetime_config_path) - return {"result": 0, "about": "ok", "output_dict": {"pc_token": pc_token}} + return {"result": 0, "about": "ok", "output_dict": {"pc_token": pc_token, "master_password": master_password, "master_password_hash": master_password_hash}} else: return {"result": 1, "about": "exist"} @@ -214,7 +226,9 @@ def register(): if onetime_password == onetime_config["onetime"]: register_result = auth.register(pc_number=pc_number, pc_uuid=pc_uuid) pc_token = register_result["output_dict"]["pc_token"] - return jsonify({'message': 'ok', 'pc_token': pc_token}), 200 + master_password = register_result["output_dict"]["master_password"] + master_password_hash = register_result["output_dict"]["master_password_hash"] + return jsonify({'message': 'ok', 'pc_token': pc_token, 'master_password': master_password, 'master_password_hash': master_password_hash}), 200 else: return jsonify({'message': 'damedesu'}), 401 else: diff --git a/dislocker_client.py b/dislocker_client.py index 40c06fb..b6ad341 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -46,7 +46,7 @@ elif os.path.isfile(client_config_path): def master_password_gen(): numbers = string.digits # (1) password = ''.join(random.choice(numbers) for _ in range(10)) # (2) - password_hash = hashlib.md5(password.encode()).hexdigest() + password_hash = hashlib.sha256(password.encode()).hexdigest() result = {"password": password, "password_hash": password_hash} return result @@ -91,11 +91,12 @@ def init(**kwargs): print("PCの情報が登録されました。") responce_json = responce.json() pc_token = str(responce_json["pc_token"]) + master_password_hash = str(responce_json["master_password_hash"]) + master_password = str(responce_json["master_password"]) client_config["pc_token"] = pc_token - - 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["master_password_hash"] = master_password_hash + + msgbox = tkinter.messagebox.showinfo(title=f"{app_name} | 初回起動を検出", message=f"初回起動のようです。\nマスターパスワードを記録しておいてください。\nBotが起動している場合は、管理者がDiscordから確認することもできます。\n\n{master_password}\n\n") client_config["initial"] = 0 with open(client_config_path, "w") as w: @@ -246,7 +247,7 @@ class Lock(customtkinter.CTkToplevel): keyboard.add_hotkey('ctrl+shift+q', self.exit) def hash_genarate(self, source): - hashed = hashlib.md5(source.encode()) + hashed = hashlib.sha256(source.encode()) return hashed.hexdigest() def auth_start(self): From 4713dc7a233e00c76d79206e1b777b4292de2d9e Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 6 Sep 2024 01:30:44 +0900 Subject: [PATCH 054/175] =?UTF-8?q?=E3=83=87=E3=83=90=E3=82=A4=E3=82=B9?= =?UTF-8?q?=E7=95=AA=E5=8F=B7=E3=81=AE=E5=85=A5=E5=8A=9B=E3=81=AE=E8=87=AA?= =?UTF-8?q?=E5=8B=95=E5=8C=96=20(#14)=20=E3=81=B8=E3=81=AE=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 42 ++++++++++-- dislocker_auth.py | 156 +++++++++++++++++++++++++++++++++++++++++--- dislocker_client.py | 93 +++++++++++++++++++++++++- 3 files changed, 276 insertions(+), 15 deletions(-) diff --git a/dislocker.py b/dislocker.py index e8d88d5..7161271 100644 --- a/dislocker.py +++ b/dislocker.py @@ -687,17 +687,51 @@ class Bot(discord.Client): if os.path.isfile(dislocker.onetime_config_path): with open(dislocker.onetime_config_path, "r") as r: onetime_config = json.load(r) - onetime = str(onetime_config["onetime"]) - await message.channel.send(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {onetime}") - + onetime = str(onetime_config["onetime"]["pc_register"]) + if onetime == None: + onetime = str(self.password_generate(8)) + onetime_config["onetime"]["pc_register"] = onetime + with open(dislocker.onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) + await message.channel.send(f"# :dizzy_face: PC登録時のワンタイムパスワードを発行します。\n# パスワード | {onetime}") + else: + await message.channel.send(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {onetime}") else: onetime = str(self.password_generate(8)) onetime_config = { - "onetime": str(onetime) + "onetime": { + "pc_register": onetime, + "device_register": None + } } with open(dislocker.onetime_config_path, "w") as w: json.dump(onetime_config, w, indent=4) await message.channel.send(f"# :dizzy_face: PC登録時のワンタイムパスワードを発行します。\n# パスワード | {onetime}") + elif msg_split[0] == "/devreg": + if os.path.isfile(dislocker.onetime_config_path): + with open(dislocker.onetime_config_path, "r") as r: + onetime_config = json.load(r) + onetime = str(onetime_config["onetime"]["device_register"]) + if onetime == None: + onetime = str(self.password_generate(8)) + onetime_config["onetime"]["device_register"] = onetime + with open(dislocker.onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) + await message.channel.send(f"# :dizzy_face: デバイス登録時のワンタイムパスワードを発行します。\n# パスワード | {onetime}") + else: + await message.channel.send(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {onetime}") + else: + onetime = str(self.password_generate(8)) + onetime_config = { + "onetime": { + "pc_register": None, + "device_register": onetime + } + } + with open(dislocker.onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) + await message.channel.send(f"# :dizzy_face: デバイス登録時のワンタイムパスワードを発行します。\n# パスワード | {onetime}") + else: await message.channel.send("# :warning: DMでの応答は、現在無効化されています。") else: diff --git a/dislocker_auth.py b/dislocker_auth.py index 7c2c99b..4600902 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -65,19 +65,43 @@ class Auth(): pc_number = int(kwargs["pc_number"]) pc_uuid = str(kwargs["pc_uuid"]) pc_token = str(kwargs["pc_token"]) + device_list = kwargs["device_list"] + keyboard_number = "own" + mouse_number = "own" if "password_hash" in kwargs: password_hash = str(kwargs["password_hash"]) cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s AND password_hash = %s AND pc_uuid = %s AND pc_token = %s", (pc_number, password_hash, pc_uuid, pc_token)) pc_info = cursor.fetchall() + if pc_info: + for device in device_list: + cursor.execute("SELECT * FROM keyboard_list WHERE device_id = %s", (device["device_id"],)) + keyboard_record = cursor.fetchall() + if keyboard_record: + keyboard_number = int(keyboard_record[0][0]) + break + else: + pass + + for device in device_list: + cursor.execute("SELECT * FROM mouse_list WHERE device_id = %s", (device["device_id"],)) + mouse_record = cursor.fetchall() + if mouse_record: + mouse_number = int(mouse_record[0][0]) + break + else: + pass + + return {"result": 0, "about": "ok", "output_dict": {"keyboard_number": keyboard_number, "mouse_number": mouse_number}} + else: + return {"result": 1, "about": "unregistered_pc"} else: cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s AND pc_uuid = %s AND pc_token = %s", (pc_number, pc_uuid, pc_token)) pc_info = cursor.fetchall() - - if pc_info: - return {"result": 0, "about": "ok"} - else: - return {"result": 1, "about": "unregistered_pc"} + if pc_info: + return {"result": 0, "about": "ok"} + else: + return {"result": 1, "about": "unregistered_pc"} except Exception as error: print("PCの登録状況を調査中にエラーが発生しました。\nエラー内容") @@ -88,7 +112,80 @@ class Auth(): finally: cursor.close() - + + def device_use_register(self, **kwargs): + try: + pc_number = int(kwargs["pc_number"]) + if "keyboard_number" in kwargs: + keyboard_number = int(kwargs["keyboard_number"]) + else: + keyboard_number = None + if "mouse_number" in kwargs: + mouse_number = int(kwargs["mouse_number"]) + else: + mouse_number = None + + cursor = self.db.cursor() + cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s", (pc_number,)) + pc_list_record = cursor.fetchall() + pc_using_member_id = pc_list_record[0][1] + if pc_using_member_id == None: + return {"result": 1, "about": "not_used"} + else: + cursor.execute("SELECT * FROM pc_usage_history WHERE member_id = %s AND pc_number = %s ORDER BY id DESC LIMIT 1", (pc_using_member_id, pc_number)) + pc_usage_history_record = cursor.fetchall() + pc_usage_history_record_id = pc_usage_history_record[0][0] + cursor.execute("UPDATE pc_usage_history SET keyboard_number = %s, mouse_number = %s WHERE id = %s", (keyboard_number, mouse_number, pc_usage_history_record_id)) + cursor.execute("UPDATE keyboard_list SET using_member_id = %s WHERE keyboard_number = %s", (pc_using_member_id, keyboard_number)) + cursor.execute("UPDATE mouse_list SET using_member_id = %s WHERE mouse_number = %s", (pc_using_member_id, mouse_number)) + self.db.commit() + return {"result": 0, "about": "ok"} + + except Exception as error: + print("デバイスの使用登録中にエラーが発生しました。\nエラー内容") + print(str(error.__class__.__name__)) + print(str(error.args)) + print(str(error)) + return {"result": 1, "about": "error"} + + def device_register(self, **kwargs): + try: + cursor = self.db.cursor() + mode = kwargs["mode"] + number = kwargs["number"] + device_id = kwargs["device_id"] + device_name = kwargs["device_name"] + + if mode == "keyboard": + keyboard_number = int(kwargs["number"]) + cursor.execute("SELECT * FROM keyboard_list WHERE keyboard_number = %s", (keyboard_number,)) + keyboard_record = cursor.fetchall() + if keyboard_record: + cursor.execute("UPDATE keyboard_list SET device_id = %s, device_name = %s WHERE keyboard_number = %s", (device_id, device_name, keyboard_number)) + self.db.commit() + return {"result": 0, "about": "ok"} + else: + cursor.execute("INSERT INTO keyboard_list (keyboard_number, device_id, device_name) VALUES (%s, %s, %s)", (keyboard_number, device_id, device_name)) + return {"result": 0, "about": "ok"} + elif mode == "mouse": + mouse_number = int(kwargs["number"]) + cursor.execute("SELECT * FROM mouse_list WHERE mouse_number = %s", (mouse_number,)) + mouse_record = cursor.fetchall() + if mouse_record: + cursor.execute("UPDATE mouse_list SET device_id = %s, device_name = %s WHERE mouse_number = %s", (device_id, device_name, mouse_number)) + self.db.commit() + return {"result": 0, "about": "ok"} + else: + cursor.execute("INSERT INTO mouse_list (mouse_number, device_id, device_name) VALUES (%s, %s, %s)", (mouse_number, device_id, device_name)) + return {"result": 0, "about": "ok"} + + except Exception as error: + print("停止処理中にエラーが発生しました。\nエラー内容") + print(str(error.__class__.__name__)) + print(str(error.args)) + print(str(error)) + return {"result": 1, "about": "error"} + def delete(self, pc_number): try: cursor = self.db.cursor() @@ -194,7 +291,6 @@ class Auth(): master_password_hash = self.hash_genarate(master_password) cursor.execute("UPDATE pc_list SET pc_uuid = %s, pc_token = %s, master_password = %s WHERE pc_number = %s", (pc_uuid, pc_token, master_password, pc_number)) self.db.commit() - os.remove(onetime_config_path) return {"result": 0, "about": "ok", "output_dict": {"pc_token": pc_token, "master_password": master_password, "master_password_hash": master_password_hash}} else: return {"result": 1, "about": "exist"} @@ -223,11 +319,14 @@ def register(): with open(onetime_config_path, "r") as r: onetime_config = json.load(r) - if onetime_password == onetime_config["onetime"]: + if onetime_password == onetime_config["onetime"]["pc_register"]: register_result = auth.register(pc_number=pc_number, pc_uuid=pc_uuid) pc_token = register_result["output_dict"]["pc_token"] master_password = register_result["output_dict"]["master_password"] master_password_hash = register_result["output_dict"]["master_password_hash"] + onetime_config["onetime"]["pcregister"] = None + with open(onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) return jsonify({'message': 'ok', 'pc_token': pc_token, 'master_password': master_password, 'master_password_hash': master_password_hash}), 200 else: return jsonify({'message': 'damedesu'}), 401 @@ -240,11 +339,14 @@ def verify(): password_hash = request.json.get('password') pc_uuid = request.json.get('pc_uuid') pc_token = request.json.get('pc_token') + devices = request.json.get('devices') + print(str(pc_number) + "の認証処理を開始...") - pc_auth = auth.check(pc_number=pc_number, password_hash=password_hash, pc_uuid=pc_uuid, pc_token=pc_token) + pc_auth = auth.check(pc_number=pc_number, password_hash=password_hash, pc_uuid=pc_uuid, pc_token=pc_token, device_list=devices) if pc_auth["result"] == 0: auth.delete(pc_number) + auth.device_use_register(pc_number=pc_number, keyboard_number=pc_auth["output_dict"]["keyboard_number"], mouse_number=pc_auth["output_dict"]["mouse_number"]) print(str(pc_number) + "の認証処理は成功しました.") return jsonify({'message': 'ok'}), 200 else: @@ -271,6 +373,40 @@ def stop(): else: return jsonify({'message': 'damedesu'}), 401 - +@app.route('/device_register', methods=['POST']) +def device_register(): + onetime_password = str(request.json.get('onetime')) + mode = str(request.json.get('mode')) + number = int(request.json.get('number')) + device_id = str(request.json.get('device_id')) + device_name = str(request.json.get('device_name')) + + if os.path.isfile(onetime_config_path): + with open(onetime_config_path, "r") as r: + onetime_config = json.load(r) + + if onetime_password == onetime_config["onetime"]["device_register"]: + if mode == "keyboard": + print("キーボードの登録処理を開始...") + device_register = auth.device_register(mode="keyboard", number=number, device_id=device_id, device_name=device_name) + if device_register["result"] == 0: + print(f"キーボード {number} 番の登録処理は成功しました.") + return jsonify({'message': 'ok'}), 200 + else: + print(f"キーボード {number} 番の登録処理は失敗しました.") + return jsonify({'message': 'error'}), 500 + elif mode == "mouse": + print("マウスの登録処理を開始...") + device_register = auth.device_register(mode="mouse", number=number, device_id=device_id, device_name=device_name) + if device_register["result"] == 0: + print(f"マウス {number} 番の登録処理は成功しました.") + return jsonify({'message': 'ok'}), 200 + else: + print(f"マウス {number} 番の登録処理は失敗しました.") + return jsonify({'message': 'error'}), 500 + else: + return jsonify({'message': 'damedesu'}), 401 + + if __name__ == '__main__': app.run(host="0.0.0.0", port=5000, debug=False) \ No newline at end of file diff --git a/dislocker_client.py b/dislocker_client.py index b6ad341..dc9367c 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -15,6 +15,7 @@ import sys import shutil import uuid import time +import win32com.client app_name = "Dislocker" dislocker_dir = os.path.dirname(os.path.abspath(sys.argv[0])) @@ -43,6 +44,50 @@ elif os.path.isfile(client_config_path): with open(client_config_path, "r") as r: client_config = json.load(r) +def get_usb_devices(self): + str_computer = "." + obj_wmi_service = win32com.client.Dispatch("WbemScripting.SWbemLocator") + obj_swem_services = obj_wmi_service.ConnectServer(str_computer, "root\\cimv2") + col_items = obj_swem_services.ExecQuery("Select * from Win32_PnPEntity where PNPDeviceID like 'USB%'") + + devices = [] + for obj_item in col_items: + devices.append({ + "device_id": obj_item.DeviceID, + "PNPDeviceID": obj_item.PNPDeviceID, + "device_name": obj_item.Description, + "name": obj_item.Name, + "Manufacturer": obj_item.Manufacturer, + "Service": obj_item.Service + }) + + return devices + +def device_register(**kwargs): + onetime = str(kwargs["onetime"]) + mode = str(kwargs["mode"]) + number = int(kwargs["number"]) + device_id = str(kwargs["device_id"]) + device_name = str(kwargs["device_name"]) + device_register_json = { + "number": number, + "device_id": device_id, + "device_name": device_name, + "mode": mode, + "onetime": onetime + } + register_url = client_config["auth_host_url"] + "/device_register" + responce = requests.post(register_url, json=device_register_json) + if responce.status_code == 200: + print("デバイスの登録に成功しました。") + return {"result": 0, "about": "ok"} + elif responce.status_code == 401: + print("認証に失敗しました。") + return {"result": 1, "about": "auth_failed"} + else: + print("内部エラーによりデバイスの登録に失敗しました。") + return {"result": 1, "about": "error"} + def master_password_gen(): numbers = string.digits # (1) password = ''.join(random.choice(numbers) for _ in range(10)) # (2) @@ -270,10 +315,29 @@ class Lock(customtkinter.CTkToplevel): self.signin_button.configure(state="normal", fg_color="#3c8dd0") self.signout_button.configure(state="normal", fg_color="#3c8dd0") + def get_usb_devices(self): + str_computer = "." + obj_wmi_service = win32com.client.Dispatch("WbemScripting.SWbemLocator") + obj_swem_services = obj_wmi_service.ConnectServer(str_computer, "root\\cimv2") + col_items = obj_swem_services.ExecQuery("Select * from Win32_PnPEntity where PNPDeviceID like 'USB%'") + + devices = [] + for obj_item in col_items: + devices.append({ + "device_id": obj_item.DeviceID, + "PNPDeviceID": obj_item.PNPDeviceID, + "Description": obj_item.Description, + "device_name": obj_item.Name, + "Manufacturer": obj_item.Manufacturer, + "Service": obj_item.Service + }) + + return devices def auth(self): self.button_disable() password = str(self.password_entry.get()) + devices = self.get_usb_devices() if len(password) == 10: print("マスターパスワードで認証を試行します。") @@ -289,13 +353,13 @@ class Lock(customtkinter.CTkToplevel): self.button_enable() self.deiconify() - print("認証サーバーにアクセスします。") auth_url = client_config["auth_host_url"] + "/verify" auth_json = { "pc_number": int(client_config["pc_number"]), "pc_uuid": str(client_config["pc_uuid"]), "pc_token": str(client_config["pc_token"]), + "devices": devices, "password": self.hash_genarate(str(self.password_entry.get())) } try: @@ -483,6 +547,33 @@ if __name__ == '__main__': pass else: pass + + elif args[1] == "devicesetup": + init_result = init(pc_number=args[2], onetime=args[3]) + if init_result == 1: + warning_msgbox = tkinter.messagebox.showwarning(title=f"{app_name} | 多重起動エラー", message=f"すでに {app_name} は実行されています。\n正常に起動しない場合は、既に起動しているプロセスを終了してから、もう一度起動してみてください。") + elif init_result == 2: + pass + else: + mode = input('登録するデバイスはキーボードとマウスのどちらですか?(keyboard/mouse): ') + usb_devices = get_usb_devices() + i = 0 + for device in usb_devices: + print(f"{str(i + 1)} 番目 | デバイス名: {device['Description']} \n製造元: {device['Manufacturer']} \nデバイスID: {device['DeviceID']}") + print("-" * 20) + i += 1 + device_num = input('どのデバイスを登録しますか?番号で指定してください: ') + device_register_num = input('そのデバイスは何番目のデバイスとして登録しますか?番号で指定してください: ') + onetime = input('ワンタイムパスワードを入力してください: ') + device_register_result = device_register(onetime=onetime, mode=mode, number=int(device_register_num), device_id=usb_devices[int(device_num) - 1]["device_id"], device_name=usb_devices[int(device_num) - 1]["device_name"]) + if device_register_result["result"] == 0: + print("登録されました。") + elif device_register_result["result"] == 1: + if device_register_result["about"] == "auth_failed": + print("認証に失敗しました。") + else: + print("エラーが発生しました。") + else: print("引数エラー。") else: From 9279edc8cab5af74e687b1f91c77d8e533b58904 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 6 Sep 2024 01:47:45 +0900 Subject: [PATCH 055/175] =?UTF-8?q?devicesetup=E3=82=92deviceregister?= =?UTF-8?q?=E3=81=AB=E6=94=B9=E5=90=8D=E3=81=A8=E3=81=A1=E3=82=87=E3=81=A3?= =?UTF-8?q?=E3=81=A8=E3=81=AE=E3=83=90=E3=82=B0=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker_client.py b/dislocker_client.py index dc9367c..0f7aa5d 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -549,7 +549,7 @@ if __name__ == '__main__': pass elif args[1] == "devicesetup": - init_result = init(pc_number=args[2], onetime=args[3]) + init_result = init() if init_result == 1: warning_msgbox = tkinter.messagebox.showwarning(title=f"{app_name} | 多重起動エラー", message=f"すでに {app_name} は実行されています。\n正常に起動しない場合は、既に起動しているプロセスを終了してから、もう一度起動してみてください。") elif init_result == 2: From 3f3130716405d00b06464cb754574de5271e95bb Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 6 Sep 2024 01:48:54 +0900 Subject: [PATCH 056/175] =?UTF-8?q?=E6=94=B9=E5=90=8D=E3=81=A7=E3=81=8D?= =?UTF-8?q?=E3=81=A6=E3=81=AA=E3=81=8B=E3=81=A3=E3=81=9F=E3=80=80=E3=81=93?= =?UTF-8?q?=E3=81=A3=E3=81=A1=E3=81=A7=E6=94=B9=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker_client.py b/dislocker_client.py index 0f7aa5d..ab3568e 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -548,7 +548,7 @@ if __name__ == '__main__': else: pass - elif args[1] == "devicesetup": + elif args[1] == "deviceregister": init_result = init() if init_result == 1: warning_msgbox = tkinter.messagebox.showwarning(title=f"{app_name} | 多重起動エラー", message=f"すでに {app_name} は実行されています。\n正常に起動しない場合は、既に起動しているプロセスを終了してから、もう一度起動してみてください。") From 75fa89497ece38b828039a03c8f9e8f34922e014 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 6 Sep 2024 01:55:19 +0900 Subject: [PATCH 057/175] =?UTF-8?q?self=E3=81=AA=E3=81=8F=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker_client.py b/dislocker_client.py index ab3568e..f40ebb9 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -44,7 +44,7 @@ elif os.path.isfile(client_config_path): with open(client_config_path, "r") as r: client_config = json.load(r) -def get_usb_devices(self): +def get_usb_devices(): str_computer = "." obj_wmi_service = win32com.client.Dispatch("WbemScripting.SWbemLocator") obj_swem_services = obj_wmi_service.ConnectServer(str_computer, "root\\cimv2") From 611bd03b80573b47d4ea0221dee83559a53a908d Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 6 Sep 2024 02:02:07 +0900 Subject: [PATCH 058/175] =?UTF-8?q?str=E3=81=B8=E3=81=AE=E5=A4=89=E6=8F=9B?= =?UTF-8?q?=E3=82=92=E6=92=A4=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dislocker.py b/dislocker.py index 7161271..df3a2a1 100644 --- a/dislocker.py +++ b/dislocker.py @@ -687,7 +687,7 @@ class Bot(discord.Client): if os.path.isfile(dislocker.onetime_config_path): with open(dislocker.onetime_config_path, "r") as r: onetime_config = json.load(r) - onetime = str(onetime_config["onetime"]["pc_register"]) + onetime = onetime_config["onetime"]["pc_register"] if onetime == None: onetime = str(self.password_generate(8)) onetime_config["onetime"]["pc_register"] = onetime @@ -711,7 +711,7 @@ class Bot(discord.Client): if os.path.isfile(dislocker.onetime_config_path): with open(dislocker.onetime_config_path, "r") as r: onetime_config = json.load(r) - onetime = str(onetime_config["onetime"]["device_register"]) + onetime = onetime_config["onetime"]["device_register"] if onetime == None: onetime = str(self.password_generate(8)) onetime_config["onetime"]["device_register"] = onetime From 7282af55e078af0efaf2a8120257a7afcaac5ddf Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 6 Sep 2024 02:04:51 +0900 Subject: [PATCH 059/175] =?UTF-8?q?=E3=83=87=E3=83=90=E3=82=A4=E3=82=B9?= =?UTF-8?q?=E4=B8=80=E8=A6=A7=E3=81=8B=E3=82=89=E5=8F=82=E7=85=A7=E3=81=99?= =?UTF-8?q?=E3=82=8B=E5=9E=8B=E5=90=8D=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker_client.py b/dislocker_client.py index f40ebb9..48ec257 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -559,7 +559,7 @@ if __name__ == '__main__': usb_devices = get_usb_devices() i = 0 for device in usb_devices: - print(f"{str(i + 1)} 番目 | デバイス名: {device['Description']} \n製造元: {device['Manufacturer']} \nデバイスID: {device['DeviceID']}") + print(f"{str(i + 1)} 番目 | デバイス名: {device['device_name']} \n製造元: {device['Manufacturer']} \nデバイスID: {device['device_id']}") print("-" * 20) i += 1 device_num = input('どのデバイスを登録しますか?番号で指定してください: ') From e97562b9b2de8dbb750f8a524961012f9069c55b Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 6 Sep 2024 02:26:52 +0900 Subject: [PATCH 060/175] =?UTF-8?q?=E3=83=87=E3=83=90=E3=82=A4=E3=82=B9?= =?UTF-8?q?=E7=95=AA=E5=8F=B7=E3=81=AE=E3=83=9C=E3=82=BF=E3=83=B3=E3=82=92?= =?UTF-8?q?=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 50 +++++++++++++++++--------------------------------- 1 file changed, 17 insertions(+), 33 deletions(-) diff --git a/dislocker.py b/dislocker.py index df3a2a1..87f2c0c 100644 --- a/dislocker.py +++ b/dislocker.py @@ -867,7 +867,7 @@ class Bot(discord.Client): pc_button_view = View(timeout=None) for i in dislocker.pc_list: - pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"pcregister_{str(i)}") + pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"mouseregister_{str(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) @@ -877,7 +877,7 @@ class Bot(discord.Client): elif msg_split[0] == "/registerbutton": pc_button_view = View(timeout=None) for i in dislocker.pc_list: - pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"pcregister_{str(i)}") + pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"mouseregister_{str(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) @@ -950,45 +950,29 @@ class Bot(discord.Client): elif custom_id_split[0] == "mouseregister": pc_number = custom_id_split[1] - keyboard_number = custom_id_split[2] - if keyboard_number == "own": - keyboard_number_show = "自前" - else: - keyboard_number_show = keyboard_number - mouse_number = custom_id_split[3] - if mouse_number == "own": - mouse_number_show = "自前" - else: - mouse_number_show = mouse_number + reason_register_view = View(timeout=15) for i in dislocker.preset_games: - reason_quick_button = reason_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"reasonregister_{str(pc_number)}_{str(keyboard_number)}_{str(mouse_number)}_quick_{str(i)}") + reason_quick_button = reason_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"reasonregister_{str(pc_number)}_quick_{str(i)}") reason_register_view.add_item(reason_quick_button) - reason_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="使用目的を入力する", custom_id=f"reasonregister_{str(pc_number)}_{str(keyboard_number)}_{str(mouse_number)}") + reason_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="使用目的を入力する", custom_id=f"reasonregister_{str(pc_number)}") reason_register_view.add_item(reason_button) - await interaction.response.send_message(f"# :regional_indicator_q: 使用目的を書いてください!\n>>> # PC番号 | {str(pc_number)}\n# キーボード番号 | {str(keyboard_number_show)}\n# マウス番号 | {str(mouse_number_show)}", view=reason_register_view, ephemeral=True) + await interaction.response.send_message(f"# :regional_indicator_q: 使用目的を書いてください!\n>>> # PC番号 | {str(pc_number)}", view=reason_register_view, ephemeral=True) elif custom_id_split[0] == "reasonregister": pc_number = custom_id_split[1] - keyboard_number = custom_id_split[2] - if keyboard_number == "own": - keyboard_number_show = "自前" - else: - keyboard_number_show = keyboard_number - mouse_number = custom_id_split[3] - if mouse_number == "own": - mouse_number_show = "自前" - else: - mouse_number_show = mouse_number - if len(custom_id_split) >= 5: - if custom_id_split[4] == "quick": + keyboard_number = "own" + mouse_number = "own" + + if len(custom_id_split) >= 3: + if custom_id_split[2] == "quick": reason = custom_id_split[5] register = bot.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=reason) if register["about"] == "ok": - await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\n## 使用目的 | {reason}", ephemeral=True) - await bot.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':white_check_mark: {register["output_dict"]["name"]} さんがPC {pc_number} の使用を開始しました。\n>>> ## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\n## 使用目的 | {reason}') - dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, キーボード番号 | {keyboard_number_show}, マウス番号 | {mouse_number_show}, 使用目的 | {reason}", flag=0) + await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## 使用目的 | {reason}", ephemeral=True) + await bot.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':white_check_mark: {register["output_dict"]["name"]} さんがPC {pc_number} の使用を開始しました。\n>>> ## PC番号 | {pc_number}\n## 使用目的 | {reason}') + dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, 使用目的 | {reason}", flag=0) elif register["about"] == "pc_already_in_use_by_you": pc_usage_history = register["pc_usage_history"] await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {pc_usage_history["keyboard_number"]}\n# マウス番号 | {pc_usage_history["mouse_number"]}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) @@ -1145,9 +1129,9 @@ class Reason(Modal): register = bot.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=self.reason_input_form.value) if register["about"] == "ok": - await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\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["output_dict"]["name"]} さんがPC {pc_number} の使用を開始しました。\n>>> ## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\n## 使用目的 | {self.reason_input_form.value}') - dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, キーボード番号 | {keyboard_number_show}, マウス番号 | {mouse_number_show}, 使用目的 | {self.reason_input_form.value}", flag=0) + await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_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["output_dict"]["name"]} さんがPC {pc_number} の使用を開始しました。\n>>> ## PC番号 | {pc_number}\n## 使用目的 | {self.reason_input_form.value}') + dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, 使用目的 | {self.reason_input_form.value}", flag=0) elif register["about"] == "pc_already_in_use_by_you": pc_usage_history = register["pc_usage_history"] await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {pc_usage_history["keyboard_number"]}\n# マウス番号 | {pc_usage_history["mouse_number"]}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) From 4836e6ae8f40785629085eb9979c61937a61febd Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 6 Sep 2024 02:29:19 +0900 Subject: [PATCH 061/175] =?UTF-8?q?=E3=83=AA=E3=82=B9=E3=83=88=E3=81=AE?= =?UTF-8?q?=E7=95=AA=E5=8F=B7=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker.py b/dislocker.py index 87f2c0c..866a0e9 100644 --- a/dislocker.py +++ b/dislocker.py @@ -967,7 +967,7 @@ class Bot(discord.Client): if len(custom_id_split) >= 3: if custom_id_split[2] == "quick": - reason = custom_id_split[5] + reason = custom_id_split[3] register = bot.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=reason) if register["about"] == "ok": await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## 使用目的 | {reason}", ephemeral=True) From cc771d5196411a826f191478564926dd4dcc657f Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 6 Sep 2024 02:39:40 +0900 Subject: [PATCH 062/175] =?UTF-8?q?get=5Fusb=5Fdevices=E3=81=8Cclass?= =?UTF-8?q?=E5=86=85=E3=81=A7=E5=8B=95=E4=BD=9C=E3=81=97=E3=81=AA=E3=81=8B?= =?UTF-8?q?=E3=81=A3=E3=81=9F=E3=83=90=E3=82=B0=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/dislocker_client.py b/dislocker_client.py index 48ec257..faf41cd 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -16,6 +16,7 @@ import shutil import uuid import time import win32com.client +import pythoncom app_name = "Dislocker" dislocker_dir = os.path.dirname(os.path.abspath(sys.argv[0])) @@ -316,23 +317,29 @@ class Lock(customtkinter.CTkToplevel): self.signout_button.configure(state="normal", fg_color="#3c8dd0") def get_usb_devices(self): - str_computer = "." - obj_wmi_service = win32com.client.Dispatch("WbemScripting.SWbemLocator") - obj_swem_services = obj_wmi_service.ConnectServer(str_computer, "root\\cimv2") - col_items = obj_swem_services.ExecQuery("Select * from Win32_PnPEntity where PNPDeviceID like 'USB%'") + try: + pythoncom.CoInitialize() + str_computer = "." + obj_wmi_service = win32com.client.Dispatch("WbemScripting.SWbemLocator") + obj_swem_services = obj_wmi_service.ConnectServer(str_computer, "root\\cimv2") + col_items = obj_swem_services.ExecQuery("Select * from Win32_PnPEntity where PNPDeviceID like 'USB%'") - devices = [] - for obj_item in col_items: - devices.append({ - "device_id": obj_item.DeviceID, - "PNPDeviceID": obj_item.PNPDeviceID, - "Description": obj_item.Description, - "device_name": obj_item.Name, - "Manufacturer": obj_item.Manufacturer, - "Service": obj_item.Service - }) + devices = [] + for obj_item in col_items: + devices.append({ + "device_id": obj_item.DeviceID, + "PNPDeviceID": obj_item.PNPDeviceID, + "Description": obj_item.Description, + "device_name": obj_item.Name, + "Manufacturer": obj_item.Manufacturer, + "Service": obj_item.Service + }) - return devices + pythoncom.CoUninitialize() + return devices + except pythoncom.com_error as e: + print("Error:", e) + return [] def auth(self): self.button_disable() From 61e01ae055aacd750eb1b871d43bd6ad2195c341 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 6 Sep 2024 16:30:10 +0900 Subject: [PATCH 063/175] =?UTF-8?q?=E3=82=A2=E3=83=83=E3=83=97=E3=83=87?= =?UTF-8?q?=E3=83=BC=E3=83=88=E3=82=92=E7=B0=A1=E7=95=A5=E5=8C=96=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=83=90=E3=83=83=E3=83=81=E3=83=95=E3=82=A1=E3=82=A4?= =?UTF-8?q?=E3=83=AB=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/update.cmd | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 script/update.cmd diff --git a/script/update.cmd b/script/update.cmd new file mode 100644 index 0000000..ccb404a --- /dev/null +++ b/script/update.cmd @@ -0,0 +1,19 @@ +@echo off +set dir=%~dp0 +cd %dir% + +set update_package_path="0" +set /P update_package_path=Abvf[gzipt@C̃pX : +if %update_package_path%=="0" (set update_package_path=C:%HOMEPATH%\Downloads\dislocker_client_latest.zip) +set dislocker_dir_path="0" +set /P dislocker_dir_path=Dislocker̃fBNg̃pX : +if %dislocker_dir_path%=="0" (set dislocker_dir_path=C:\Dislocker) +mkdir %dir%\temp_ds +tar -xf %update_package_path% -C %dir%\temp_ds +rmdir /s /q %dislocker_dir_path%\_internal +mkdir %dislocker_dir_path%\_internal +xcopy /e %dir%\temp_ds\_internal %dislocker_dir_path%\_internal +xcopy /Y %dir%\temp_ds\dislocker_client.exe %dislocker_dir_path%\dislocker_client.exe +rmdir /s /q %dir%\temp_ds +echo Abvf[gBDefendeřx߁AxNĂĂB +pause \ No newline at end of file From 7e35bd608952e19d52636aa3a184c06a742b5b9f Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 7 Sep 2024 13:40:13 +0900 Subject: [PATCH 064/175] =?UTF-8?q?=E3=83=87=E3=83=90=E3=82=A4=E3=82=B9ID?= =?UTF-8?q?=E3=82=92=E3=83=87=E3=83=90=E3=82=A4=E3=82=B9=E3=82=A4=E3=83=B3?= =?UTF-8?q?=E3=82=B9=E3=82=BF=E3=83=B3=E3=82=B9=E3=83=91=E3=82=B9=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4=20=E3=83=AF=E3=83=B3=E3=82=BF=E3=82=A4?= =?UTF-8?q?=E3=83=A0=E3=83=91=E3=82=B9=E3=83=AF=E3=83=BC=E3=83=89=E3=81=8C?= =?UTF-8?q?=E6=B6=88=E5=8E=BB=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84=E3=83=90?= =?UTF-8?q?=E3=82=B0=E3=82=92=E4=BF=AE=E6=AD=A3=20=E3=83=87=E3=83=90?= =?UTF-8?q?=E3=82=A4=E3=82=B9=E7=99=BB=E9=8C=B2=E3=81=AE=E9=9A=9BUSB?= =?UTF-8?q?=E3=83=87=E3=83=90=E3=82=A4=E3=82=B9=E3=81=A7=E3=81=AF=E3=81=AA?= =?UTF-8?q?=E3=81=8F=E5=85=A5=E5=8A=9B=E3=83=87=E3=83=90=E3=82=A4=E3=82=B9?= =?UTF-8?q?=E3=81=AE=E4=B8=80=E8=A6=A7=E3=82=92=E5=87=BA=E5=8A=9B=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=20=E8=87=AA=E5=89=8D?= =?UTF-8?q?=E3=81=AE=E3=83=87=E3=83=90=E3=82=A4=E3=82=B9=E3=81=AF0?= =?UTF-8?q?=E7=95=AA=E3=81=A8=E3=81=97=E3=81=A6=E5=87=A6=E7=90=86=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 8 +++---- dislocker_auth.py | 54 ++++++++++++++++++++++++++++++--------------- dislocker_client.py | 51 +++++++++++++++++++++++------------------- 3 files changed, 68 insertions(+), 45 deletions(-) diff --git a/dislocker.py b/dislocker.py index 866a0e9..ab724cc 100644 --- a/dislocker.py +++ b/dislocker.py @@ -84,8 +84,8 @@ class DL(): cursor = self.db.cursor() self.pc_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - self.keyboard_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - self.mouse_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + self.keyboard_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + self.mouse_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] self.preset_games = self.server_config["bot"]["preset_games"] self.debug = self.server_config["bot"]["debug"] @@ -110,7 +110,7 @@ class DL(): find_keyboard_list_table = cursor.fetchall() print(find_keyboard_list_table) if find_keyboard_list_table[0][0] == False: - cursor.execute("CREATE TABLE keyboard_list (keyboard_number INTEGER NOT NULL, using_member_id INTEGER, device_id TEXT, device_name TEXT, detail TEXT, PRIMARY KEY (keyboard_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") + cursor.execute("CREATE TABLE keyboard_list (keyboard_number INTEGER NOT NULL, using_member_id INTEGER, device_instance_path TEXT, device_name TEXT, detail TEXT, PRIMARY KEY (keyboard_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") for i in self.keyboard_list: print(i) cursor.execute("INSERT INTO keyboard_list (keyboard_number) VALUES (%s)", (i,)) @@ -120,7 +120,7 @@ class DL(): find_mouse_list_table = cursor.fetchall() print(find_mouse_list_table) if find_mouse_list_table[0][0] == False: - cursor.execute("CREATE TABLE mouse_list (mouse_number INTEGER NOT NULL, using_member_id INTEGER, device_id TEXT, device_name TEXT, detail TEXT, PRIMARY KEY (mouse_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") + cursor.execute("CREATE TABLE mouse_list (mouse_number INTEGER NOT NULL, using_member_id INTEGER, device_instance_path TEXT, device_name TEXT, detail TEXT, PRIMARY KEY (mouse_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") for i in self.mouse_list: print(i) cursor.execute("INSERT INTO mouse_list (mouse_number) VALUES (%s)", (i,)) diff --git a/dislocker_auth.py b/dislocker_auth.py index 4600902..742a398 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -75,7 +75,7 @@ class Auth(): pc_info = cursor.fetchall() if pc_info: for device in device_list: - cursor.execute("SELECT * FROM keyboard_list WHERE device_id = %s", (device["device_id"],)) + cursor.execute("SELECT * FROM keyboard_list WHERE device_instance_path = %s", (device["device_instance_path"],)) keyboard_record = cursor.fetchall() if keyboard_record: keyboard_number = int(keyboard_record[0][0]) @@ -84,7 +84,7 @@ class Auth(): pass for device in device_list: - cursor.execute("SELECT * FROM mouse_list WHERE device_id = %s", (device["device_id"],)) + cursor.execute("SELECT * FROM mouse_list WHERE device_instance_path = %s", (device["device_instance_path"],)) mouse_record = cursor.fetchall() if mouse_record: mouse_number = int(mouse_record[0][0]) @@ -116,14 +116,16 @@ class Auth(): def device_use_register(self, **kwargs): try: pc_number = int(kwargs["pc_number"]) - if "keyboard_number" in kwargs: + if kwargs["keyboard_number"] == "own": + keyboard_number = 0 + else: keyboard_number = int(kwargs["keyboard_number"]) + + if kwargs["mouse_number"] == "own": + mouse_number = 0 else: - keyboard_number = None - if "mouse_number" in kwargs: mouse_number = int(kwargs["mouse_number"]) - else: - mouse_number = None + cursor = self.db.cursor() cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s", (pc_number,)) @@ -135,9 +137,19 @@ class Auth(): cursor.execute("SELECT * FROM pc_usage_history WHERE member_id = %s AND pc_number = %s ORDER BY id DESC LIMIT 1", (pc_using_member_id, pc_number)) pc_usage_history_record = cursor.fetchall() pc_usage_history_record_id = pc_usage_history_record[0][0] + cursor.execute("UPDATE pc_usage_history SET keyboard_number = %s, mouse_number = %s WHERE id = %s", (keyboard_number, mouse_number, pc_usage_history_record_id)) - cursor.execute("UPDATE keyboard_list SET using_member_id = %s WHERE keyboard_number = %s", (pc_using_member_id, keyboard_number)) - cursor.execute("UPDATE mouse_list SET using_member_id = %s WHERE mouse_number = %s", (pc_using_member_id, mouse_number)) + + if keyboard_number == 0: + pass + else: + cursor.execute("UPDATE keyboard_list SET using_member_id = %s WHERE keyboard_number = %s", (pc_using_member_id, keyboard_number)) + + if mouse_number == 0: + pass + else: + cursor.execute("UPDATE mouse_list SET using_member_id = %s WHERE mouse_number = %s", (pc_using_member_id, mouse_number)) + self.db.commit() return {"result": 0, "about": "ok"} @@ -153,7 +165,7 @@ class Auth(): cursor = self.db.cursor() mode = kwargs["mode"] number = kwargs["number"] - device_id = kwargs["device_id"] + device_instance_path = kwargs["device_instance_path"] device_name = kwargs["device_name"] if mode == "keyboard": @@ -161,22 +173,22 @@ class Auth(): cursor.execute("SELECT * FROM keyboard_list WHERE keyboard_number = %s", (keyboard_number,)) keyboard_record = cursor.fetchall() if keyboard_record: - cursor.execute("UPDATE keyboard_list SET device_id = %s, device_name = %s WHERE keyboard_number = %s", (device_id, device_name, keyboard_number)) + cursor.execute("UPDATE keyboard_list SET device_instance_path = %s, device_name = %s WHERE keyboard_number = %s", (device_instance_path, device_name, keyboard_number)) self.db.commit() return {"result": 0, "about": "ok"} else: - cursor.execute("INSERT INTO keyboard_list (keyboard_number, device_id, device_name) VALUES (%s, %s, %s)", (keyboard_number, device_id, device_name)) + cursor.execute("INSERT INTO keyboard_list (keyboard_number, device_instance_path, device_name) VALUES (%s, %s, %s)", (keyboard_number, device_instance_path, device_name)) return {"result": 0, "about": "ok"} elif mode == "mouse": mouse_number = int(kwargs["number"]) cursor.execute("SELECT * FROM mouse_list WHERE mouse_number = %s", (mouse_number,)) mouse_record = cursor.fetchall() if mouse_record: - cursor.execute("UPDATE mouse_list SET device_id = %s, device_name = %s WHERE mouse_number = %s", (device_id, device_name, mouse_number)) + cursor.execute("UPDATE mouse_list SET device_instance_path = %s, device_name = %s WHERE mouse_number = %s", (device_instance_path, device_name, mouse_number)) self.db.commit() return {"result": 0, "about": "ok"} else: - cursor.execute("INSERT INTO mouse_list (mouse_number, device_id, device_name) VALUES (%s, %s, %s)", (mouse_number, device_id, device_name)) + cursor.execute("INSERT INTO mouse_list (mouse_number, device_instance_path, device_name) VALUES (%s, %s, %s)", (mouse_number, device_instance_path, device_name)) return {"result": 0, "about": "ok"} except Exception as error: @@ -324,7 +336,7 @@ def register(): pc_token = register_result["output_dict"]["pc_token"] master_password = register_result["output_dict"]["master_password"] master_password_hash = register_result["output_dict"]["master_password_hash"] - onetime_config["onetime"]["pcregister"] = None + onetime_config["onetime"]["pc_register"] = None with open(onetime_config_path, "w") as w: json.dump(onetime_config, w, indent=4) return jsonify({'message': 'ok', 'pc_token': pc_token, 'master_password': master_password, 'master_password_hash': master_password_hash}), 200 @@ -378,7 +390,7 @@ def device_register(): onetime_password = str(request.json.get('onetime')) mode = str(request.json.get('mode')) number = int(request.json.get('number')) - device_id = str(request.json.get('device_id')) + device_instance_path = str(request.json.get('device_instance_path')) device_name = str(request.json.get('device_name')) if os.path.isfile(onetime_config_path): @@ -388,18 +400,24 @@ def device_register(): if onetime_password == onetime_config["onetime"]["device_register"]: if mode == "keyboard": print("キーボードの登録処理を開始...") - device_register = auth.device_register(mode="keyboard", number=number, device_id=device_id, device_name=device_name) + device_register = auth.device_register(mode="keyboard", number=number, device_instance_path=device_instance_path, device_name=device_name) if device_register["result"] == 0: print(f"キーボード {number} 番の登録処理は成功しました.") + onetime_config["onetime"]["device_register"] = None + with open(onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) return jsonify({'message': 'ok'}), 200 else: print(f"キーボード {number} 番の登録処理は失敗しました.") return jsonify({'message': 'error'}), 500 elif mode == "mouse": print("マウスの登録処理を開始...") - device_register = auth.device_register(mode="mouse", number=number, device_id=device_id, device_name=device_name) + device_register = auth.device_register(mode="mouse", number=number, device_instance_path=device_instance_path, device_name=device_name) if device_register["result"] == 0: print(f"マウス {number} 番の登録処理は成功しました.") + onetime_config["onetime"]["device_register"] = None + with open(onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) return jsonify({'message': 'ok'}), 200 else: print(f"マウス {number} 番の登録処理は失敗しました.") diff --git a/dislocker_client.py b/dislocker_client.py index faf41cd..18490d9 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -45,34 +45,36 @@ elif os.path.isfile(client_config_path): with open(client_config_path, "r") as r: client_config = json.load(r) -def get_usb_devices(): +def get_input_devices(): str_computer = "." obj_wmi_service = win32com.client.Dispatch("WbemScripting.SWbemLocator") obj_swem_services = obj_wmi_service.ConnectServer(str_computer, "root\\cimv2") - col_items = obj_swem_services.ExecQuery("Select * from Win32_PnPEntity where PNPDeviceID like 'USB%'") + col_items = obj_swem_services.ExecQuery("Select * from Win32_PnPEntity where PNPDeviceID like 'HID%'") - devices = [] + input_devices = [] for obj_item in col_items: - devices.append({ + input_devices.append({ "device_id": obj_item.DeviceID, "PNPDeviceID": obj_item.PNPDeviceID, - "device_name": obj_item.Description, - "name": obj_item.Name, + "Description": obj_item.Description, + "device_name": obj_item.Name, "Manufacturer": obj_item.Manufacturer, - "Service": obj_item.Service + "Service": obj_item.Service, + "FriendlyName": obj_item.Name, # デバイスとプリンターで表示される名前 + "device_instance_path": obj_item.DeviceID # デバイスインスタンスパス }) - return devices + return input_devices def device_register(**kwargs): onetime = str(kwargs["onetime"]) mode = str(kwargs["mode"]) number = int(kwargs["number"]) - device_id = str(kwargs["device_id"]) + device_instance_path = str(kwargs["device_instance_path"]) device_name = str(kwargs["device_name"]) device_register_json = { "number": number, - "device_id": device_id, + "device_instance_path": device_instance_path, "device_name": device_name, "mode": mode, "onetime": onetime @@ -316,27 +318,29 @@ class Lock(customtkinter.CTkToplevel): self.signin_button.configure(state="normal", fg_color="#3c8dd0") self.signout_button.configure(state="normal", fg_color="#3c8dd0") - def get_usb_devices(self): + def get_input_devices(self): try: pythoncom.CoInitialize() str_computer = "." obj_wmi_service = win32com.client.Dispatch("WbemScripting.SWbemLocator") obj_swem_services = obj_wmi_service.ConnectServer(str_computer, "root\\cimv2") - col_items = obj_swem_services.ExecQuery("Select * from Win32_PnPEntity where PNPDeviceID like 'USB%'") + col_items = obj_swem_services.ExecQuery("Select * from Win32_PnPEntity where PNPDeviceID like 'HID%'") - devices = [] + input_devices = [] for obj_item in col_items: - devices.append({ - "device_id": obj_item.DeviceID, + input_devices.append({ + "DeviceID": obj_item.DeviceID, "PNPDeviceID": obj_item.PNPDeviceID, "Description": obj_item.Description, - "device_name": obj_item.Name, + "Name": obj_item.Name, "Manufacturer": obj_item.Manufacturer, - "Service": obj_item.Service + "Service": obj_item.Service, + "FriendlyName": obj_item.Name, + "DeviceInstancePath": obj_item.DeviceID }) pythoncom.CoUninitialize() - return devices + return input_devices except pythoncom.com_error as e: print("Error:", e) return [] @@ -344,7 +348,7 @@ class Lock(customtkinter.CTkToplevel): def auth(self): self.button_disable() password = str(self.password_entry.get()) - devices = self.get_usb_devices() + devices = self.get_input_devices() if len(password) == 10: print("マスターパスワードで認証を試行します。") @@ -563,16 +567,17 @@ if __name__ == '__main__': pass else: mode = input('登録するデバイスはキーボードとマウスのどちらですか?(keyboard/mouse): ') - usb_devices = get_usb_devices() + input_devices = get_input_devices() + i = 0 - for device in usb_devices: - print(f"{str(i + 1)} 番目 | デバイス名: {device['device_name']} \n製造元: {device['Manufacturer']} \nデバイスID: {device['device_id']}") + for device in input_devices: + print(f"{str(i + 1)} 番目 | デバイス名: {device['device_name']} \n製造元: {device['Manufacturer']} \nデバイスインスタンスパス: {device['device_instance_path']}") print("-" * 20) i += 1 device_num = input('どのデバイスを登録しますか?番号で指定してください: ') device_register_num = input('そのデバイスは何番目のデバイスとして登録しますか?番号で指定してください: ') onetime = input('ワンタイムパスワードを入力してください: ') - device_register_result = device_register(onetime=onetime, mode=mode, number=int(device_register_num), device_id=usb_devices[int(device_num) - 1]["device_id"], device_name=usb_devices[int(device_num) - 1]["device_name"]) + device_register_result = device_register(onetime=onetime, mode=mode, number=int(device_register_num), device_instance_path=input_devices[int(device_num) - 1]["device_instance_path"], device_name=input_devices[int(device_num) - 1]["device_name"]) if device_register_result["result"] == 0: print("登録されました。") elif device_register_result["result"] == 1: From 20fa6f1fb8315a49d9a54e45867a4cf129933d2a Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 7 Sep 2024 14:01:05 +0900 Subject: [PATCH 065/175] =?UTF-8?q?=E3=83=87=E3=83=90=E3=82=A4=E3=82=B9?= =?UTF-8?q?=E3=83=AA=E3=82=B9=E3=83=88=E3=81=AE=E3=82=AD=E3=83=BC=E5=90=8D?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dislocker_client.py b/dislocker_client.py index 18490d9..11bf302 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -329,14 +329,14 @@ class Lock(customtkinter.CTkToplevel): input_devices = [] for obj_item in col_items: input_devices.append({ - "DeviceID": obj_item.DeviceID, + "device_id": obj_item.DeviceID, "PNPDeviceID": obj_item.PNPDeviceID, "Description": obj_item.Description, - "Name": obj_item.Name, + "device_name": obj_item.Name, "Manufacturer": obj_item.Manufacturer, "Service": obj_item.Service, - "FriendlyName": obj_item.Name, - "DeviceInstancePath": obj_item.DeviceID + "FriendlyName": obj_item.Name, # デバイスとプリンターで表示される名前 + "device_instance_path": obj_item.DeviceID # デバイスインスタンスパス }) pythoncom.CoUninitialize() From ebef42cc550eae92605206c922549b7c175db77b Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 7 Sep 2024 14:06:26 +0900 Subject: [PATCH 066/175] =?UTF-8?q?=E5=86=85=E9=83=A8=E3=82=A8=E3=83=A9?= =?UTF-8?q?=E3=83=BC=E3=81=AE=E3=81=A8=E3=81=8D=E3=82=82=E3=83=91=E3=82=B9?= =?UTF-8?q?=E3=83=AF=E3=83=BC=E3=83=89=E3=81=8C=E9=96=93=E9=81=95=E3=81=A3?= =?UTF-8?q?=E3=81=A6=E3=81=84=E3=82=8B=E3=81=A8=E5=87=BA=E5=8A=9B=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=83=90=E3=82=B0=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_auth.py | 12 ++++++++---- dislocker_client.py | 9 ++++++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/dislocker_auth.py b/dislocker_auth.py index 742a398..bfcaa43 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -94,7 +94,7 @@ class Auth(): return {"result": 0, "about": "ok", "output_dict": {"keyboard_number": keyboard_number, "mouse_number": mouse_number}} else: - return {"result": 1, "about": "unregistered_pc"} + return {"result": 1, "about": "incorrect_password"} else: cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s AND pc_uuid = %s AND pc_token = %s", (pc_number, pc_uuid, pc_token)) pc_info = cursor.fetchall() @@ -361,9 +361,13 @@ def verify(): auth.device_use_register(pc_number=pc_number, keyboard_number=pc_auth["output_dict"]["keyboard_number"], mouse_number=pc_auth["output_dict"]["mouse_number"]) print(str(pc_number) + "の認証処理は成功しました.") return jsonify({'message': 'ok'}), 200 - else: - print(str(pc_number) + "の認証処理は失敗しました.") - return jsonify({'message': 'damedesu'}), 401 + elif pc_auth["result"] == 1: + if pc_auth["about"] == "incorrect_password": + print(str(pc_number) + "の認証処理はパスワードが正しくないため失敗しました.") + return jsonify({'message': 'incorrect_password'}), 401 + else: + print(str(pc_number) + "の認証処理は失敗しました.") + return jsonify({'message': 'damedesu'}), 500 @app.route('/stop', methods=['POST']) def stop(): diff --git a/dislocker_client.py b/dislocker_client.py index 11bf302..7effd52 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -378,13 +378,20 @@ class Lock(customtkinter.CTkToplevel): if responce.status_code == 200: print("認証サーバー経由で認証しました。") self.exit() - else: + elif responce.status_code == 401: print("認証サーバー経由での認証に失敗しました。") self.withdraw() msgbox = tkinter.messagebox.showinfo(title=f"{app_name} | 誤ったパスワード", message=f"パスワードが間違っています!") self.msg_subtitle_1.configure(text='パスワードが間違っています! ') self.button_enable() self.deiconify() + elif responce.status_code == 500: + 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: print("認証サーバーにアクセスできません。マスターパスワードで認証を試行します。") master_password_hash = self.hash_genarate(str(self.password_entry.get())) From 0fcfc8009f4fb8f8770e00ec4860bcc8b913f74a Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 7 Sep 2024 14:36:27 +0900 Subject: [PATCH 067/175] =?UTF-8?q?PC=E3=82=92=E8=87=AA=E5=88=86=E3=81=8C?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E4=B8=AD=E3=81=AA=E3=81=AE=E3=81=AB=E4=BB=96?= =?UTF-8?q?=E4=BA=BA=E3=81=8C=E4=BD=BF=E7=94=A8=E4=B8=AD=E3=81=A8=E5=87=BA?= =?UTF-8?q?=E5=8A=9B=E3=81=95=E3=82=8C=E3=82=8B=E3=83=90=E3=82=B0=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker.py b/dislocker.py index ab724cc..736d334 100644 --- a/dislocker.py +++ b/dislocker.py @@ -232,7 +232,7 @@ class Bot(discord.Client): else: discord_user_id = None if "member_id" in kwargs: - member_id = str(kwargs["member_id"]) + member_id = int(kwargs["member_id"]) else: member_id = None From 65403a4cca54c35b0512a964d899a2bbe6d460c2 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 7 Sep 2024 14:47:47 +0900 Subject: [PATCH 068/175] =?UTF-8?q?=E3=81=93=E3=81=AE=E5=89=8D=E3=81=AE?= =?UTF-8?q?=E3=82=B3=E3=83=9F=E3=83=83=E3=83=88=E3=81=AE=E3=83=90=E3=82=B0?= =?UTF-8?q?=E3=82=92=E4=BB=8A=E5=BA=A6=E3=81=93=E3=81=9D=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=20=E3=81=A4=E3=81=84=E3=81=A7=E3=81=AB=E3=83=A1=E3=83=83?= =?UTF-8?q?=E3=82=BB=E3=83=BC=E3=82=B8=E3=81=AE=E8=A1=A8=E7=A4=BA=E3=82=92?= =?UTF-8?q?=E8=A6=8B=E3=82=84=E3=81=99=E3=81=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/dislocker.py b/dislocker.py index 736d334..d107a0a 100644 --- a/dislocker.py +++ b/dislocker.py @@ -256,8 +256,11 @@ class Bot(discord.Client): cursor.execute("SELECT * FROM pc_usage_history WHERE member_id = %s ORDER BY id DESC LIMIT 1", (member_id,)) pc_usage_history_record = cursor.fetchall() if pc_usage_history_record: - if pc_usage_history_record[0][5] == None: - return {"result": 1, "about": "used_by_you", "pc_usage_history": {"pc_number": str(pc_usage_history_record[0][2]), "keyboard_number": str(pc_usage_history_record[0][3]), "mouse_number": str(pc_usage_history_record[0][4]), "start_time": str(pc_usage_history_record[0][5]), "use_detail": str(pc_usage_history_record[0][7])}} + if pc_usage_history_record[0][6] == None: + keyboard_number = pc_usage_history_record[0][3] + mouse_number = pc_usage_history_record[0][4] + + return {"result": 1, "about": "used_by_you", "pc_usage_history": {"pc_number": str(pc_usage_history_record[0][2]), "keyboard_number": keyboard_number, "mouse_number": mouse_number, "start_time": str(pc_usage_history_record[0][5]), "use_detail": str(pc_usage_history_record[0][7])}} else: return {"result": 0, "about": "vacent"} else: @@ -268,8 +271,10 @@ class Bot(discord.Client): cursor.execute("SELECT * FROM pc_usage_history WHERE member_id = %s ORDER BY id DESC LIMIT 1", (member_id,)) pc_usage_history_record = cursor.fetchall() if pc_usage_history_record: - if pc_usage_history_record[0][5] == None: - return {"result": 1, "about": "used_by_you", "pc_usage_history": {"pc_number": str(pc_usage_history_record[0][2]), "keyboard_number": str(pc_usage_history_record[0][3]), "mouse_number": str(pc_usage_history_record[0][4]), "start_time": str(pc_usage_history_record[0][5]), "use_detail": str(pc_usage_history_record[0][7])}} + if pc_usage_history_record[0][6] == None: + keyboard_number = pc_usage_history_record[0][3] + mouse_number = pc_usage_history_record[0][4] + return {"result": 1, "about": "used_by_you", "pc_usage_history": {"pc_number": str(pc_usage_history_record[0][2]), "keyboard_number": keyboard_number, "mouse_number": mouse_number, "start_time": str(pc_usage_history_record[0][5]), "use_detail": str(pc_usage_history_record[0][7])}} else: return {"result": 0, "about": "vacent"} else: @@ -975,7 +980,20 @@ class Bot(discord.Client): dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, 使用目的 | {reason}", flag=0) elif register["about"] == "pc_already_in_use_by_you": pc_usage_history = register["pc_usage_history"] - await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {pc_usage_history["keyboard_number"]}\n# マウス番号 | {pc_usage_history["mouse_number"]}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) + if pc_usage_history[keyboard_number] == None: + keyboard_number_show = "未認証" + elif pc_usage_history[keyboard_number] == 0: + keyboard_number_show = "自前" + else: + keyboard_number_show = str(keyboard_number) + + if pc_usage_history[mouse_number] == None: + mouse_number_show = "未認証" + elif pc_usage_history[mouse_number] == 0: + mouse_number_show = "自前" + else: + mouse_number_show = str(mouse_number) + await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {keyboard_number_show}\n# マウス番号 | {mouse_number_show}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) elif register["about"] == "pc_already_in_use_by_other": await interaction.response.send_message(f"# :man_gesturing_no: そのPCは他のメンバーによって使用されています。\n別のPC番号を指定して、再度お試しください。", ephemeral=True) elif register["about"] == "keyboard_already_in_use": @@ -1134,7 +1152,21 @@ class Reason(Modal): dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, 使用目的 | {self.reason_input_form.value}", flag=0) elif register["about"] == "pc_already_in_use_by_you": pc_usage_history = register["pc_usage_history"] - await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {pc_usage_history["keyboard_number"]}\n# マウス番号 | {pc_usage_history["mouse_number"]}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) + if pc_usage_history[keyboard_number] == None: + keyboard_number_show = "未認証" + elif pc_usage_history[keyboard_number] == 0: + keyboard_number_show = "自前" + else: + keyboard_number_show = str(keyboard_number) + + if pc_usage_history[mouse_number] == None: + mouse_number_show = "未認証" + elif pc_usage_history[mouse_number] == 0: + mouse_number_show = "自前" + else: + mouse_number_show = str(mouse_number) + await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {keyboard_number_show}\n# マウス番号 | {mouse_number_show}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) + elif register["about"] == "pc_already_in_use_by_other": await interaction.response.send_message(f"# :man_gesturing_no: そのPCは他のメンバーによって使用されています。\n別のPC番号を指定して、再度お試しください。", ephemeral=True) elif register["about"] == "keyboard_already_in_use": From 9fe9330b131b470cea1cc63de98d24d6ebc7a488 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 7 Sep 2024 14:57:58 +0900 Subject: [PATCH 069/175] =?UTF-8?q?=E3=81=93=E3=81=AE=E5=89=8D=E3=81=AE?= =?UTF-8?q?=E5=89=8D=E3=81=AE=E3=82=B3=E3=83=9F=E3=83=83=E3=83=88=E3=81=AE?= =?UTF-8?q?=E3=83=90=E3=82=B0=E3=82=92=E4=BB=8A=E5=BA=A6=E4=BB=8A=E5=BA=A6?= =?UTF-8?q?=E3=81=93=E3=81=9D=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/dislocker.py b/dislocker.py index d107a0a..26ed588 100644 --- a/dislocker.py +++ b/dislocker.py @@ -980,19 +980,19 @@ class Bot(discord.Client): dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, 使用目的 | {reason}", flag=0) elif register["about"] == "pc_already_in_use_by_you": pc_usage_history = register["pc_usage_history"] - if pc_usage_history[keyboard_number] == None: + if pc_usage_history["keyboard_number"] == None: keyboard_number_show = "未認証" - elif pc_usage_history[keyboard_number] == 0: + elif pc_usage_history["keyboard_number"] == 0: keyboard_number_show = "自前" else: - keyboard_number_show = str(keyboard_number) + keyboard_number_show = str(pc_usage_history["keyboard_number"]) - if pc_usage_history[mouse_number] == None: + if pc_usage_history["mouse_number"] == None: mouse_number_show = "未認証" - elif pc_usage_history[mouse_number] == 0: + elif pc_usage_history["mouse_number"] == 0: mouse_number_show = "自前" else: - mouse_number_show = str(mouse_number) + mouse_number_show = str(pc_usage_history["mouse_number"]) await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {keyboard_number_show}\n# マウス番号 | {mouse_number_show}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) elif register["about"] == "pc_already_in_use_by_other": await interaction.response.send_message(f"# :man_gesturing_no: そのPCは他のメンバーによって使用されています。\n別のPC番号を指定して、再度お試しください。", ephemeral=True) @@ -1152,21 +1152,21 @@ class Reason(Modal): dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, 使用目的 | {self.reason_input_form.value}", flag=0) elif register["about"] == "pc_already_in_use_by_you": pc_usage_history = register["pc_usage_history"] - if pc_usage_history[keyboard_number] == None: + if pc_usage_history["keyboard_number"] == None: keyboard_number_show = "未認証" - elif pc_usage_history[keyboard_number] == 0: + elif pc_usage_history["keyboard_number"] == 0: keyboard_number_show = "自前" else: - keyboard_number_show = str(keyboard_number) + keyboard_number_show = str(pc_usage_history["keyboard_number"]) - if pc_usage_history[mouse_number] == None: + if pc_usage_history["mouse_number"] == None: mouse_number_show = "未認証" - elif pc_usage_history[mouse_number] == 0: + elif pc_usage_history["mouse_number"] == 0: mouse_number_show = "自前" else: - mouse_number_show = str(mouse_number) + mouse_number_show = str(pc_usage_history["mouse_number"]) await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {keyboard_number_show}\n# マウス番号 | {mouse_number_show}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) - + elif register["about"] == "pc_already_in_use_by_other": await interaction.response.send_message(f"# :man_gesturing_no: そのPCは他のメンバーによって使用されています。\n別のPC番号を指定して、再度お試しください。", ephemeral=True) elif register["about"] == "keyboard_already_in_use": From e4b99a85ff23d571d0b5cfe5118cb06278f9f2fb Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 7 Sep 2024 15:30:31 +0900 Subject: [PATCH 070/175] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=9B=AE=E7=9A=84?= =?UTF-8?q?=E3=81=8C=E5=8F=96=E5=BE=97=E3=81=A7=E3=81=8D=E3=81=AA=E3=81=84?= =?UTF-8?q?=E3=83=90=E3=82=B0=E3=81=B8=E3=81=AE=E6=9A=AB=E5=AE=9A=E7=9A=84?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dislocker.py b/dislocker.py index 26ed588..19a7cfc 100644 --- a/dislocker.py +++ b/dislocker.py @@ -407,7 +407,7 @@ class Bot(discord.Client): else: return {"result": 1, "about": "pc_already_in_use_by_other"} else: - return {"result": 1, "about": "pc_already_in_use_by_you", "pc_usage_history": {"pc_number": pc_check_self["pc_usage_history"]["pc_number"], "keyboard_number": pc_check_self["pc_usage_history"]["keyboard_number"], "mouse_number": pc_check_self["pc_usage_history"]["mouse_number"], "start_time": pc_check_self["pc_usage_history"]["start_time"]}, "use_detail": pc_check_self["pc_usage_history"]["use_detail"]} + return {"result": 1, "about": "pc_already_in_use_by_you", "pc_usage_history": {"pc_number": pc_check_self["pc_usage_history"]["pc_number"], "keyboard_number": pc_check_self["pc_usage_history"]["keyboard_number"], "mouse_number": pc_check_self["pc_usage_history"]["mouse_number"], "start_time": pc_check_self["pc_usage_history"]["start_time"], "use_detail": pc_check_self["pc_usage_history"]["use_detail"]}} else: return {"result": 1, "about": "user_data_not_found"} except Exception as error: @@ -993,7 +993,8 @@ class Bot(discord.Client): mouse_number_show = "自前" else: mouse_number_show = str(pc_usage_history["mouse_number"]) - await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {keyboard_number_show}\n# マウス番号 | {mouse_number_show}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) + await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {keyboard_number_show}\n# マウス番号 | {mouse_number_show}\n# 使用開始時刻 | {pc_usage_history["start_time"]}", ephemeral=True) + #await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {keyboard_number_show}\n# マウス番号 | {mouse_number_show}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) elif register["about"] == "pc_already_in_use_by_other": await interaction.response.send_message(f"# :man_gesturing_no: そのPCは他のメンバーによって使用されています。\n別のPC番号を指定して、再度お試しください。", ephemeral=True) elif register["about"] == "keyboard_already_in_use": @@ -1165,7 +1166,9 @@ class Reason(Modal): mouse_number_show = "自前" else: mouse_number_show = str(pc_usage_history["mouse_number"]) - await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {keyboard_number_show}\n# マウス番号 | {mouse_number_show}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) + + await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {keyboard_number_show}\n# マウス番号 | {mouse_number_show}\n# 使用開始時刻 | {pc_usage_history["start_time"]}", ephemeral=True) + #await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {keyboard_number_show}\n# マウス番号 | {mouse_number_show}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) elif register["about"] == "pc_already_in_use_by_other": await interaction.response.send_message(f"# :man_gesturing_no: そのPCは他のメンバーによって使用されています。\n別のPC番号を指定して、再度お試しください。", ephemeral=True) From 6bcb570b105ac8c582fc1ca7fd83602d1842d38a Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Tue, 10 Sep 2024 18:11:41 +0900 Subject: [PATCH 071/175] =?UTF-8?q?=E7=B5=82=E4=BA=86=E5=87=A6=E7=90=86?= =?UTF-8?q?=E3=82=92=E7=B0=A1=E7=95=A5=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/dislocker_client.py b/dislocker_client.py index 7effd52..27c39ce 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -454,14 +454,6 @@ class Stop(): def run(self): stop_thread = threading.Thread(target=self.stop) stop_thread.run() - run_notify = Notification( - app_id='Dislocker', - title='終了処理を実行中', - msg='終了処理を実行しています。\nPCがシャットダウンするまで、そのままでお待ち下さい。', - icon=resource_path + r'\success.png' - ) - run_notify.set_audio(audio.Default, loop=False) - run_notify.show() def delete_appdata(self, **kwargs): process_name = kwargs["process_name"] @@ -533,13 +525,10 @@ class Stop(): print("停止処理は成功しました。") elif responce.status_code == 401: print("認証に失敗しました。") - tkinter.messagebox.showwarning(title=f"{app_name} | エラー", message=f"認証に失敗しました。\nDiscordサーバーの指示に従って、停止処理を自身で行ってください。") 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() @@ -550,7 +539,7 @@ if __name__ == '__main__': if args[1] == "stop": init_result = init() if init_result == 1: - warning_msgbox = tkinter.messagebox.showwarning(title=f"{app_name} | 多重起動エラー", message=f"もう終了処理を行っています。\nPCがシャットダウンするまで、もう少しお待ちください。") + print("多重起動を検出。") elif init_result == 2: pass else: From a28df9ad77f9ca12907f0347aeb0fa508ad23367 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Tue, 10 Sep 2024 19:18:25 +0900 Subject: [PATCH 072/175] =?UTF-8?q?log=E3=82=92=E9=99=A4=E5=A4=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 541d26b..080152b 100644 --- a/.gitignore +++ b/.gitignore @@ -165,4 +165,5 @@ db/ test.py data/ export/ -temp/ \ No newline at end of file +temp/ +log/ \ No newline at end of file From ab6e77a99f2d7480e9ed1bfa06bddd1ff744ef6e Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Tue, 10 Sep 2024 19:21:01 +0900 Subject: [PATCH 073/175] =?UTF-8?q?=E5=81=9C=E6=AD=A2=E5=87=A6=E7=90=86?= =?UTF-8?q?=E3=81=8C=E3=81=A7=E3=81=8D=E3=81=AA=E3=81=84=E3=83=90=E3=82=B0?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_auth.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dislocker_auth.py b/dislocker_auth.py index bfcaa43..a4b32be 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -65,7 +65,10 @@ class Auth(): pc_number = int(kwargs["pc_number"]) pc_uuid = str(kwargs["pc_uuid"]) pc_token = str(kwargs["pc_token"]) - device_list = kwargs["device_list"] + if "device_list" in kwargs: + device_list = kwargs["device_list"] + else: + pass keyboard_number = "own" mouse_number = "own" From 7b75da62a302510285c699e062bcd5bba4f307e1 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Wed, 11 Sep 2024 16:28:10 +0900 Subject: [PATCH 074/175] =?UTF-8?q?=E3=83=9B=E3=82=B9=E3=83=88URL=E3=82=92?= =?UTF-8?q?=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89=E3=83=A9=E3=82=A4=E3=83=B3?= =?UTF-8?q?=E3=81=8B=E3=82=89=E6=8C=87=E5=AE=9A=E3=81=A7=E3=81=8D=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/dislocker_client.py b/dislocker_client.py index 27c39ce..8b7767d 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -125,6 +125,11 @@ def init(**kwargs): else: tkinter.messagebox.showerror(title=f"{app_name} | 登録時にエラー", message=f"登録時にエラーが発生しました。\nワンタイムパスワードが指定されていません。1個目の引数にPC番号、2個目の引数にワンタイムパスワードを指定して、もう一度お試しください。") return 2 + + if "host_url" in kwargs: + client_config["auth_host_url"] = str(kwargs["host_url"]) + else: + pass register_url = client_config["auth_host_url"] + "/register" register_json = { @@ -546,8 +551,14 @@ if __name__ == '__main__': stop = Stop() stop.run() - elif args[1] == "setup": - init_result = init(pc_number=args[2], onetime=args[3]) + elif args[1] == "setup": + if len(args) == 4: + init_result = init(pc_number=args[2], onetime=args[3]) + elif len(args) == 5: + init_result = init(pc_number=args[2], onetime=args[3], host_url=args[4]) + else: + print("引数エラー。") + error_msgbox = tkinter.messagebox.showerror(title=f"{app_name} | 引数エラー", message=f"引数が多すぎるか、少なすぎます。\n引数がPC番号、ワンタイムパスワード、ホストURLの順で正しく指定されているか確認してください。") if init_result == 1: warning_msgbox = tkinter.messagebox.showwarning(title=f"{app_name} | 多重起動エラー", message=f"すでに {app_name} は実行されています。\n正常に起動しない場合は、既に起動しているプロセスを終了してから、もう一度起動してみてください。") elif init_result == 2: From 20b46e8c405a453da246c26c68daecefc34f4c14 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Mon, 23 Sep 2024 22:24:08 +0900 Subject: [PATCH 075/175] =?UTF-8?q?config=E3=83=95=E3=82=A1=E3=82=A4?= =?UTF-8?q?=E3=83=AB=E3=81=AE=E5=80=A4=E3=82=92=E3=82=8F=E3=81=8B=E3=82=8A?= =?UTF-8?q?=E3=82=84=E3=81=99=E3=81=8F=20etc...=20hard=5Flock=E3=83=A2?= =?UTF-8?q?=E3=83=BC=E3=83=89=E3=81=AE=E5=AE=9F=E8=A3=85=20subprocess?= =?UTF-8?q?=E3=81=AE=E5=AE=9F=E8=A1=8C=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89?= =?UTF-8?q?=E3=81=AE=E5=85=A5=E5=8A=9B=E3=82=92=E3=83=AA=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=81=8B=E3=82=89=E6=96=87=E5=AD=97=E5=88=97=E3=81=AB=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 57 ++++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/dislocker_client.py b/dislocker_client.py index 8b7767d..86a3332 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -23,6 +23,10 @@ dislocker_dir = os.path.dirname(os.path.abspath(sys.argv[0])) os.chdir(dislocker_dir) +sp_startupinfo = subprocess.STARTUPINFO() +sp_startupinfo.dwFlags = subprocess.STARTF_USESHOWWINDOW +sp_startupinfo.wShowWindow = subprocess.SW_HIDE + 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" @@ -31,14 +35,15 @@ if not os.path.isfile(client_config_path): os.mkdir(config_dir_path) client_config = { - "initial": 1, + "initial": True, "auth_host_url": "http://localhost", "pc_number": 1, "master_password_hash": "", - "testing": 0, - "eraser": 1, + "testing": False, + "eraser": True, "pc_uuid": "", - "pc_token": "" + "pc_token": "", + "hard_lock": False } elif os.path.isfile(client_config_path): @@ -99,9 +104,6 @@ def master_password_gen(): return result 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") @@ -110,7 +112,7 @@ def init(**kwargs): else: return 1 - if client_config["initial"] == 1: + if client_config["initial"] == True: pc_uuid = uuid.uuid4() client_config["pc_uuid"] = str(pc_uuid) @@ -150,7 +152,7 @@ def init(**kwargs): client_config["master_password_hash"] = master_password_hash msgbox = tkinter.messagebox.showinfo(title=f"{app_name} | 初回起動を検出", message=f"初回起動のようです。\nマスターパスワードを記録しておいてください。\nBotが起動している場合は、管理者がDiscordから確認することもできます。\n\n{master_password}\n\n") - client_config["initial"] = 0 + client_config["initial"] = False with open(client_config_path, "w") as w: json.dump(client_config, w, indent=4) @@ -159,6 +161,14 @@ def init(**kwargs): msgbox = tkinter.messagebox.showerror(title=f"{app_name} | 登録時にエラー", message=f"登録時にエラーが発生しました。\nワンタイムパスワードが間違っている可能性があります。") return 2 else: + if client_config["hard_lock"] == True: + exit_explorer = subprocess.run('taskkill /f /im explorer.exe', startupinfo=sp_startupinfo, stdout=subprocess.PIPE, text=True) + if exit_explorer.returncode == 0: + pass + else: + signout_session = subprocess.run('shutdown /l /f /t 3', startupinfo=sp_startupinfo, stdout=subprocess.PIPE, text=True) + error_msgbox = tkinter.messagebox.showerror(title=f"{app_name} | 初回処理のエラー", message=f"初回処理の実行にエラーが発生しました。\n自動的にサインアウトされます。") + return 1 return 0 @@ -167,7 +177,7 @@ class App(customtkinter.CTk): super().__init__() self.title(f"{app_name} | ロック中") self.iconbitmap(default=resource_path + '\\icon\\dislocker.ico') - if client_config["testing"] == 1: + if client_config["testing"] == True: pass else: self.attributes('-fullscreen', True) @@ -182,6 +192,11 @@ class App(customtkinter.CTk): def exit(self): self.unlock_taskmgr() + if client_config["hard_lock"] == True: + self.wake_explorer() + else: + pass + self.toast() self.destroy() @@ -191,13 +206,17 @@ class App(customtkinter.CTk): keyboard.block_key(i) 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', '/f']) + block = subprocess.run('reg add HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System /v DisableTaskMgr /t REG_DWORD /d 1 /f', startupinfo=sp_startupinfo, stdout=subprocess.PIPE, text=True) print(block) def unlock_taskmgr(self): - unlock = subprocess.run(['reg', 'delete', 'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System', '/v', 'DisableTaskMgr', '/f']) + unlock = subprocess.run('reg delete HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System /v DisableTaskMgr /f', startupinfo=sp_startupinfo, stdout=subprocess.PIPE, text=True) print(unlock) + def wake_explorer(self): + wake_explorer = subprocess.Popen('explorer', startupinfo=sp_startupinfo, stdout=subprocess.PIPE, text=True) + + def toast(self): success = Notification( app_id='Dislocker', @@ -215,7 +234,7 @@ class App(customtkinter.CTk): class Lock(customtkinter.CTkToplevel): def __init__(self): super().__init__() - if client_config["testing"] == 1: + if client_config["testing"] == True: self.title(f'{app_name} | PC番号 {client_config["pc_number"]} | テストモード') else: self.title(f'{app_name} | PC番号 {client_config["pc_number"]} | ロックされています') @@ -349,7 +368,7 @@ class Lock(customtkinter.CTkToplevel): except pythoncom.com_error as e: print("Error:", e) return [] - + def auth(self): self.button_disable() password = str(self.password_entry.get()) @@ -414,7 +433,7 @@ class Lock(customtkinter.CTkToplevel): def signout(self): app.unlock_taskmgr() self.destroy() - signout_command = subprocess.run(['shutdown', '/l', '/f']) + signout_command = subprocess.run('shutdown /l /f', startupinfo=sp_startupinfo, stdout=subprocess.PIPE, text=True) print(signout_command) @@ -434,7 +453,7 @@ class Lock(customtkinter.CTkToplevel): class Help(customtkinter.CTkToplevel): def __init__(self): super().__init__() - if client_config["testing"] == 1: + if client_config["testing"] == True: self.title(f'{app_name} | ヘルプ | テストモード') else: self.title(f'{app_name} | ヘルプ') @@ -475,7 +494,7 @@ class Stop(): i += 1 try: # プロセスの終了 - subprocess.run(['taskkill', '/f', '/t', '/im', process_name]) + subprocess.run(f'taskkill /f /t /im {process_name}', startupinfo=sp_startupinfo, stdout=subprocess.PIPE, text=True) print(f"{process_name} を終了しました。") time.sleep(0.1) # ディレクトリの削除 @@ -503,11 +522,11 @@ class Stop(): def shutdown(self): - shutdown_command = subprocess.run(['shutdown', '/s', '/t', '1']) + shutdown_command = subprocess.run('shutdown /s /f /t 0', startupinfo=sp_startupinfo, stdout=subprocess.PIPE, text=True) def stop(self): print("停止処理を実行。") - if client_config["eraser"] == 1: + if client_config["eraser"] == True: 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") From 83ef0bbd079a27d1d1fb93784cff30430f198a27 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Mon, 23 Sep 2024 23:06:26 +0900 Subject: [PATCH 076/175] =?UTF-8?q?=E3=83=9B=E3=82=B9=E3=83=88URL=E3=82=92?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/setup.cmd | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/script/setup.cmd b/script/setup.cmd index 62c2e0f..3cc379c 100644 --- a/script/setup.cmd +++ b/script/setup.cmd @@ -16,7 +16,11 @@ if %ERRORLEVEL% == 0 ( if %ERRORLEVEL% == 1 ( echo V[gJbg̍쐬ŃG[܂B ) + set /P pc_number=PCԍ set /P onetime=^CpX[h -start %dir%dislocker_client.exe setup %pc_number% %onetime% +set /P host_url=zXgURL (Kvȏꍇ̂) + +start %dir%dislocker_client.exe setup %pc_number% %onetime% %host_url% + pause \ No newline at end of file From 8c8e9e6f06f71a6c1820a52c8461272792552c2f Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Mon, 23 Sep 2024 23:11:47 +0900 Subject: [PATCH 077/175] =?UTF-8?q?hard=5Flock=E3=81=AE=E3=82=A8=E3=82=AF?= =?UTF-8?q?=E3=82=B9=E3=83=97=E3=83=AD=E3=83=BC=E3=83=A9=E3=83=BC=E3=81=AE?= =?UTF-8?q?=E7=B5=82=E4=BA=86=E3=82=92App=E8=B5=B7=E5=8B=95=E6=99=82?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/dislocker_client.py b/dislocker_client.py index 86a3332..8efd145 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -161,6 +161,13 @@ def init(**kwargs): msgbox = tkinter.messagebox.showerror(title=f"{app_name} | 登録時にエラー", message=f"登録時にエラーが発生しました。\nワンタイムパスワードが間違っている可能性があります。") return 2 else: + return 0 + + +class App(customtkinter.CTk): + def __init__(self): + super().__init__() + if client_config["hard_lock"] == True: exit_explorer = subprocess.run('taskkill /f /im explorer.exe', startupinfo=sp_startupinfo, stdout=subprocess.PIPE, text=True) if exit_explorer.returncode == 0: @@ -168,13 +175,7 @@ def init(**kwargs): else: signout_session = subprocess.run('shutdown /l /f /t 3', startupinfo=sp_startupinfo, stdout=subprocess.PIPE, text=True) error_msgbox = tkinter.messagebox.showerror(title=f"{app_name} | 初回処理のエラー", message=f"初回処理の実行にエラーが発生しました。\n自動的にサインアウトされます。") - return 1 - return 0 - - -class App(customtkinter.CTk): - def __init__(self): - super().__init__() + self.title(f"{app_name} | ロック中") self.iconbitmap(default=resource_path + '\\icon\\dislocker.ico') if client_config["testing"] == True: From b090e27056951bbe6e395d7442a083136459fa7c Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Mon, 23 Sep 2024 23:50:33 +0900 Subject: [PATCH 078/175] =?UTF-8?q?=E6=97=A2=E3=81=AB=E9=96=8B=E3=81=8B?= =?UTF-8?q?=E3=82=8C=E3=81=A6=E3=81=84=E3=82=8B=E3=82=A6=E3=82=A3=E3=83=B3?= =?UTF-8?q?=E3=83=89=E3=82=A6=E3=82=92=E3=83=AD=E3=83=83=E3=82=AF=E5=89=8D?= =?UTF-8?q?=E3=81=AB=E6=9C=80=E5=B0=8F=E5=8C=96=E3=81=99=E3=82=8B=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dislocker_client.py b/dislocker_client.py index 8efd145..8f9ba1e 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -181,6 +181,7 @@ class App(customtkinter.CTk): if client_config["testing"] == True: pass else: + shutup_window = keyboard.press_and_release('windows + d') self.attributes('-fullscreen', True) self.attributes('-topmost', True) self.block_taskmgr() From 1e1ab94dcf39e55a8c43985cdda9c3c5f974c2c8 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Tue, 24 Sep 2024 00:10:58 +0900 Subject: [PATCH 079/175] =?UTF-8?q?=E3=81=9D=E3=82=8C=E3=81=9E=E3=82=8C?= =?UTF-8?q?=E3=81=AE=E3=83=96=E3=83=AD=E3=83=83=E3=82=AF=E5=87=A6=E7=90=86?= =?UTF-8?q?=E3=81=AE=E9=A0=86=E7=95=AA=E3=82=92=E8=A6=8B=E7=9B=B4=E3=81=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/dislocker_client.py b/dislocker_client.py index 8f9ba1e..d3517ab 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -167,7 +167,16 @@ def init(**kwargs): class App(customtkinter.CTk): def __init__(self): super().__init__() - + + if client_config["testing"] == True: + pass + else: + self.block_taskmgr() + self.block_key() + shutup_window = keyboard.press_and_release('windows + d') + self.attributes('-fullscreen', True) + self.attributes('-topmost', True) + if client_config["hard_lock"] == True: exit_explorer = subprocess.run('taskkill /f /im explorer.exe', startupinfo=sp_startupinfo, stdout=subprocess.PIPE, text=True) if exit_explorer.returncode == 0: @@ -178,15 +187,6 @@ class App(customtkinter.CTk): self.title(f"{app_name} | ロック中") self.iconbitmap(default=resource_path + '\\icon\\dislocker.ico') - if client_config["testing"] == True: - pass - else: - shutup_window = keyboard.press_and_release('windows + d') - self.attributes('-fullscreen', True) - self.attributes('-topmost', True) - self.block_taskmgr() - self.block_key() - self.frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') self.frame.grid(row=0, column=0, sticky='nsew') From 6b617a6a63b0d0c7bf79856b6574f0cdf184aaea Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Tue, 24 Sep 2024 14:27:21 +0900 Subject: [PATCH 080/175] =?UTF-8?q?=E3=83=80=E3=82=A6=E3=83=B3=E3=83=AD?= =?UTF-8?q?=E3=83=BC=E3=83=89=E7=94=A8=E3=81=AE=E3=82=B9=E3=82=AF=E3=83=AA?= =?UTF-8?q?=E3=83=97=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/download.cmd | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 script/download.cmd diff --git a/script/download.cmd b/script/download.cmd new file mode 100644 index 0000000..5584a13 --- /dev/null +++ b/script/download.cmd @@ -0,0 +1,15 @@ +@echo off +set dir=%~dp0 +cd %dir% + +set download_url="" + +curl -L %download_url% -o %dir%\dislocker_client.zip + +mkdir %dir%\temp_ds +tar -xf %dir%\dislocker_client.zip -C %dir%\temp_ds + +mkdir C:\ProgramData\Dislocker +xcopy /e %dir%\temp_ds C:\ProgramData\Dislocker +rmdir /s /q %dir%\temp_ds +C:\ProgramData\Dislocker\setup.cmd \ No newline at end of file From 99fd556319e14f0e97ee5fddf9a23379dc8f6428 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Tue, 24 Sep 2024 14:27:46 +0900 Subject: [PATCH 081/175] =?UTF-8?q?=E6=96=87=E8=A8=80=E3=81=AE=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/setup.cmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/setup.cmd b/script/setup.cmd index 3cc379c..5855fb6 100644 --- a/script/setup.cmd +++ b/script/setup.cmd @@ -19,7 +19,7 @@ if %ERRORLEVEL% == 1 ( set /P pc_number=PCԍ set /P onetime=^CpX[h -set /P host_url=zXgURL (Kvȏꍇ̂) +set /P host_url=zXgURL (Kvȏꍇ̂) start %dir%dislocker_client.exe setup %pc_number% %onetime% %host_url% From 71ca2c0cda309873ed931ea89b81ad965984a124 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Wed, 25 Sep 2024 14:50:11 +0900 Subject: [PATCH 082/175] =?UTF-8?q?=E3=82=B9=E3=83=A9=E3=83=83=E3=82=B7?= =?UTF-8?q?=E3=83=A5=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89=E3=83=86=E3=82=B9?= =?UTF-8?q?=E3=83=88=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 142 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 dislocker_slash.py diff --git a/dislocker_slash.py b/dislocker_slash.py new file mode 100644 index 0000000..996c9b5 --- /dev/null +++ b/dislocker_slash.py @@ -0,0 +1,142 @@ +import discord +import os +import json +import psycopg2 +import datetime +import asyncio + +class DL(): + def __init__(self): + self.config_dir_path = "./config/" + self.export_dir_path = "./export/" + self.log_dir_path = "./log/" + self.server_config_path = self.config_dir_path + "server.json" + self.onetime_config_path = self.config_dir_path + "onetime.json" + self.log_path = self.log_dir_path + "dislocker.txt" + 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" + }, + "preset_games": ["TEST1", "TEST2", "TEST3", "TEST4", "TEST5"], + "admin_user_id": "TYPE HERE CHANNEL ID (YOU MUST USE INT !!!!)", + "debug": False + } + } + + 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 not os.path.isdir(self.log_dir_path): + print("log ディレクトリが見つかりません... 作成します。") + os.mkdir(self.log_dir_path) + + + self.init_result = "ok" + + except (Exception) as error: + print("初回処理でエラーが発生しました。\nエラー内容\n" + str(error)) + self.init_result = "error" + + finally: + pass + + def log(self, **kwargs): + if self.debug == True: + flag = 1 + else: + if "flag" in kwargs: + if kwargs["flag"] == 1: + flag = 1 + else: + flag = 0 + else: + flag = 0 + + if flag == 1: + title = str(kwargs["title"]) + if "message" in kwargs: + message = str(kwargs["message"]) + else: + message = None + + current_datetime = str(datetime.now()) + + if message == None: + detail = f"{current_datetime} | {title}\n" + else: + detail = f"{current_datetime} | {title}\n{message}\n" + + print(detail) + + if os.path.isfile(self.log_path): + try: + with open(self.log_path, "a", encoding="utf-8") as a: + a.write(detail) + except: + print("LOGGING ERROR mode a") + else: + try: + with open(self.log_path, "w", encoding="utf-8") as w: + w.write(detail) + except: + print("LOGGING ERROR mode w") + +intents = discord.Intents.default() +intents.message_content = True + +client = discord.Client(intents=intents) +tree = discord.app_commands.CommandTree(client) + +@client.event +async def on_ready(): + print("Bot is ready.") + print("Logged in as") + print(client.user.name) + print(client.user.id) + print("------") + await tree.sync() + +@tree.command(name="use", description="パソコンの使用登録をします。") +async def use(interaction: discord.Interaction): + await interaction.response.send_message("パソコンの使用登録をします。", ephemeral=True) + +dislocker = DL() +client.run(dislocker.server_config["bot"]["token"]) \ No newline at end of file From fd8b5e3a8b01f7ea23e841b58618ba108e232ec2 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Wed, 25 Sep 2024 22:46:53 +0900 Subject: [PATCH 083/175] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=99=BB=E9=8C=B2?= =?UTF-8?q?=E3=80=81=E4=BD=BF=E7=94=A8=E5=81=9C=E6=AD=A2=E3=81=8C=E3=81=A7?= =?UTF-8?q?=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 566 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 561 insertions(+), 5 deletions(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index 996c9b5..14e5fda 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -4,6 +4,10 @@ import json import psycopg2 import datetime import asyncio +import string +import random +import hashlib +import openpyxl class DL(): def __init__(self): @@ -67,9 +71,66 @@ class DL(): if not os.path.isdir(self.log_dir_path): print("log ディレクトリが見つかりません... 作成します。") os.mkdir(self.log_dir_path) - - self.init_result = "ok" + 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] + self.keyboard_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + self.mouse_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + self.preset_games = self.server_config["bot"]["preset_games"] + self.debug = self.server_config["bot"]["debug"] + + 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 (member_id SERIAL NOT NULL, name TEXT NOT NULL, discord_user_name TEXT NOT NULL, discord_user_id TEXT NOT NULL, PRIMARY KEY (member_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_member_id INTEGER, password_hash VARCHAR(64), pc_uuid VARCHAR(36), pc_token VARCHAR(36), master_password VARCHAR(16), detail TEXT, PRIMARY KEY (pc_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") + for i in self.pc_list: + print(i) + cursor.execute("INSERT INTO pc_list (pc_number) VALUES (%s)", (i,)) + self.db.commit() + + cursor.execute("SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'keyboard_list')") + find_keyboard_list_table = cursor.fetchall() + print(find_keyboard_list_table) + if find_keyboard_list_table[0][0] == False: + cursor.execute("CREATE TABLE keyboard_list (keyboard_number INTEGER NOT NULL, using_member_id INTEGER, device_instance_path TEXT, device_name TEXT, detail TEXT, PRIMARY KEY (keyboard_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") + for i in self.keyboard_list: + print(i) + cursor.execute("INSERT INTO keyboard_list (keyboard_number) VALUES (%s)", (i,)) + self.db.commit() + + cursor.execute("SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'mouse_list')") + find_mouse_list_table = cursor.fetchall() + print(find_mouse_list_table) + if find_mouse_list_table[0][0] == False: + cursor.execute("CREATE TABLE mouse_list (mouse_number INTEGER NOT NULL, using_member_id INTEGER, device_instance_path TEXT, device_name TEXT, detail TEXT, PRIMARY KEY (mouse_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") + for i in self.mouse_list: + print(i) + cursor.execute("INSERT INTO mouse_list (mouse_number) VALUES (%s)", (i,)) + 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, keyboard_number INTEGER, mouse_number INTEGER, start_use_time TIMESTAMP NOT NULL, end_use_time TIMESTAMP, use_detail TEXT, bot_about TEXT, PRIMARY KEY (id), FOREIGN KEY (member_id) REFERENCES club_member(member_id), FOREIGN KEY (pc_number) REFERENCES pc_list(pc_number), FOREIGN KEY (keyboard_number) REFERENCES keyboard_list(keyboard_number), FOREIGN KEY (mouse_number) REFERENCES mouse_list(mouse_number))") + self.db.commit() + + cursor.close() + self.init_result = "ok" except (Exception) as error: print("初回処理でエラーが発生しました。\nエラー内容\n" + str(error)) @@ -118,6 +179,469 @@ class DL(): w.write(detail) except: print("LOGGING ERROR mode w") + + def password_generate(self, length): + numbers = string.digits # (1) + password = ''.join(random.choice(numbers) for _ in range(length)) # (2) + return password + + def hash_genarate(self, source): + hashed = hashlib.sha256(source.encode()) + return hashed.hexdigest() + + def user_register_check(self, **kwargs): + try: + discord_user_id = str(kwargs["discord_user_id"]) + + cursor = self.db.cursor() + + cursor.execute("SELECT * FROM club_member WHERE discord_user_id = %s", (discord_user_id,)) + user_record = cursor.fetchall() + #ユーザーデータが見つかった場合(登録済みの場合) + if user_record: + member_id = user_record[0][0] + name = user_record[0][1] + discord_user_name = user_record[0][2] + return {"result": 0, "about": "exist", "user_info": {"member_id": member_id, "name": name, "discord_user_name": discord_user_name}} + #ユーザーデータがなかったら(未登録の場合) + else: + return {"result": 1, "about": "user_data_not_found"} + + except Exception as error: + self.log(title=f"[ERROR] ユーザーの登録状態を調査中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error"} + + finally: + cursor.close() + + def pc_used_check(self, **kwargs): + try: + if "pc_number" in kwargs: + pc_number = int(kwargs["pc_number"]) + else: + pc_number = None + if "discord_user_id" in kwargs: + discord_user_id = str(kwargs["discord_user_id"]) + else: + discord_user_id = None + if "member_id" in kwargs: + member_id = int(kwargs["member_id"]) + else: + member_id = None + + cursor = self.db.cursor() + + if pc_number != None: + # pc番号を指定してpc_listから探す + cursor.execute("SELECT * FROM pc_list WHERE pc_number= %s", (pc_number,)) + pc_list_record = cursor.fetchall() + if pc_list_record[0][1] == None: + return {"result": 0, "about": "vacent"} + else: + return {"result": 1, "about": "used_by_other"} + elif discord_user_id != None: + #ユーザーIDを指定してPC使用履歴から探す + cursor.execute("SELECT * FROM club_member WHERE discord_user_id = %s", (discord_user_id,)) + user_record = cursor.fetchall() + #ユーザーデータが見つかった場合(登録済みの場合) + if user_record: + member_id = user_record[0][0] + cursor.execute("SELECT * FROM pc_usage_history WHERE member_id = %s ORDER BY id DESC LIMIT 1", (member_id,)) + pc_usage_history_record = cursor.fetchall() + if pc_usage_history_record: + if pc_usage_history_record[0][6] == None: + keyboard_number = pc_usage_history_record[0][3] + mouse_number = pc_usage_history_record[0][4] + + return {"result": 1, "about": "used_by_you", "pc_usage_history": {"pc_number": str(pc_usage_history_record[0][2]), "keyboard_number": keyboard_number, "mouse_number": mouse_number, "start_time": str(pc_usage_history_record[0][5]), "use_detail": str(pc_usage_history_record[0][7])}} + else: + return {"result": 0, "about": "vacent"} + else: + return {"result": 0, "about": "vacent"} + else: + return {"result": 1, "about": "user_data_not_found"} + elif member_id != None: + cursor.execute("SELECT * FROM pc_usage_history WHERE member_id = %s ORDER BY id DESC LIMIT 1", (member_id,)) + pc_usage_history_record = cursor.fetchall() + if pc_usage_history_record: + if pc_usage_history_record[0][6] == None: + keyboard_number = pc_usage_history_record[0][3] + mouse_number = pc_usage_history_record[0][4] + return {"result": 1, "about": "used_by_you", "pc_usage_history": {"pc_number": str(pc_usage_history_record[0][2]), "keyboard_number": keyboard_number, "mouse_number": mouse_number, "start_time": str(pc_usage_history_record[0][5]), "use_detail": str(pc_usage_history_record[0][7])}} + else: + return {"result": 0, "about": "vacent"} + else: + return {"result": 0, "about": "vacent"} + else: + return {"result": 1, "about": "search_options_error"} + + except Exception as error: + self.log(title=f"[ERROR] PCの使用状況を調査中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error"} + + finally: + if cursor: + cursor.close() + + def keyboard_used_check(self, **kwargs): + try: + cursor = self.db.cursor() + if kwargs["keyboard_number"] == None: + return {"result": 0, "about": "ok"} + else: + keyboard_number = int(kwargs["keyboard_number"]) + + cursor.execute("SELECT * FROM keyboard_list WHERE keyboard_number=%s", (keyboard_number,)) + keyboard_list_record = cursor.fetchall() + if keyboard_list_record[0][1] == None: + return {"result": 0, "about": "ok"} + else: + return {"result": 1, "about": "keyboard_already_in_use_by_other"} + + except Exception as error: + self.log(title=f"[ERROR] キーボードの使用状況を調査中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error"} + + finally: + if cursor: + cursor.close() + + def mouse_used_check(self, **kwargs): + try: + cursor = self.db.cursor() + if kwargs["mouse_number"] == None: + return {"result": 0, "about": "ok"} + else: + mouse_number = int(kwargs["mouse_number"]) + + cursor.execute("SELECT * FROM mouse_list WHERE mouse_number=%s", (mouse_number,)) + mouse_list_record = cursor.fetchall() + if mouse_list_record[0][1] == None: + return {"result": 0, "about": "ok"} + else: + return {"result": 1, "about": "mouse_already_in_use_by_other"} + + except Exception as error: + self.log(title=f"[ERROR] マウスの使用状況を調査中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error"} + + finally: + if cursor: + cursor.close() + + + def register(self, **kwargs): + try: + cursor = self.db.cursor() + user_info = { + "id": str(kwargs["discord_user_id"]), + "name": str(kwargs["name"]), + "display_name": str(kwargs["display_name"]), + "pc_number": int(kwargs["pc_number"]), + "keyboard_number": None, + "mouse_number": None, + "detail": None + } + if "detail" in kwargs: + user_info["detail"] = str(kwargs["detail"]) + else: + pass + + if kwargs["keyboard_number"] == "own": + pass + else: + user_info["keyboard_number"] = int(kwargs["keyboard_number"]) + + if kwargs["mouse_number"] == "own": + pass + else: + user_info["mouse_number"] = int(kwargs["mouse_number"]) + # ユーザー登録されているかの確認 + user_register = self.user_register_check(discord_user_id=user_info["id"]) + if user_register["result"] == 0: + member_id = user_register["user_info"]["member_id"] + name = user_register["user_info"]["name"] + # ユーザーがPCを使っているか + pc_check_self = self.pc_used_check(member_id=member_id) + if pc_check_self["result"] == 0: + # 他の人がそのPCを使っているか + pc_check = self.pc_used_check(pc_number=user_info["pc_number"]) + if pc_check["result"] == 0: + # キーボードは使われているか + keyboard_check = self.keyboard_used_check(keyboard_number=user_info["keyboard_number"]) + if keyboard_check["result"] == 0: + # マウスは使われているか + mouse_check = self.mouse_used_check(mouse_number=user_info["mouse_number"]) + if mouse_check["result"] == 0: + # パスワードとハッシュ作成 + password = self.password_generate(4) + password_hash = self.hash_genarate(password) + # PC使用履歴のテーブルにレコードを挿入 + + cursor.execute("INSERT INTO pc_usage_history (member_id, pc_number, keyboard_number, mouse_number, start_use_time, use_detail) VALUES (%s, %s, %s, %s, clock_timestamp(), %s)", (member_id, user_info["pc_number"], user_info["keyboard_number"], user_info["mouse_number"], user_info["detail"])) + # PCリストの該当のレコードを更新 + cursor.execute("UPDATE pc_list SET using_member_id = %s, password_hash = %s WHERE pc_number = %s", (member_id, password_hash, user_info["pc_number"])) + # キーボードリストの該当のレコードを自前(None)だったらスキップ、借りていたら更新 + if user_info["keyboard_number"] == None: + pass + else: + cursor.execute("UPDATE keyboard_list SET using_member_id = %s WHERE keyboard_number = %s", (member_id, user_info["keyboard_number"])) + # マウスも同様に + if user_info["mouse_number"] == None: + pass + else: + cursor.execute("UPDATE mouse_list SET using_member_id = %s WHERE mouse_number = %s", (member_id, user_info["mouse_number"])) + self.db.commit() + return {"result": 0, "about": "ok", "output_dict": {"password": str(password), "name": str(name)}} + else: + return {"result": 1, "about": "mouse_already_in_use"} + else: + return {"result": 1, "about": "keyboard_already_in_use"} + else: + return {"result": 1, "about": "pc_already_in_use_by_other"} + else: + return {"result": 1, "about": "pc_already_in_use_by_you", "pc_usage_history": {"pc_number": pc_check_self["pc_usage_history"]["pc_number"], "keyboard_number": pc_check_self["pc_usage_history"]["keyboard_number"], "mouse_number": pc_check_self["pc_usage_history"]["mouse_number"], "start_time": pc_check_self["pc_usage_history"]["start_time"], "use_detail": pc_check_self["pc_usage_history"]["use_detail"]}} + else: + return {"result": 1, "about": "user_data_not_found"} + except Exception as error: + self.log(title=f"[ERROR] PCの使用登録中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error"} + finally: + if cursor: + cursor.close() + + + def stop(self, **kwargs): + try: + cursor = self.db.cursor() + discord_user_id = str(kwargs["discord_user_id"]) + if "bot_about" in kwargs: + bot_about = kwargs["bot_about"] + else: + bot_about = None + + # ユーザーが登録してるかというよりはデータの取得のため + user_register = self.user_register_check(discord_user_id=discord_user_id) + member_id = user_register["user_info"]["member_id"] + name = user_register["user_info"]["name"] + + if user_register["result"] == 0: + cursor.execute("SELECT * FROM pc_usage_history WHERE member_id= %s ORDER BY id DESC LIMIT 1", (member_id,)) + pc_usage_history_record = cursor.fetchall() + + if pc_usage_history_record: + pc_usage_history_id = pc_usage_history_record[0][0] + pc_number = pc_usage_history_record[0][2] + keyboard_number = pc_usage_history_record[0][3] + mouse_number = pc_usage_history_record[0][4] + end_use_time = pc_usage_history_record[0][6] + + # 使用中のとき (使用停止時間がNoneのとき) + if end_use_time == None: + # 利用停止の理由の有無を判断 + if bot_about == None: + cursor.execute("UPDATE pc_usage_history SET end_use_time = clock_timestamp() WHERE id = %s", (pc_usage_history_id,)) + else: + cursor.execute("UPDATE pc_usage_history SET end_use_time = clock_timestamp(), bot_about = %s WHERE id = %s", (bot_about, pc_usage_history_id)) + # pc_listの使用中ユーザーを消す + cursor.execute("UPDATE pc_list SET using_member_id = NULL, password_hash = NULL WHERE pc_number = %s", (pc_number,)) + if keyboard_number == None: + pass + else: + # keyboard_listの使用中ユーザーを消す + cursor.execute("UPDATE keyboard_list SET using_member_id = NULL WHERE keyboard_number = %s", (keyboard_number,)) + if mouse_number == None: + pass + else: + # mouse_listの使用中ユーザーを消す + cursor.execute("UPDATE mouse_list SET using_member_id = NULL WHERE mouse_number = %s", (mouse_number,)) + self.db.commit() + return {"result": 0, "about": "ok", "output_dict": {"pc_number": str(pc_number), "name": str(name)}} + else: + return {"result": 1, "about": "unused"} + else: + return {"result": 1, "about": "unused"} + else: + return {"result": 1, "about": "user_data_not_found"} + + except Exception as error: + self.log(title=f"[ERROR] PCの使用停止処理中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error"} + + finally: + if cursor: + cursor.close() + + 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 = self.db.cursor() + cursor.execute("SELECT * FROM club_member WHERE discord_user_id = %s", (discord_user_id,)) + user_record = cursor.fetchall() + if not user_record: + cursor.execute("INSERT INTO club_member (name, discord_user_name, discord_user_id) VALUES (%s, %s, %s)", (name, discord_user_name, discord_user_id)) + self.db.commit() + return {"result": 0, "about": "ok"} + else: + return {"result": 1, "about": "already_exists"} + + except Exception as error: + self.log(title=f"[ERROR] ユーザー情報の登録中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error"} + + finally: + if cursor: + cursor.close() + + + 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 = self.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,)) + self.db.commit() + return {"result": 0, "about": "ok"} + else: + return {"result": 1, "about": "already_exists"} + + except Exception as error: + self.log(title=f"[ERROR] PCの情報を登録中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error"} + + finally: + if cursor: + cursor.close() + + def report_export(self, **kwargs): + try: + cursor = self.db.cursor() + csv_file_path = self.export_dir_path + "pc_usage_history.csv" + main_table = "pc_usage_history" + related_table = "club_member" + excel_file_path = self.export_dir_path + "pc_usage_history.xlsx" + + # メインテーブルの列情報を取得(user_idを除く) + cursor.execute(psycopg2.sql.SQL("SELECT * FROM {} LIMIT 0").format(psycopg2.sql.Identifier(main_table))) + main_columns = [desc[0] for desc in cursor.description if desc[0] != 'member_id'] + + # クエリを作成(列名を明確に指定) + query = psycopg2.sql.SQL(""" + SELECT {main_columns}, {related_table}.name + FROM {main_table} + LEFT JOIN {related_table} ON {main_table}.member_id = {related_table}.member_id + ORDER BY id + """).format( + main_columns=psycopg2.sql.SQL(', ').join([psycopg2.sql.SQL("{}.{}").format(psycopg2.sql.Identifier(main_table), psycopg2.sql.Identifier(col)) for col in main_columns]), + main_table=psycopg2.sql.Identifier(main_table), + related_table=psycopg2.sql.Identifier(related_table) + ) + + cursor.execute(query) + + # 列名を再構成(nameを2番目に配置) + column_names = [main_columns[0], 'name'] + main_columns[1:] + + rows = cursor.fetchall() + + # Excelワークブックを作成 + wb = openpyxl.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) + self.log(title=f"[SUCCESS] PCの使用履歴をエクスポートしました。", message=f"ファイルパス | {excel_file_path}", flag=0) + return {"result": 0, "about": "ok", "file_path": excel_file_path} + + + except Exception as error: + self.log(title=f"[ERROR] PCの使用履歴をエクスポート中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error"} + + finally: + if cursor: + cursor.close() + + + def force_stop(self, **kwargs): + try: + pc_number = kwargs["pc_number"] + cursor = self.db.cursor() + if "bot_about" in kwargs: + bot_about = kwargs["bot_about"] + cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s", (pc_number,)) + pc_list_record = cursor.fetchall() + pc_using_member_id = pc_list_record[0][1] + pc_password_hash = pc_list_record[0][2] + if pc_using_member_id == None: + return {"result": 1, "about": "not_used"} + else: + cursor.execute("UPDATE pc_list SET using_member_id = NULL WHERE pc_number = %s", (pc_number,)) + if pc_password_hash == None: + pass + else: + 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_using_member_id, pc_number)) + pc_usage_history_record = cursor.fetchall() + pc_usage_history_record_id = pc_usage_history_record[0][0] + keyboard_number = pc_usage_history_record[0][3] + mouse_number = pc_usage_history_record[0][4] + if keyboard_number == None: + pass + else: + # keyboard_listの使用中ユーザーを消す + cursor.execute("UPDATE keyboard_list SET using_member_id = NULL WHERE keyboard_number = %s", (keyboard_number,)) + if mouse_number == None: + pass + else: + # mouse_listの使用中ユーザーを消す + cursor.execute("UPDATE mouse_list SET using_member_id = NULL WHERE mouse_number = %s", (mouse_number,)) + cursor.execute("UPDATE pc_usage_history SET end_use_time = clock_timestamp(), bot_about = %s WHERE id = %s", (bot_about, pc_usage_history_record_id)) + self.db.commit() + return {"result": 0, "about": "ok"} + else: + return {"result": 1, "about": "bot_about_not_found"} + + except Exception as error: + self.log(title=f"[ERROR] fstop中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error"} + + finally: + if cursor: + cursor.close() + +dislocker = DL() intents = discord.Intents.default() intents.message_content = True @@ -135,8 +659,40 @@ async def on_ready(): await tree.sync() @tree.command(name="use", description="パソコンの使用登録をします。") -async def use(interaction: discord.Interaction): - await interaction.response.send_message("パソコンの使用登録をします。", ephemeral=True) +async def use(interaction: discord.Interaction, pc_number: int, keyboard_number: int, mouse_number: int, detail: str): + register = dislocker.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=detail) + if register["result"] == 0: + await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## 使用目的 | {detail}", ephemeral=True) + dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, 使用目的 | {detail}", flag=0) + await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':white_check_mark: {register["output_dict"]["name"]} さんがPC {pc_number} の使用を開始しました。\n>>> ## PC番号 | {pc_number}\n## 使用目的 | {detail}') + elif register["result"] == 1: + if register["about"] == "pc_already_in_use_by_other": + await interaction.response.send_message(":x: 他の方がそのPCを使用中です。", ephemeral=True) + elif register["about"] == "pc_already_in_use_by_you": + await interaction.response.send_message(f":x: あなたは既にPC {register['pc_usage_history']['pc_number']} を使用中です。\n>>> ## PC番号 | {register['pc_usage_history']['pc_number']}\n## 使用目的 | {register['pc_usage_history']['use_detail']}", ephemeral=True) + elif register["about"] == "keyboard_already_in_use": + await interaction.response.send_message(":x: キーボードは既に使用中です。", ephemeral=True) + elif register["about"] == "mouse_already_in_use": + await interaction.response.send_message(":x: マウスは既に使用中です。", ephemeral=True) + elif register["about"] == "user_data_not_found": + await interaction.response.send_message(":x: ユーザーデータが見つかりませんでした。", ephemeral=True) + elif register["about"] == "error": + await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) + +@tree.command(name="stop", description="パソコンの使用を終了します。") +async def stop(interaction: discord.Interaction): + stop = dislocker.stop(discord_user_id=interaction.user.id) + if stop["result"] == 0: + await interaction.response.send_message(f":white_check_mark: 使用が終了されました。\n>>> ## PC番号 | {stop['output_dict']['pc_number']}", ephemeral=True) + dislocker.log(title=f"[INFO] PC番号{stop['output_dict']['pc_number']} の使用が終了されました。", message=f"名前 | {stop['output_dict']['name']}", flag=0) + await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':white_check_mark: {stop["output_dict"]["name"]} さんがPC {stop["output_dict"]["pc_number"]} の使用を終了しました。\n>>> ## PC番号 | {stop["output_dict"]["pc_number"]}') + elif stop["result"] == 1: + if stop["about"] == "unused": + await interaction.response.send_message(":x: あなたはPCを使用していません。", ephemeral=True) + elif stop["about"] == "user_data_not_found": + await interaction.response.send_message(":x: ユーザーデータが見つかりませんでした。", ephemeral=True) + elif stop["about"] == "error": + await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) + -dislocker = DL() client.run(dislocker.server_config["bot"]["token"]) \ No newline at end of file From 0fc7c0c3ebe1df41b19d93b196f2b61825f0128a Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Wed, 25 Sep 2024 22:56:08 +0900 Subject: [PATCH 084/175] =?UTF-8?q?datetime=E3=81=AEimport=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3,=20=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E3=81=AE=E6=96=87=E8=A8=80=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index 14e5fda..c5f2802 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -2,7 +2,7 @@ import discord import os import json import psycopg2 -import datetime +from datetime import datetime import asyncio import string import random @@ -658,7 +658,7 @@ async def on_ready(): print("------") await tree.sync() -@tree.command(name="use", description="パソコンの使用登録をします。") +@tree.command(name="use", description="パソコンの使用登録をします。通常はこのコマンドを使用する必要はありません。\n必要引数 : pc_number(PC番号), keyboard_number(キーボード番号), mouse_number(マウス番号), detail(使用目的)") async def use(interaction: discord.Interaction, pc_number: int, keyboard_number: int, mouse_number: int, detail: str): register = dislocker.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=detail) if register["result"] == 0: @@ -679,7 +679,7 @@ async def use(interaction: discord.Interaction, pc_number: int, keyboard_number: elif register["about"] == "error": await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) -@tree.command(name="stop", description="パソコンの使用を終了します。") +@tree.command(name="stop", description="パソコンの使用を終了します。通常はこのコマンドを使用する必要はありません。") async def stop(interaction: discord.Interaction): stop = dislocker.stop(discord_user_id=interaction.user.id) if stop["result"] == 0: @@ -688,11 +688,11 @@ async def stop(interaction: discord.Interaction): await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':white_check_mark: {stop["output_dict"]["name"]} さんがPC {stop["output_dict"]["pc_number"]} の使用を終了しました。\n>>> ## PC番号 | {stop["output_dict"]["pc_number"]}') elif stop["result"] == 1: if stop["about"] == "unused": - await interaction.response.send_message(":x: あなたはPCを使用していません。", ephemeral=True) + await interaction.response.send_message("# :shaking_face: あなたはPCを使用されていないようです...", ephemeral=True) elif stop["about"] == "user_data_not_found": - await interaction.response.send_message(":x: ユーザーデータが見つかりませんでした。", ephemeral=True) + await interaction.response.send_message("# :dizzy_face: ユーザーとして登録されていないようです。\n最初にサーバーで登録を行ってください。", ephemeral=True) elif stop["about"] == "error": - await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) + await interaction.response.send_message("# :skull_crossbones: 停止できませんでした。\n内部エラーが発生しています。", ephemeral=True) client.run(dislocker.server_config["bot"]["token"]) \ No newline at end of file From 5578cffad6d0d57505cf9787507c6ca31c04e30c Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Wed, 25 Sep 2024 22:58:54 +0900 Subject: [PATCH 085/175] =?UTF-8?q?=E8=AA=AC=E6=98=8E=E6=96=87=E8=A8=80?= =?UTF-8?q?=E3=82=92100=E6=96=87=E5=AD=97=E4=BB=A5=E4=B8=8B=E3=81=AB?= =?UTF-8?q?=E6=8A=91=E3=81=88=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index c5f2802..1996671 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -658,7 +658,7 @@ async def on_ready(): print("------") await tree.sync() -@tree.command(name="use", description="パソコンの使用登録をします。通常はこのコマンドを使用する必要はありません。\n必要引数 : pc_number(PC番号), keyboard_number(キーボード番号), mouse_number(マウス番号), detail(使用目的)") +@tree.command(name="use", description="パソコンの使用登録をします。通常はこのコマンドを使用する必要はありません。") async def use(interaction: discord.Interaction, pc_number: int, keyboard_number: int, mouse_number: int, detail: str): register = dislocker.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=detail) if register["result"] == 0: From 4f78a54b2f9db7ca2626e8d479f44acf2d6c15a3 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 08:42:54 +0900 Subject: [PATCH 086/175] =?UTF-8?q?=E3=82=B9=E3=83=A9=E3=83=83=E3=82=B7?= =?UTF-8?q?=E3=83=A5=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 117 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/dislocker_slash.py b/dislocker_slash.py index 1996671..a668adf 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -640,6 +640,64 @@ class DL(): finally: if cursor: cursor.close() + + def pc_onetime_gen(self): + try: + if os.path.isfile(dislocker.onetime_config_path): + with open(dislocker.onetime_config_path, "r") as r: + onetime_config = json.load(r) + onetime = onetime_config["onetime"]["pc_register"] + if onetime == None: + onetime = str(self.password_generate(8)) + onetime_config["onetime"]["pc_register"] = onetime + with open(dislocker.onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) + return {"result": 0, "about": "ok", "onetime": onetime} + else: + return {"result": 0, "already_exists": "ok", "onetime": onetime} + else: + onetime = str(self.password_generate(8)) + onetime_config = { + "onetime": { + "pc_register": onetime, + "device_register": None + } + } + with open(dislocker.onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) + return {"result": 0, "about": "ok", "onetime": onetime} + except Exception as error: + self.log(title=f"[ERROR] PC登録用のワンタイムパスワードを発行中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error"} + + def device_onetime_gen(self): + try: + if os.path.isfile(dislocker.onetime_config_path): + with open(dislocker.onetime_config_path, "r") as r: + onetime_config = json.load(r) + onetime = onetime_config["onetime"]["device_register"] + if onetime == None: + onetime = str(self.password_generate(8)) + onetime_config["onetime"]["device_register"] = onetime + with open(dislocker.onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) + return {"result": 0, "about": "ok", "onetime": onetime} + else: + return {"result": 0, "already_exists": "ok", "onetime": onetime} + else: + onetime = str(self.password_generate(8)) + onetime_config = { + "onetime": { + "pc_register": None, + "device_register": onetime + } + } + with open(dislocker.onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) + return {"result": 0, "about": "ok", "onetime": onetime} + except Exception as error: + self.log(title=f"[ERROR] デバイス登録用のワンタイムパスワードを発行中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error"} dislocker = DL() @@ -658,6 +716,7 @@ async def on_ready(): print("------") await tree.sync() +#使用者側のスラッシュコマンド @tree.command(name="use", description="パソコンの使用登録をします。通常はこのコマンドを使用する必要はありません。") async def use(interaction: discord.Interaction, pc_number: int, keyboard_number: int, mouse_number: int, detail: str): register = dislocker.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=detail) @@ -694,5 +753,63 @@ async def stop(interaction: discord.Interaction): elif stop["about"] == "error": await interaction.response.send_message("# :skull_crossbones: 停止できませんでした。\n内部エラーが発生しています。", ephemeral=True) +#管理者側のスラッシュコマンド +@tree.command(name="userreg", description="ユーザーを登録します。") +async def userreg(interaction: discord.Interaction, discord_user_id: str, discord_user_name: str, name: str): + user_register = dislocker.user_register(discord_user_id=discord_user_id, discord_user_name=discord_user_name, name=name) + if user_register["result"] == 0: + await interaction.response.send_message(":white_check_mark: ユーザーを登録しました。", ephemeral=True) + dislocker.log(title=f"[INFO] ユーザーを登録しました。", message=f"名前 | {name}, Discordユーザー名 | {discord_user_name}, DiscordユーザーID | {discord_user_id}", flag=0) + elif user_register["result"] == 1: + if user_register["about"] == "already_exists": + await interaction.response.send_message(":x: 既に登録されているユーザーです。", ephemeral=True) + elif user_register["about"] == "error": + await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) + +@tree.command(name="pcreg", description="PCを登録するためのワンタイムパスワードを発行します。") +async def pcreg(interaction: discord.Interaction): + onetime = dislocker.pc_onetime_gen() + if onetime["result"] == 0: + await interaction.response.send_message(f":white_check_mark: PC登録用のワンタイムパスワードを発行しました。\n>>> # ワンタイムパスワード | {onetime['onetime']}\n## 有効期限 | 1回の登録でのみ使用可能です。", ephemeral=True) + dislocker.log(title=f"[INFO] PC登録用のワンタイムパスワードを発行しました。", message=f"ワンタイムパスワード | {onetime['onetime']}", flag=0) + elif onetime["result"] == 1: + if onetime["about"] == "error": + await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) + +@tree.command(name="devicereg", description="デバイスを登録するためのワンタイムパスワードを発行します。") +async def devicereg(interaction: discord.Interaction): + onetime = dislocker.device_onetime_gen() + if onetime["result"] == 0: + await interaction.response.send_message(f":white_check_mark: デバイス登録用のワンタイムパスワードを発行しました。\n>>> # ワンタイムパスワード | {onetime['onetime']}\n## 有効期限 | 1回の登録でのみ使用可能です。", ephemeral=True) + dislocker.log(title=f"[INFO] デバイス登録用のワンタイムパスワードを発行しました。", message=f"ワンタイムパスワード | {onetime['onetime']}", flag=0) + elif onetime["result"] == 1: + if onetime["about"] == "error": + await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) + +@tree.command(name="fstop", description="PCの使用を強制終了します。") +async def fstop(interaction: discord.Interaction, pc_number: int, bot_about: str): + force_stop = dislocker.force_stop(pc_number=pc_number, bot_about=bot_about) + if force_stop["result"] == 0: + await interaction.response.send_message(f":white_check_mark: PC {pc_number} の使用を強制終了しました。", ephemeral=True) + dislocker.log(title=f"[INFO] PC {pc_number} の使用を強制終了しました。", message=f"理由 | {bot_about}", flag=0) + elif force_stop["result"] == 1: + if force_stop["about"] == "not_used": + await interaction.response.send_message(":x: そのPCは使用されていません。", ephemeral=True) + elif force_stop["about"] == "bot_about_not_found": + await interaction.response.send_message(":x: 理由が指定されていません。", ephemeral=True) + elif force_stop["about"] == "error": + await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) + +@tree.command(name="report", description="PCの使用履歴をエクスポートします。") +async def report(interaction: discord.Interaction): + report_export = dislocker.report_export() + if report_export["result"] == 0: + await interaction.response.send_message(f":white_check_mark: 使用履歴のレポートです。", file=discord.File(report_export["file_path"]), ephemeral=True) + dislocker.log(title=f"[INFO] PCの使用履歴をエクスポートしました。", message=f"ファイルパス | {report_export['file_path']}", flag=0) + elif report_export["result"] == 1: + if report_export["about"] == "error": + await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) + + client.run(dislocker.server_config["bot"]["token"]) \ No newline at end of file From f7f3e5d40b2f399b705f972949619012d5588ed5 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 09:25:31 +0900 Subject: [PATCH 087/175] =?UTF-8?q?sql=E3=81=AE=E3=82=A4=E3=83=B3=E3=83=9D?= =?UTF-8?q?=E3=83=BC=E3=83=88=E3=82=92=E4=BF=AE=E6=AD=A3=E3=80=81=E6=97=A5?= =?UTF-8?q?=E6=9C=AC=E8=AA=9E=E3=83=86=E3=82=B9=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index a668adf..d27b18a 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -2,6 +2,7 @@ import discord import os import json import psycopg2 +from psycopg2 import sql from datetime import datetime import asyncio import string @@ -531,19 +532,19 @@ class DL(): excel_file_path = self.export_dir_path + "pc_usage_history.xlsx" # メインテーブルの列情報を取得(user_idを除く) - cursor.execute(psycopg2.sql.SQL("SELECT * FROM {} LIMIT 0").format(psycopg2.sql.Identifier(main_table))) + 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 = psycopg2.sql.SQL(""" + query = sql.SQL(""" SELECT {main_columns}, {related_table}.name FROM {main_table} LEFT JOIN {related_table} ON {main_table}.member_id = {related_table}.member_id ORDER BY id """).format( - main_columns=psycopg2.sql.SQL(', ').join([psycopg2.sql.SQL("{}.{}").format(psycopg2.sql.Identifier(main_table), psycopg2.sql.Identifier(col)) for col in main_columns]), - main_table=psycopg2.sql.Identifier(main_table), - related_table=psycopg2.sql.Identifier(related_table) + 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) @@ -787,7 +788,9 @@ async def devicereg(interaction: discord.Interaction): await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) @tree.command(name="fstop", description="PCの使用を強制終了します。") -async def fstop(interaction: discord.Interaction, pc_number: int, bot_about: str): +async def fstop(interaction: discord.Interaction, PC番号: int, 理由: str): + pc_number = PC番号 + bot_about = 理由 force_stop = dislocker.force_stop(pc_number=pc_number, bot_about=bot_about) if force_stop["result"] == 0: await interaction.response.send_message(f":white_check_mark: PC {pc_number} の使用を強制終了しました。", ephemeral=True) From 3575818af885df169d79d5a3ab6c62e5c8a77c60 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 09:28:20 +0900 Subject: [PATCH 088/175] =?UTF-8?q?Workbook=E3=81=AE=E3=82=A4=E3=83=B3?= =?UTF-8?q?=E3=83=9D=E3=83=BC=E3=83=88=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index d27b18a..50fd245 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -9,6 +9,7 @@ import string import random import hashlib import openpyxl +from openpyxl import Workbook class DL(): def __init__(self): @@ -555,7 +556,7 @@ class DL(): rows = cursor.fetchall() # Excelワークブックを作成 - wb = openpyxl.Workbook()() + wb = Workbook() ws = wb.active # 列名を書き込み From db16ddce29455382c73ca72c70ffe81fee49ecfc Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 09:29:19 +0900 Subject: [PATCH 089/175] =?UTF-8?q?=E6=97=A5=E6=9C=AC=E8=AA=9E=E4=BD=BF?= =?UTF-8?q?=E3=81=88=E3=81=AA=E3=81=8B=E3=81=A3=E3=81=9F=E3=81=AE=E3=81=A7?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index 50fd245..6484861 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -789,13 +789,11 @@ async def devicereg(interaction: discord.Interaction): await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) @tree.command(name="fstop", description="PCの使用を強制終了します。") -async def fstop(interaction: discord.Interaction, PC番号: int, 理由: str): - pc_number = PC番号 - bot_about = 理由 - force_stop = dislocker.force_stop(pc_number=pc_number, bot_about=bot_about) +async def fstop(interaction: discord.Interaction, pc_number: int, about: str): + force_stop = dislocker.force_stop(pc_number=pc_number, bot_about=about) if force_stop["result"] == 0: await interaction.response.send_message(f":white_check_mark: PC {pc_number} の使用を強制終了しました。", ephemeral=True) - dislocker.log(title=f"[INFO] PC {pc_number} の使用を強制終了しました。", message=f"理由 | {bot_about}", flag=0) + dislocker.log(title=f"[INFO] PC {pc_number} の使用を強制終了しました。", message=f"理由 | {about}", flag=0) elif force_stop["result"] == 1: if force_stop["about"] == "not_used": await interaction.response.send_message(":x: そのPCは使用されていません。", ephemeral=True) From f1967f0388dc13ad82308fb58ecd43ccb9f0dfa5 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 16:36:18 +0900 Subject: [PATCH 090/175] =?UTF-8?q?=E3=83=9C=E3=82=BF=E3=83=B3=E5=91=A8?= =?UTF-8?q?=E3=82=8A=E3=81=AE=E6=A9=9F=E8=83=BD=E3=82=92=E7=A7=BB=E6=A4=8D?= =?UTF-8?q?=E3=81=97=E3=81=A6=E3=81=8F=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 231 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) diff --git a/dislocker_slash.py b/dislocker_slash.py index 6484861..913b543 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -700,6 +700,67 @@ class DL(): except Exception as error: self.log(title=f"[ERROR] デバイス登録用のワンタイムパスワードを発行中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) return {"result": 1, "about": "error"} + + +class ReasonModal(discord.ui.Modal): + def __init__(self, title: str, pc_number: str, keyboard_number: str, mouse_number: str, timeout=15) -> None: + super().__init__(title=title, timeout=timeout) + self.reason_input_form = discord.ui.TextInput(label="使用目的を入力してください", style=discord.ui.TextInput.short, custom_id=f"register_{pc_number}_{keyboard_number}_{mouse_number}") + self.add_item(self.reason_input_form) + + async def on_submit(self, interaction: discord.Interaction) -> None: + custom_id = interaction.data["components"][0]["components"][0]["custom_id"] + custom_id_split = custom_id.split("_") + pc_number = custom_id_split[1] + keyboard_number = custom_id_split[2] + mouse_number = custom_id_split[3] + + if keyboard_number == "own": + keyboard_number_show = "自前" + else: + keyboard_number_show = keyboard_number + + if mouse_number == "own": + mouse_number_show = "自前" + else: + mouse_number_show = mouse_number + + register = dislocker.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=self.reason_input_form.value) + + if register["about"] == "ok": + await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\n## 使用目的 | {self.reason_input_form.value}", ephemeral=True) + await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':white_check_mark: {register["output_dict"]["name"]} さんがPC {pc_number} の使用を開始しました。\n>>> ## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\n## 使用目的 | {self.reason_input_form.value}') + dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, 使用目的 | {self.reason_input_form.value}", flag=0) + elif register["about"] == "pc_already_in_use_by_you": + pc_usage_history = register["pc_usage_history"] + if pc_usage_history["keyboard_number"] == None: + keyboard_number_show = "未認証" + elif pc_usage_history["keyboard_number"] == 0: + keyboard_number_show = "自前" + else: + keyboard_number_show = str(pc_usage_history["keyboard_number"]) + + if pc_usage_history["mouse_number"] == None: + mouse_number_show = "未認証" + elif pc_usage_history["mouse_number"] == 0: + mouse_number_show = "自前" + else: + mouse_number_show = str(pc_usage_history["mouse_number"]) + + await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {keyboard_number_show}\n# マウス番号 | {mouse_number_show}\n# 使用開始時刻 | {pc_usage_history["start_time"]}", ephemeral=True) + #await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {keyboard_number_show}\n# マウス番号 | {mouse_number_show}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) + + elif register["about"] == "pc_already_in_use_by_other": + await interaction.response.send_message(f"# :man_gesturing_no: そのPCは他のメンバーによって使用されています。\n別のPC番号を指定して、再度お試しください。", ephemeral=True) + elif register["about"] == "keyboard_already_in_use": + await interaction.response.send_message(f"# :man_gesturing_no: そのキーボードは他のメンバーによって使用されています。\n別のキーボードのデバイス番号を指定して、再度お試しください。", ephemeral=True) + elif register["about"] == "mouse_already_in_use": + await interaction.response.send_message(f"# :man_gesturing_no: そのマウスは他のメンバーによって使用されています。\n別のマウスのデバイス番号を指定して、再度お試しください。", ephemeral=True) + elif register["about"] == "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() @@ -718,6 +779,154 @@ async def on_ready(): print("------") await tree.sync() +@client.event +async def on_button(interaction: discord.Interaction): + custom_id = interaction.data["custom_id"] + custom_id_split = custom_id.split("_") + dislocker.log(title=f"[INFO] ボタンが押されました。", message=f"custom_id | {custom_id}, DiscordユーザーID | {interaction.user.id}", flag=0) + + if custom_id_split[0] == "pcregister": + keyboard_register_view = discord.ui.View(timeout=15) + pc_number = custom_id_split[1] + for i in dislocker.keyboard_list: + keyboard_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"keyboardregister_{str(pc_number)}_{str(i)}") + keyboard_register_view.add_item(keyboard_register_button) + keyboard_not_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="キーボードは自前", custom_id=f"keyboardregister_{str(pc_number)}_own") + keyboard_register_view.add_item(keyboard_not_register_button) + + await interaction.response.send_message(f"# :keyboard: キーボードのデバイス番号を選んでください!\n>>> # PC番号 | {str(pc_number)}", view=keyboard_register_view, ephemeral=True) + + elif custom_id_split[0] == "keyboardregister": + mouse_register_view = discord.ui.View(timeout=15) + pc_number = custom_id_split[1] + keyboard_number = custom_id_split[2] + if keyboard_number == "own": + keyboard_number_show = "自前" + else: + keyboard_number_show = keyboard_number + for i in dislocker.mouse_list: + mouse_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_{str(i)}") + mouse_register_view.add_item(mouse_register_button) + mouse_not_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="マウスは自前", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_own") + mouse_register_view.add_item(mouse_not_register_button) + + await interaction.response.send_message(f"# :mouse_three_button: マウスのデバイス番号を選んでください!\n>>> # PC番号 | {str(pc_number)}\n# キーボード番号 | {str(keyboard_number_show)}", view=mouse_register_view, ephemeral=True) + + elif custom_id_split[0] == "mouseregister": + pc_number = custom_id_split[1] + keyboard_number = custom_id_split[2] + mouse_number = custom_id_split[3] + + if keyboard_number == "own": + keyboard_number_show = "自前" + else: + keyboard_number_show = keyboard_number + + if mouse_number == "own": + mouse_number_show = "自前" + else: + mouse_number_show = mouse_number + + reason_register_view = discord.ui.View(timeout=15) + for i in dislocker.preset_games: + reason_quick_button = reason_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"quickreasonregister_{str(pc_number)}_{str(keyboard_number)}_{str(mouse_number)}_{str(i)}") + reason_register_view.add_item(reason_quick_button) + reason_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="使用目的を入力する", custom_id=f"reasonregister_{str(pc_number)}_{str(keyboard_number)}_{str(mouse_number)}") + reason_register_view.add_item(reason_button) + + await interaction.response.send_message(f"# :regional_indicator_q: 使用目的を書いてください!\n>>> # PC番号 | {str(pc_number)}\n# キーボード番号 | {str(keyboard_number_show)}\n# マウス番号 | {str(mouse_number_show)}", view=reason_register_view, ephemeral=True) + + elif custom_id_split[0] == "quickreasonregister": + pc_number = custom_id_split[1] + keyboard_number = custom_id_split[2] + mouse_number = custom_id_split[3] + + if keyboard_number == "own": + keyboard_number_show = "自前" + else: + keyboard_number_show = keyboard_number + + if mouse_number == "own": + mouse_number_show = "自前" + else: + mouse_number_show = mouse_number + + reason = custom_id_split[4] + + register = dislocker.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=reason) + if register["about"] == "ok": + await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## キーボード番号 | {str(keyboard_number_show)}\n## マウス番号 | {str(mouse_number_show)}\n## 使用目的 | {reason}", ephemeral=True) + await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':white_check_mark: {register["output_dict"]["name"]} さんがPC {pc_number} の使用を開始しました。\n>>> ## PC番号 | {pc_number}\n## 使用目的 | {reason}') + dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, 使用目的 | {reason}", flag=0) + elif register["about"] == "pc_already_in_use_by_you": + pc_usage_history = register["pc_usage_history"] + if pc_usage_history["keyboard_number"] == None: + keyboard_number_show = "未認証" + elif pc_usage_history["keyboard_number"] == 0: + keyboard_number_show = "自前" + else: + keyboard_number_show = str(pc_usage_history["keyboard_number"]) + + if pc_usage_history["mouse_number"] == None: + mouse_number_show = "未認証" + elif pc_usage_history["mouse_number"] == 0: + mouse_number_show = "自前" + else: + mouse_number_show = str(pc_usage_history["mouse_number"]) + await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {keyboard_number_show}\n# マウス番号 | {mouse_number_show}\n# 使用開始時刻 | {pc_usage_history["start_time"]}", ephemeral=True) + #await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {keyboard_number_show}\n# マウス番号 | {mouse_number_show}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) + elif register["about"] == "pc_already_in_use_by_other": + await interaction.response.send_message(f"# :man_gesturing_no: そのPCは他のメンバーによって使用されています。\n別のPC番号を指定して、再度お試しください。", ephemeral=True) + elif register["about"] == "keyboard_already_in_use": + await interaction.response.send_message(f"# :man_gesturing_no: そのキーボードは他のメンバーによって使用されています。\n別のキーボードのデバイス番号を指定して、再度お試しください。", ephemeral=True) + elif register["about"] == "mouse_already_in_use": + await interaction.response.send_message(f"# :man_gesturing_no: そのマウスは他のメンバーによって使用されています。\n別のマウスのデバイス番号を指定して、再度お試しください。", ephemeral=True) + elif register["about"] == "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) + + elif custom_id_split[0] == "reasonregister": + pc_number = custom_id_split[1] + keyboard_number = custom_id_split[2] + mouse_number = custom_id_split[3] + + if keyboard_number == "own": + keyboard_number_show = "自前" + else: + keyboard_number_show = keyboard_number + + if mouse_number == "own": + mouse_number_show = "自前" + else: + mouse_number_show = mouse_number + + reason_input_form = ReasonModal(title="Dislocker | 登録", pc_number=str(pc_number), keyboard_number=str(keyboard_number), mouse_number=str(mouse_number)) + await interaction.response.send_modal(reason_input_form) + + elif custom_id_split[0] == "stop": + pc_stop = dislocker.stop(discord_user_id=interaction.user.id) + stop_view = discord.ui.View(timeout=15) + if pc_stop["about"] == "unused": + await interaction.response.send_message("# :shaking_face: 使用されていないようです...", ephemeral=True) + elif pc_stop["about"] == "user_data_not_found": + await interaction.response.send_message("# :dizzy_face: ユーザーとして登録されていないようです。\n最初にサーバーで登録を行ってください。", ephemeral=True) + elif pc_stop["about"] == "ok": + await interaction.response.send_message(f":white_check_mark: PC番号 {pc_stop["output_dict"]["pc_number"]} の使用が終了されました。", ephemeral=True) + await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':negative_squared_cross_mark: {pc_stop["output_dict"]["name"]} さんがPC {pc_stop["output_dict"]["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": + user_register = dislocker.user_register(name=interaction.user.display_name, discord_user_name=interaction.user.name, discord_user_id=interaction.user.id) + if user_register["about"] == "ok": + await interaction.response.send_message(f"# :white_check_mark: ユーザー情報が登録されました。\n>>> ユーザー名:{interaction.user.display_name}", ephemeral=True) + elif user_register["about"] == "already_exists": + await interaction.response.send_message("# :no_entry: 登録できませんでした。\nもう登録されている可能性があります。", ephemeral=True) + else: + await interaction.response.send_message("# :no_entry: 登録できませんでした。\n内部エラーが発生しています。", ephemeral=True) + + #使用者側のスラッシュコマンド @tree.command(name="use", description="パソコンの使用登録をします。通常はこのコマンドを使用する必要はありません。") async def use(interaction: discord.Interaction, pc_number: int, keyboard_number: int, mouse_number: int, detail: str): @@ -812,6 +1021,28 @@ async def report(interaction: discord.Interaction): if report_export["about"] == "error": await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) +@tree.command(name="init", description="操作チャンネルにボタン一式を送信します。") +async def button_init(interaction: discord.Interaction, text_channel: discord.TextChannel): + user_register_button_view = discord.ui.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 client.get_channel(text_channel.id).send(f'# :index_pointing_at_the_viewer: ユーザー登録はお済ですか?', view=user_register_button_view) + + stop_button_view = discord.ui.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 client.get_channel(text_channel.id).send(f'# :index_pointing_at_the_viewer: 使用を停止しますか?', view=stop_button_view) + + pc_button_view = discord.ui.View(timeout=None) + for i in dislocker.pc_list: + pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"pcregister_{str(i)}") + pc_button_view.add_item(pc_register_button) + + await client.get_channel(text_channel.id).send(f'# :index_pointing_at_the_viewer: 使いたいPCの番号を選んでください!', view=pc_button_view) + dislocker.log(title=f"[INFO] サーバーで初回処理を実行しました。", flag=0) + client.run(dislocker.server_config["bot"]["token"]) \ No newline at end of file From fc16d15ef9002926abbf2236527771367a752cb4 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 16:52:48 +0900 Subject: [PATCH 091/175] =?UTF-8?q?=E3=83=9C=E3=82=BF=E3=83=B3=E5=91=A8?= =?UTF-8?q?=E3=82=8A=E3=81=AE=E5=87=A6=E7=90=86=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index 913b543..e75931e 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -761,7 +761,6 @@ class ReasonModal(discord.ui.Modal): else: await interaction.response.send_message("# :skull_crossbones: 登録できませんでした。\n内部エラーが発生しています。", ephemeral=True) - dislocker = DL() intents = discord.Intents.default() @@ -780,6 +779,13 @@ async def on_ready(): await tree.sync() @client.event +async def on_interaction(interaction: discord.Interaction): + try: + if interaction.data["component_type"] == 2: + await on_button(interaction) + except KeyError: + pass + async def on_button(interaction: discord.Interaction): custom_id = interaction.data["custom_id"] custom_id_split = custom_id.split("_") @@ -997,7 +1003,7 @@ async def devicereg(interaction: discord.Interaction): if onetime["about"] == "error": await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) -@tree.command(name="fstop", description="PCの使用を強制終了します。") +@tree.command(name="fstop", description="PCの使用登録を強制的に終了します。") async def fstop(interaction: discord.Interaction, pc_number: int, about: str): force_stop = dislocker.force_stop(pc_number=pc_number, bot_about=about) if force_stop["result"] == 0: @@ -1043,6 +1049,8 @@ async def button_init(interaction: discord.Interaction, text_channel: discord.Te await client.get_channel(text_channel.id).send(f'# :index_pointing_at_the_viewer: 使いたいPCの番号を選んでください!', view=pc_button_view) dislocker.log(title=f"[INFO] サーバーで初回処理を実行しました。", flag=0) + await interaction.response.send_message(f":white_check_mark: ボタンを送信しました!", ephemeral=True) + client.run(dislocker.server_config["bot"]["token"]) \ No newline at end of file From 65d1f6e5686e8d8627b61d8e9e9b1a6250b80195 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 16:57:49 +0900 Subject: [PATCH 092/175] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=90=86=E7=94=B1?= =?UTF-8?q?=E3=81=AE=E3=83=A2=E3=83=BC=E3=83=80=E3=83=AB=E3=81=8C=E8=B5=B7?= =?UTF-8?q?=E5=8B=95=E3=81=97=E3=81=AA=E3=81=8B=E3=81=A3=E3=81=9F=E3=83=90?= =?UTF-8?q?=E3=82=B0=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index e75931e..89a6a32 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -705,7 +705,7 @@ class DL(): class ReasonModal(discord.ui.Modal): def __init__(self, title: str, pc_number: str, keyboard_number: str, mouse_number: str, timeout=15) -> None: super().__init__(title=title, timeout=timeout) - self.reason_input_form = discord.ui.TextInput(label="使用目的を入力してください", style=discord.ui.TextInput.short, custom_id=f"register_{pc_number}_{keyboard_number}_{mouse_number}") + self.reason_input_form = discord.ui.TextInput(label="使用目的を入力してください", style=discord.TextStyle.short, custom_id=f"register_{pc_number}_{keyboard_number}_{mouse_number}") self.add_item(self.reason_input_form) async def on_submit(self, interaction: discord.Interaction) -> None: @@ -795,8 +795,11 @@ async def on_button(interaction: discord.Interaction): keyboard_register_view = discord.ui.View(timeout=15) pc_number = custom_id_split[1] for i in dislocker.keyboard_list: - keyboard_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"keyboardregister_{str(pc_number)}_{str(i)}") - keyboard_register_view.add_item(keyboard_register_button) + if i == 0: + pass + else: + keyboard_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"keyboardregister_{str(pc_number)}_{str(i)}") + keyboard_register_view.add_item(keyboard_register_button) keyboard_not_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="キーボードは自前", custom_id=f"keyboardregister_{str(pc_number)}_own") keyboard_register_view.add_item(keyboard_not_register_button) @@ -811,8 +814,11 @@ async def on_button(interaction: discord.Interaction): else: keyboard_number_show = keyboard_number for i in dislocker.mouse_list: - mouse_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_{str(i)}") - mouse_register_view.add_item(mouse_register_button) + if i == 0: + pass + else: + mouse_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_{str(i)}") + mouse_register_view.add_item(mouse_register_button) mouse_not_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="マウスは自前", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_own") mouse_register_view.add_item(mouse_not_register_button) From ff30611ebda3a93a1ecc3c6b48325f19af2e05c7 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 17:01:20 +0900 Subject: [PATCH 093/175] =?UTF-8?q?=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89?= =?UTF-8?q?=E3=81=AE=E6=A8=A9=E9=99=90=E3=82=92=E8=A8=AD=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dislocker_slash.py b/dislocker_slash.py index 89a6a32..72869c0 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -978,6 +978,7 @@ async def stop(interaction: discord.Interaction): #管理者側のスラッシュコマンド @tree.command(name="userreg", description="ユーザーを登録します。") +@discord.app_commands.default_permissions(administrator=True) async def userreg(interaction: discord.Interaction, discord_user_id: str, discord_user_name: str, name: str): user_register = dislocker.user_register(discord_user_id=discord_user_id, discord_user_name=discord_user_name, name=name) if user_register["result"] == 0: @@ -990,6 +991,7 @@ async def userreg(interaction: discord.Interaction, discord_user_id: str, discor await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) @tree.command(name="pcreg", description="PCを登録するためのワンタイムパスワードを発行します。") +@discord.app_commands.default_permissions(administrator=True) async def pcreg(interaction: discord.Interaction): onetime = dislocker.pc_onetime_gen() if onetime["result"] == 0: @@ -1000,6 +1002,7 @@ async def pcreg(interaction: discord.Interaction): await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) @tree.command(name="devicereg", description="デバイスを登録するためのワンタイムパスワードを発行します。") +@discord.app_commands.default_permissions(administrator=True) async def devicereg(interaction: discord.Interaction): onetime = dislocker.device_onetime_gen() if onetime["result"] == 0: @@ -1010,6 +1013,7 @@ async def devicereg(interaction: discord.Interaction): await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) @tree.command(name="fstop", description="PCの使用登録を強制的に終了します。") +@discord.app_commands.default_permissions(administrator=True) async def fstop(interaction: discord.Interaction, pc_number: int, about: str): force_stop = dislocker.force_stop(pc_number=pc_number, bot_about=about) if force_stop["result"] == 0: @@ -1024,6 +1028,7 @@ async def fstop(interaction: discord.Interaction, pc_number: int, about: str): await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) @tree.command(name="report", description="PCの使用履歴をエクスポートします。") +@discord.app_commands.default_permissions(administrator=True) async def report(interaction: discord.Interaction): report_export = dislocker.report_export() if report_export["result"] == 0: @@ -1034,6 +1039,7 @@ async def report(interaction: discord.Interaction): await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) @tree.command(name="init", description="操作チャンネルにボタン一式を送信します。") +@discord.app_commands.default_permissions(administrator=True) async def button_init(interaction: discord.Interaction, text_channel: discord.TextChannel): user_register_button_view = discord.ui.View(timeout=None) user_register_button = discord.ui.Button(style=discord.ButtonStyle.green, label="ユーザー登録", custom_id="user_register") From f13d119eb2dd324aae55aea41222b8f04f18b6ae Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 17:04:55 +0900 Subject: [PATCH 094/175] =?UTF-8?q?=E3=83=87=E3=83=90=E3=82=A4=E3=82=B9?= =?UTF-8?q?=E7=95=AA=E5=8F=B7=E3=81=8C=E8=87=AA=E5=89=8D=E3=81=AE=E6=99=82?= =?UTF-8?q?=E3=81=AB0=E3=82=92=E7=99=BB=E9=8C=B2=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index 72869c0..a11ee02 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -340,8 +340,8 @@ class DL(): "name": str(kwargs["name"]), "display_name": str(kwargs["display_name"]), "pc_number": int(kwargs["pc_number"]), - "keyboard_number": None, - "mouse_number": None, + "keyboard_number": 0, + "mouse_number": 0, "detail": None } if "detail" in kwargs: From 6ea299f66141692fd9f676270075833048a102b9 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 17:16:15 +0900 Subject: [PATCH 095/175] =?UTF-8?q?asyncio=E5=B0=8E=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 100 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index a11ee02..a14f97f 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -3,7 +3,7 @@ import os import json import psycopg2 from psycopg2 import sql -from datetime import datetime +from datetime import datetime, timedelta import asyncio import string import random @@ -761,6 +761,92 @@ class ReasonModal(discord.ui.Modal): else: await interaction.response.send_message("# :skull_crossbones: 登録できませんでした。\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 + + async def search(self): + try: + asyncio.sleep(self.init_wait_time) + while True: + 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: + dislocker.log(title=f"[INFO] 定期のPCの使用停止処理を開始します。", flag=0) + for i in dislocker.pc_list: + stop = dislocker.force_stop(pc_number=i, bot_about="使用停止忘れによるBotによる強制停止。") + result = {"result": "FSTOP"} + dislocker.log(title=f"[SUCCESS] 定期のPCの使用停止処理は完了しました。", flag=0) + else: + if pc_list: + if len(pc_list) == 1: + member_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", (member_id,)) + pc_usage = cursor.fetchall() + start_time = pc_usage[0][5] + time_difference = current_datetime - start_time + dislocker.log(title=f"[INFO] 現在確認されているパスワード未使用のユーザー", message=f"レコード | {str(pc_usage)}, 経過時間(Sec) | {time_difference.seconds}/{timedelta(seconds=self.allowable_time).seconds}", flag=0) + if time_difference.seconds >= timedelta(seconds=self.allowable_time).seconds: + cursor.execute("SELECT * FROM club_member WHERE member_id = %s", (member_id,)) + user_info = cursor.fetchall() + stop = dislocker.stop(discord_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]) + dislocker.log(title=f"[INFO] パスワードのタイムアウト時間に達したため、強制停止されました。", flag=0) + result = {"result": "STOP", "details": str(pc_usage)} + else: + result = {"result": "BUT SAFE", "details": str(pc_usage)} + + + elif len(pc_list) >= 2: + for i in pc_list: + member_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", (member_id,)) + pc_usage = cursor.fetchall() + start_time = pc_usage[0][5] + time_difference = current_datetime - start_time + dislocker.log(title=f"[INFO] 現在確認されているパスワード未使用のユーザー", message=f"レコード | {str(pc_usage)}, 経過時間(Sec) | {time_difference.seconds}/{timedelta(seconds=self.allowable_time).seconds}", flag=0) + if time_difference.seconds >= timedelta(seconds=self.allowable_time).seconds: + cursor.execute("SELECT * FROM club_member WHERE member_id = %s", (member_id,)) + user_info = cursor.fetchall() + stop = dislocker.stop(discord_user_id=user_info[0][3], bot_about="タイムアウトでBotによる強制停止。") + + #bot.timeout_notify(pc_number=i[0], discord_display_name=user_info[0][1]) + dislocker.log(title=f"[INFO] パスワードのタイムアウト時間に達したため、強制停止されました。", flag=0) + result = {"result": "STOP", "details": str(pc_usage)} + else: + result = {"result": "BUT SAFE", "details": str(pc_usage)} + + else: + result = {"result": "NONE"} + else: + result = {"result": "NONE"} + + if result["result"] == "NONE": + pass + else: + pass + asyncio.sleep(self.search_frequency) + + + except Exception as error: + dislocker.log(title=f"[ERROR] 自動停止処理中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + result = {"result": "error"} + dislocker.db.rollback() + + finally: + if cursor: + cursor.close() + return result + + dislocker = DL() intents = discord.Intents.default() @@ -1064,5 +1150,15 @@ async def button_init(interaction: discord.Interaction, text_channel: discord.Te await interaction.response.send_message(f":white_check_mark: ボタンを送信しました!", ephemeral=True) +async def run(): + print("Botを起動します...") + 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_task = asyncio.create_task(monitor.start()) + client_task = asyncio.create_task(client.run(dislocker.server_config["bot"]["token"])) + await asyncio.gather(monitor_task, client_task) + -client.run(dislocker.server_config["bot"]["token"]) \ No newline at end of file +if dislocker.init_result == "ok": + asyncio.run(run()) +else: + pass \ No newline at end of file From 1cd58404c68da57a7f710c314bab521e57ac0569 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 17:17:15 +0900 Subject: [PATCH 096/175] =?UTF-8?q?=E9=96=A2=E6=95=B0=E3=81=AE=E5=90=8D?= =?UTF-8?q?=E5=89=8D=E3=83=9F=E3=82=B9=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index a14f97f..93530de 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -1153,7 +1153,7 @@ async def button_init(interaction: discord.Interaction, text_channel: discord.Te async def run(): print("Botを起動します...") 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_task = asyncio.create_task(monitor.start()) + monitor_task = asyncio.create_task(monitor.search()) client_task = asyncio.create_task(client.run(dislocker.server_config["bot"]["token"])) await asyncio.gather(monitor_task, client_task) From 885f62540391aaa8d7451ce40e00915583f3f7a8 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 17:26:48 +0900 Subject: [PATCH 097/175] =?UTF-8?q?asyncio=E3=82=92threading=E3=81=AB?= =?UTF-8?q?=E7=BD=AE=E3=81=8D=E6=8F=9B=E3=81=88=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index 93530de..10abe07 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -10,6 +10,8 @@ import random import hashlib import openpyxl from openpyxl import Workbook +import threading +import time class DL(): def __init__(self): @@ -769,9 +771,13 @@ class Monitor(): self.fstop_time = kwargs["fstop_time"] self.init_wait_time = 10 - async def search(self): + def start(self, **kwargs): + search_thread = threading.Thread(target=self.search) + search_thread.start() + + def search(self): try: - asyncio.sleep(self.init_wait_time) + time.sleep(self.init_wait_time) while True: cursor = dislocker.db.cursor() cursor.execute("SELECT * FROM pc_list WHERE password_hash IS NOT NULL") @@ -833,7 +839,7 @@ class Monitor(): pass else: pass - asyncio.sleep(self.search_frequency) + time.sleep(self.search_frequency) except Exception as error: @@ -1150,15 +1156,10 @@ async def button_init(interaction: discord.Interaction, text_channel: discord.Te await interaction.response.send_message(f":white_check_mark: ボタンを送信しました!", ephemeral=True) -async def run(): +if dislocker.init_result == "ok": print("Botを起動します...") 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_task = asyncio.create_task(monitor.search()) - client_task = asyncio.create_task(client.run(dislocker.server_config["bot"]["token"])) - await asyncio.gather(monitor_task, client_task) - - -if dislocker.init_result == "ok": - asyncio.run(run()) + monitor.start() + client.run(dislocker.server_config["bot"]["token"]) else: - pass \ No newline at end of file + pass From 02e0b8b95d3e64eeec9c6a39edacc5f14851f078 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 17:30:21 +0900 Subject: [PATCH 098/175] =?UTF-8?q?=E3=82=A2=E3=82=AF=E3=83=86=E3=82=A3?= =?UTF-8?q?=E3=83=93=E3=83=86=E3=82=A3=E3=81=AE=E5=A4=89=E6=9B=B4=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92=E7=A7=BB=E6=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index 10abe07..6a04f02 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -863,12 +863,15 @@ tree = discord.app_commands.CommandTree(client) @client.event async def on_ready(): - print("Bot is ready.") - print("Logged in as") - print(client.user.name) - print(client.user.id) - print("------") + dislocker.log(title=f"[SUCCESS] DiscordのBotが起動しました。", message=f"{client.user.name} としてログインしています。", flag=1) await tree.sync() + 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 client.change_presence(activity=dislocker_activity) @client.event async def on_interaction(interaction: discord.Interaction): From 33dd3aef9b38c892a76f867314a864f1ee0fb30f Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 18:09:51 +0900 Subject: [PATCH 099/175] =?UTF-8?q?=E6=8C=87=E5=AE=9A=E3=83=A6=E3=83=BC?= =?UTF-8?q?=E3=82=B6=E3=83=BC=E3=81=AEDM=E3=81=AB=E3=81=93=E3=81=9F?= =?UTF-8?q?=E3=81=88=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 90 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/dislocker_slash.py b/dislocker_slash.py index 6a04f02..39f1b47 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -873,6 +873,96 @@ async def on_ready(): ) await client.change_presence(activity=dislocker_activity) +@client.event +async def on_message(self, message): + if message.author.bot: + pass + + elif isinstance(message.channel, discord.DMChannel): + if message.author.id == dislocker.server_config["bot"]["admin_user_id"]: + msg_split = message.content.split() + if msg_split[0] == "/pcreg": + max_count = 1 + if len(msg_split) == 2: + if msg_split[1].isdecimal(): + max_count = int(msg_split[1]) + + if os.path.isfile(dislocker.onetime_config_path): + with open(dislocker.onetime_config_path, "r") as r: + onetime_config = json.load(r) + onetime = onetime_config["onetime"]["pc_register"]["password"] + if onetime == None: + onetime = str(self.password_generate(8)) + onetime_config["onetime"]["pc_register"]["password"] = onetime + onetime_config["onetime"]["pc_register"]["max_count"] = max_count + with open(dislocker.onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) + await message.channel.send(f"# :dizzy_face: PC登録時のワンタイムパスワードを発行します。\n# パスワード | {onetime}\n# 最大使用回数 | {str(max_count)}") + else: + await message.channel.send(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {onetime}\n# 残り使用回数 | {str(max_count - onetime_config['onetime']['pc_register']['current_count'])}") + else: + onetime = str(self.password_generate(8)) + onetime_config = { + "onetime": { + "pc_register": { + "password": onetime, + "current_count": 0, + "max_count": int(max_count) + }, + "device_register": { + "password": None, + "current_count": None, + "max_count": None + } + } + } + with open(dislocker.onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) + await message.channel.send(f"# :dizzy_face: PC登録時のワンタイムパスワードを発行します。\n# パスワード | {onetime}\n# 最大使用回数 | {str(max_count)}") + elif msg_split[0] == "/devreg": + max_count = 1 + if len(msg_split) == 2: + if msg_split[1].isdecimal(): + max_count = int(msg_split[1]) + + if os.path.isfile(dislocker.onetime_config_path): + with open(dislocker.onetime_config_path, "r") as r: + onetime_config = json.load(r) + onetime = onetime_config["onetime"]["device_register"]["password"] + if onetime == None: + onetime = str(self.password_generate(8)) + onetime_config["onetime"]["device_register"]["password"] = onetime + onetime_config["onetime"]["device_register"]["max_count"] = max_count + with open(dislocker.onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) + await message.channel.send(f"# :dizzy_face: デバイス登録時のワンタイムパスワードを発行します。\n# パスワード | {onetime}\n# 最大使用回数 | {str(max_count)}") + else: + await message.channel.send(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {onetime}\n# 残り使用回数 | {str(max_count - onetime_config['onetime']['device_register']['current_count'])}") + else: + onetime = str(self.password_generate(8)) + onetime_config = { + "onetime": { + "pc_register": { + "password": None, + "current_count": None, + "max_count": None + }, + "device_register": { + "password": onetime, + "current_count": 0, + "max_count": int(max_count) + } + } + } + with open(dislocker.onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) + await message.channel.send(f"# :dizzy_face: デバイス登録時のワンタイムパスワードを発行します。\n# パスワード | {onetime}\n# 最大使用回数 | {str(max_count)}") + + else: + await message.channel.send("# :warning: DMでの応答は、現在無効化されています。") + else: + await message.channel.send("# :warning: DMでの応答は、現在無効化されています。") + @client.event async def on_interaction(interaction: discord.Interaction): try: From aebab83b866f3a502394f7b7bcef1e6fa79f329d Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 18:10:12 +0900 Subject: [PATCH 100/175] =?UTF-8?q?=E3=83=AF=E3=83=B3=E3=82=BF=E3=82=A4?= =?UTF-8?q?=E3=83=A0=E3=83=91=E3=82=B9=E3=83=AF=E3=83=BC=E3=83=89=E3=81=AE?= =?UTF-8?q?=E4=BB=95=E6=A7=98=E5=A4=89=E6=9B=B4=E3=81=AB=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_auth.py | 61 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/dislocker_auth.py b/dislocker_auth.py index a4b32be..0c08fcd 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -334,15 +334,29 @@ def register(): with open(onetime_config_path, "r") as r: onetime_config = json.load(r) - if onetime_password == onetime_config["onetime"]["pc_register"]: + if onetime_password == onetime_config["onetime"]["pc_register"]["password"]: register_result = auth.register(pc_number=pc_number, pc_uuid=pc_uuid) - pc_token = register_result["output_dict"]["pc_token"] - master_password = register_result["output_dict"]["master_password"] - master_password_hash = register_result["output_dict"]["master_password_hash"] - onetime_config["onetime"]["pc_register"] = None - with open(onetime_config_path, "w") as w: - json.dump(onetime_config, w, indent=4) - return jsonify({'message': 'ok', 'pc_token': pc_token, 'master_password': master_password, 'master_password_hash': master_password_hash}), 200 + if register_result["result"] == 0: + pc_token = register_result["output_dict"]["pc_token"] + master_password = register_result["output_dict"]["master_password"] + master_password_hash = register_result["output_dict"]["master_password_hash"] + onetime_config["onetime"]["pc_register"]["current_count"] += 1 + + if onetime_config["onetime"]["pc_register"]["current_count"] == onetime_config["onetime"]["pc_register"]["max_count"]: + onetime_config["onetime"]["pc_register"]["password"] = None + with open(onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) + return jsonify({'message': 'ok', 'pc_token': pc_token, 'master_password': master_password, 'master_password_hash': master_password_hash}), 200 + else: + with open(onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) + return jsonify({'message': 'ok', 'pc_token': pc_token, 'master_password': master_password, 'master_password_hash': master_password_hash}), 200 + elif register_result["result"] == 1: + if register_result["about"] == "exist": + return jsonify({'message': 'exist'}), 401 + else: + return jsonify({'message': 'damedesu'}), 401 + else: return jsonify({'message': 'damedesu'}), 401 else: @@ -410,22 +424,37 @@ def device_register(): device_register = auth.device_register(mode="keyboard", number=number, device_instance_path=device_instance_path, device_name=device_name) if device_register["result"] == 0: print(f"キーボード {number} 番の登録処理は成功しました.") - onetime_config["onetime"]["device_register"] = None - with open(onetime_config_path, "w") as w: - json.dump(onetime_config, w, indent=4) - return jsonify({'message': 'ok'}), 200 + onetime_config["onetime"]["device_register"]["current_count"] += 1 + + if onetime_config["onetime"]["device_register"]["current_count"] == onetime_config["onetime"]["device_register"]["max_count"]: + onetime_config["onetime"]["device_register"] = None + with open(onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) + return jsonify({'message': 'ok'}), 200 + else: + with open(onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) + return jsonify({'message': 'ok'}), 200 else: print(f"キーボード {number} 番の登録処理は失敗しました.") return jsonify({'message': 'error'}), 500 + elif mode == "mouse": print("マウスの登録処理を開始...") device_register = auth.device_register(mode="mouse", number=number, device_instance_path=device_instance_path, device_name=device_name) if device_register["result"] == 0: print(f"マウス {number} 番の登録処理は成功しました.") - onetime_config["onetime"]["device_register"] = None - with open(onetime_config_path, "w") as w: - json.dump(onetime_config, w, indent=4) - return jsonify({'message': 'ok'}), 200 + onetime_config["onetime"]["device_register"]["current_count"] += 1 + + if onetime_config["onetime"]["device_register"]["current_count"] == onetime_config["onetime"]["device_register"]["max_count"]: + onetime_config["onetime"]["device_register"] = None + with open(onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) + return jsonify({'message': 'ok'}), 200 + else: + with open(onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) + return jsonify({'message': 'ok'}), 200 else: print(f"マウス {number} 番の登録処理は失敗しました.") return jsonify({'message': 'error'}), 500 From 421b1b48c77d42153ffee8c49edc800bc8041928 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 21:05:43 +0900 Subject: [PATCH 101/175] =?UTF-8?q?=E3=82=B9=E3=83=A9=E3=83=83=E3=82=B7?= =?UTF-8?q?=E3=83=A5=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89=E3=81=AEPC?= =?UTF-8?q?=E3=80=81=E3=83=87=E3=83=90=E3=82=A4=E3=82=B9=E7=99=BB=E9=8C=B2?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=82=92=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 255 ++++++++++++++++++++++++++------------------- 1 file changed, 150 insertions(+), 105 deletions(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index 39f1b47..4f7893b 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -645,62 +645,107 @@ class DL(): if cursor: cursor.close() - def pc_onetime_gen(self): + def pc_onetime_gen(self, **kwargs): + if kwargs.get("max_count") == None: + max_count = 1 + elif kwargs.get("max_count").isdecimal(): + max_count = int(kwargs.get("max_count")) + else: + max_count = 1 + try: if os.path.isfile(dislocker.onetime_config_path): with open(dislocker.onetime_config_path, "r") as r: onetime_config = json.load(r) - onetime = onetime_config["onetime"]["pc_register"] - if onetime == None: - onetime = str(self.password_generate(8)) - onetime_config["onetime"]["pc_register"] = onetime + onetime_password = onetime_config["onetime"]["pc_register"]["password"] + if onetime_password == None: + onetime_password = str(self.password_generate(8)) + onetime_config["onetime"]["pc_register"]["password"] = onetime_password + onetime_config["onetime"]["pc_register"]["max_count"] = max_count + onetime_config["onetime"]["pc_register"]["current_count"] = 0 + current_count = onetime_config["onetime"]["pc_register"]["current_count"] with open(dislocker.onetime_config_path, "w") as w: json.dump(onetime_config, w, indent=4) - return {"result": 0, "about": "ok", "onetime": onetime} + return {"result": 0, "about": "ok", "output_dict": {"onetime_password": onetime_password, "current_count": current_count, "max_count": max_count}} else: - return {"result": 0, "already_exists": "ok", "onetime": onetime} + current_count = onetime_config["onetime"]["pc_register"]["current_count"] + max_count = onetime_config["onetime"]["pc_register"]["max_count"] + return {"result": 1, "about": "already_exists", "output_dict": {"onetime_password": onetime_password, "current_count": current_count, "max_count": max_count}} else: - onetime = str(self.password_generate(8)) + onetime_password = str(self.password_generate(8)) onetime_config = { "onetime": { - "pc_register": onetime, - "device_register": None + "pc_register": { + "password": onetime_password, + "current_count": 0, + "max_count": int(max_count) + }, + "device_register": { + "password": None, + "current_count": None, + "max_count": None + } } } + current_count = onetime_config["onetime"]["pc_register"]["current_count"] with open(dislocker.onetime_config_path, "w") as w: json.dump(onetime_config, w, indent=4) - return {"result": 0, "about": "ok", "onetime": onetime} + return {"result": 0, "about": "ok", "output_dict": {"onetime_password": onetime_password, "current_count": current_count, "max_count": max_count}} + except Exception as error: - self.log(title=f"[ERROR] PC登録用のワンタイムパスワードを発行中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + self.log(title=f"[ERROR] PC登録用のワンタイムパスワード発行中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) return {"result": 1, "about": "error"} + - def device_onetime_gen(self): + def device_onetime_gen(self, **kwargs): + if kwargs.get("max_count") == None: + max_count = 1 + elif kwargs.get("max_count").isdecimal(): + max_count = int(kwargs.get("max_count")) + else: + max_count = 1 + try: if os.path.isfile(dislocker.onetime_config_path): with open(dislocker.onetime_config_path, "r") as r: onetime_config = json.load(r) - onetime = onetime_config["onetime"]["device_register"] - if onetime == None: - onetime = str(self.password_generate(8)) - onetime_config["onetime"]["device_register"] = onetime + onetime_password = onetime_config["onetime"]["device_register"]["password"] + if onetime_password == None: + onetime_password = str(self.password_generate(8)) + onetime_config["onetime"]["device_register"]["password"] = onetime_password + onetime_config["onetime"]["device_register"]["max_count"] = max_count + onetime_config["onetime"]["device_register"]["current_count"] = 0 + current_count = onetime_config["onetime"]["device_register"]["current_count"] with open(dislocker.onetime_config_path, "w") as w: json.dump(onetime_config, w, indent=4) - return {"result": 0, "about": "ok", "onetime": onetime} + return {"result": 0, "about": "ok", "output_dict": {"onetime_password": onetime_password, "current_count": current_count, "max_count": max_count}} else: - return {"result": 0, "already_exists": "ok", "onetime": onetime} + current_count = onetime_config["onetime"]["device_register"]["current_count"] + max_count = onetime_config["onetime"]["device_register"]["max_count"] + return {"result": 1, "about": "already_exists", "output_dict": {"onetime_password": onetime_password, "current_count": current_count, "max_count": max_count}} else: - onetime = str(self.password_generate(8)) + onetime_password = str(self.password_generate(8)) onetime_config = { "onetime": { - "pc_register": None, - "device_register": onetime + "pc_register": { + "password": None, + "current_count": None, + "max_count": None + }, + "device_register": { + "password": onetime_password, + "current_count": 0, + "max_count": int(max_count) + } } } + current_count = onetime_config["onetime"]["device_register"]["current_count"] with open(dislocker.onetime_config_path, "w") as w: json.dump(onetime_config, w, indent=4) - return {"result": 0, "about": "ok", "onetime": onetime} + return {"result": 0, "about": "ok", "output_dict": {"onetime_password": onetime_password, "current_count": current_count, "max_count": max_count}} + except Exception as error: - self.log(title=f"[ERROR] デバイス登録用のワンタイムパスワードを発行中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + self.log(title=f"[ERROR] PC登録用のワンタイムパスワード発行中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) return {"result": 1, "about": "error"} @@ -881,87 +926,61 @@ async def on_message(self, message): elif isinstance(message.channel, discord.DMChannel): if message.author.id == dislocker.server_config["bot"]["admin_user_id"]: msg_split = message.content.split() + if msg_split[0] == "/pcreg": max_count = 1 if len(msg_split) == 2: if msg_split[1].isdecimal(): max_count = int(msg_split[1]) + + pc_onetime_password_gen = dislocker.pc_onetime_gen(max_count=max_count) + + if pc_onetime_password_gen["result"] == 0: + pc_onetime_password = str(pc_onetime_password_gen["output_dict"]["onetime_password"]) + pc_onetime_password_max_count = str(pc_onetime_password_gen["output_dict"]["max_count"]) + pc_onetime_password_current_count = str(pc_onetime_password_gen["output_dict"]["current_count"]) + pc_onetime_password_remaining_times = str(int(pc_onetime_password_max_count) - int(pc_onetime_password_current_count)) - if os.path.isfile(dislocker.onetime_config_path): - with open(dislocker.onetime_config_path, "r") as r: - onetime_config = json.load(r) - onetime = onetime_config["onetime"]["pc_register"]["password"] - if onetime == None: - onetime = str(self.password_generate(8)) - onetime_config["onetime"]["pc_register"]["password"] = onetime - onetime_config["onetime"]["pc_register"]["max_count"] = max_count - with open(dislocker.onetime_config_path, "w") as w: - json.dump(onetime_config, w, indent=4) - await message.channel.send(f"# :dizzy_face: PC登録時のワンタイムパスワードを発行します。\n# パスワード | {onetime}\n# 最大使用回数 | {str(max_count)}") + await message.channel.send(f"# :dizzy_face: PC登録時のワンタイムパスワードを発行します。\n# パスワード | {pc_onetime_password}\n# 最大使用回数 | {pc_onetime_password_max_count}\n# 残り使用回数 | {pc_onetime_password_remaining_times}") + elif pc_onetime_password_gen["result"] == 1: + if pc_onetime_password_gen["about"] == "already_exists": + pc_onetime_password = str(pc_onetime_password_gen["output_dict"]["onetime_password"]) + pc_onetime_password_max_count = str(pc_onetime_password_gen["output_dict"]["max_count"]) + pc_onetime_password_current_count = str(pc_onetime_password_gen["output_dict"]["current_count"]) + pc_onetime_password_remaining_times = str(int(pc_onetime_password_max_count) - int(pc_onetime_password_current_count)) + await message.channel.send(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {pc_onetime_password}\n# 残り使用回数 | {pc_onetime_password_remaining_times}") else: - await message.channel.send(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {onetime}\n# 残り使用回数 | {str(max_count - onetime_config['onetime']['pc_register']['current_count'])}") - else: - onetime = str(self.password_generate(8)) - onetime_config = { - "onetime": { - "pc_register": { - "password": onetime, - "current_count": 0, - "max_count": int(max_count) - }, - "device_register": { - "password": None, - "current_count": None, - "max_count": None - } - } - } - with open(dislocker.onetime_config_path, "w") as w: - json.dump(onetime_config, w, indent=4) - await message.channel.send(f"# :dizzy_face: PC登録時のワンタイムパスワードを発行します。\n# パスワード | {onetime}\n# 最大使用回数 | {str(max_count)}") + await message.channel.send("# :skull_crossbones: ワンタイムパスワードの発行に失敗しました。") + elif msg_split[0] == "/devreg": max_count = 1 if len(msg_split) == 2: if msg_split[1].isdecimal(): max_count = int(msg_split[1]) - if os.path.isfile(dislocker.onetime_config_path): - with open(dislocker.onetime_config_path, "r") as r: - onetime_config = json.load(r) - onetime = onetime_config["onetime"]["device_register"]["password"] - if onetime == None: - onetime = str(self.password_generate(8)) - onetime_config["onetime"]["device_register"]["password"] = onetime - onetime_config["onetime"]["device_register"]["max_count"] = max_count - with open(dislocker.onetime_config_path, "w") as w: - json.dump(onetime_config, w, indent=4) - await message.channel.send(f"# :dizzy_face: デバイス登録時のワンタイムパスワードを発行します。\n# パスワード | {onetime}\n# 最大使用回数 | {str(max_count)}") + device_onetime_password_gen = dislocker.device_onetime_gen(max_count=max_count) + + if device_onetime_password_gen["result"] == 0: + device_onetime_password = str(device_onetime_password_gen["output_dict"]["onetime_password"]) + device_onetime_password_max_count = str(device_onetime_password_gen["output_dict"]["max_count"]) + device_onetime_password_current_count = str(device_onetime_password_gen["output_dict"]["current_count"]) + device_onetime_password_remaining_times = str(int(device_onetime_password_max_count) - int(device_onetime_password_current_count)) + + await message.channel.send(f"# :dizzy_face: デバイス登録時のワンタイムパスワードを発行します。\n# パスワード | {device_onetime_password}\n# 最大使用回数 | {device_onetime_password_max_count}\n# 残り使用回数 | {device_onetime_password_remaining_times}") + elif device_onetime_password_gen["result"] == 1: + if device_onetime_password_gen["about"] == "already_exists": + device_onetime_password = str(device_onetime_password_gen["output_dict"]["onetime_password"]) + device_onetime_password_max_count = str(device_onetime_password_gen["output_dict"]["max_count"]) + device_onetime_password_current_count = str(device_onetime_password_gen["output_dict"]["current_count"]) + device_onetime_password_remaining_times = str(int(device_onetime_password_max_count) - int(device_onetime_password_current_count)) + await message.channel.send(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {device_onetime_password}\n# 残り使用回数 | {device_onetime_password_remaining_times}") else: - await message.channel.send(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {onetime}\n# 残り使用回数 | {str(max_count - onetime_config['onetime']['device_register']['current_count'])}") - else: - onetime = str(self.password_generate(8)) - onetime_config = { - "onetime": { - "pc_register": { - "password": None, - "current_count": None, - "max_count": None - }, - "device_register": { - "password": onetime, - "current_count": 0, - "max_count": int(max_count) - } - } - } - with open(dislocker.onetime_config_path, "w") as w: - json.dump(onetime_config, w, indent=4) - await message.channel.send(f"# :dizzy_face: デバイス登録時のワンタイムパスワードを発行します。\n# パスワード | {onetime}\n# 最大使用回数 | {str(max_count)}") + await message.channel.send("# :skull_crossbones: ワンタイムパスワードの発行に失敗しました。") else: await message.channel.send("# :warning: DMでの応答は、現在無効化されています。") else: - await message.channel.send("# :warning: DMでの応答は、現在無効化されています。") + pass @client.event async def on_interaction(interaction: discord.Interaction): @@ -1175,27 +1194,53 @@ async def userreg(interaction: discord.Interaction, discord_user_id: str, discor elif user_register["about"] == "error": await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) -@tree.command(name="pcreg", description="PCを登録するためのワンタイムパスワードを発行します。") +@tree.command(name="pcreg", description="PCをDislockerに登録するためのワンタイムパスワードを発行します。") @discord.app_commands.default_permissions(administrator=True) -async def pcreg(interaction: discord.Interaction): - onetime = dislocker.pc_onetime_gen() - if onetime["result"] == 0: - await interaction.response.send_message(f":white_check_mark: PC登録用のワンタイムパスワードを発行しました。\n>>> # ワンタイムパスワード | {onetime['onetime']}\n## 有効期限 | 1回の登録でのみ使用可能です。", ephemeral=True) - dislocker.log(title=f"[INFO] PC登録用のワンタイムパスワードを発行しました。", message=f"ワンタイムパスワード | {onetime['onetime']}", flag=0) - elif onetime["result"] == 1: - if onetime["about"] == "error": - await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) +async def pcreg(interaction: discord.Interaction, how_much: int = 1): + max_count = how_much + + pc_onetime_password_gen = dislocker.pc_onetime_gen(max_count=max_count) + + if pc_onetime_password_gen["result"] == 0: + pc_onetime_password = str(pc_onetime_password_gen["output_dict"]["onetime_password"]) + pc_onetime_password_max_count = str(pc_onetime_password_gen["output_dict"]["max_count"]) + pc_onetime_password_current_count = str(pc_onetime_password_gen["output_dict"]["current_count"]) + pc_onetime_password_remaining_times = str(int(pc_onetime_password_max_count) - int(pc_onetime_password_current_count)) -@tree.command(name="devicereg", description="デバイスを登録するためのワンタイムパスワードを発行します。") + await interaction.response.send_message(f"# :dizzy_face: PC登録時のワンタイムパスワードを発行します。\n# パスワード | {pc_onetime_password}\n# 最大使用回数 | {pc_onetime_password_max_count}\n# 残り使用回数 | {pc_onetime_password_remaining_times}", ephemeral=True) + elif pc_onetime_password_gen["result"] == 1: + if pc_onetime_password_gen["about"] == "already_exists": + pc_onetime_password = str(pc_onetime_password_gen["output_dict"]["onetime_password"]) + pc_onetime_password_max_count = str(pc_onetime_password_gen["output_dict"]["max_count"]) + pc_onetime_password_current_count = str(pc_onetime_password_gen["output_dict"]["current_count"]) + pc_onetime_password_remaining_times = str(int(pc_onetime_password_max_count) - int(pc_onetime_password_current_count)) + await interaction.response.send_message(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {pc_onetime_password}\n# 残り使用回数 | {pc_onetime_password_remaining_times}", ephemeral=True) + else: + await interaction.response.send_message("# :skull_crossbones: ワンタイムパスワードの発行に失敗しました。", ephemeral=True) + +@tree.command(name="devicereg", description="デバイスをDislockerに登録するためのワンタイムパスワードを発行します。") @discord.app_commands.default_permissions(administrator=True) -async def devicereg(interaction: discord.Interaction): - onetime = dislocker.device_onetime_gen() - if onetime["result"] == 0: - await interaction.response.send_message(f":white_check_mark: デバイス登録用のワンタイムパスワードを発行しました。\n>>> # ワンタイムパスワード | {onetime['onetime']}\n## 有効期限 | 1回の登録でのみ使用可能です。", ephemeral=True) - dislocker.log(title=f"[INFO] デバイス登録用のワンタイムパスワードを発行しました。", message=f"ワンタイムパスワード | {onetime['onetime']}", flag=0) - elif onetime["result"] == 1: - if onetime["about"] == "error": - await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) +async def devicereg(interaction: discord.Interaction, how_much: int): + max_count = how_much + + device_onetime_password_gen = dislocker.device_onetime_gen(max_count=max_count) + + if device_onetime_password_gen["result"] == 0: + device_onetime_password = str(device_onetime_password_gen["output_dict"]["onetime_password"]) + device_onetime_password_max_count = str(device_onetime_password_gen["output_dict"]["max_count"]) + device_onetime_password_current_count = str(device_onetime_password_gen["output_dict"]["current_count"]) + device_onetime_password_remaining_times = str(int(device_onetime_password_max_count) - int(device_onetime_password_current_count)) + + await interaction.response.send_message(f"# :dizzy_face: デバイス登録時のワンタイムパスワードを発行します。\n# パスワード | {device_onetime_password}\n# 最大使用回数 | {device_onetime_password_max_count}\n# 残り使用回数 | {device_onetime_password_remaining_times}", ephemeral=True) + elif device_onetime_password_gen["result"] == 1: + if device_onetime_password_gen["about"] == "already_exists": + device_onetime_password = str(device_onetime_password_gen["output_dict"]["onetime_password"]) + device_onetime_password_max_count = str(device_onetime_password_gen["output_dict"]["max_count"]) + device_onetime_password_current_count = str(device_onetime_password_gen["output_dict"]["current_count"]) + device_onetime_password_remaining_times = str(int(device_onetime_password_max_count) - int(device_onetime_password_current_count)) + await interaction.response.send_message(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {device_onetime_password}\n# 残り使用回数 | {device_onetime_password_remaining_times}", ephemeral=True) + else: + await interaction.response.send_message("# :skull_crossbones: ワンタイムパスワードの発行に失敗しました。", ephemeral=True) @tree.command(name="fstop", description="PCの使用登録を強制的に終了します。") @discord.app_commands.default_permissions(administrator=True) From aa9379b2a6b3282c3709266a6ccf71097a827bd1 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 21:15:47 +0900 Subject: [PATCH 102/175] =?UTF-8?q?=E3=82=B9=E3=83=A9=E3=83=83=E3=82=B7?= =?UTF-8?q?=E3=83=A5=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89=E3=81=A7=E3=83=AF?= =?UTF-8?q?=E3=83=B3=E3=82=BF=E3=82=A4=E3=83=A0=E3=83=91=E3=82=B9=E3=83=AF?= =?UTF-8?q?=E3=83=BC=E3=83=89=E3=81=8C=E8=A6=81=E6=B1=82=E3=81=A7=E3=81=8D?= =?UTF-8?q?=E3=81=AA=E3=81=84=E3=83=90=E3=82=B0=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index 4f7893b..262cacb 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -648,7 +648,7 @@ class DL(): def pc_onetime_gen(self, **kwargs): if kwargs.get("max_count") == None: max_count = 1 - elif kwargs.get("max_count").isdecimal(): + elif isinstance(kwargs.get("max_count"), int): max_count = int(kwargs.get("max_count")) else: max_count = 1 @@ -700,7 +700,7 @@ class DL(): def device_onetime_gen(self, **kwargs): if kwargs.get("max_count") == None: max_count = 1 - elif kwargs.get("max_count").isdecimal(): + elif isinstance(kwargs.get("max_count"), int): max_count = int(kwargs.get("max_count")) else: max_count = 1 From 224492568a6d1925ea81d6cb9f45194a7269b89a Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 21:22:08 +0900 Subject: [PATCH 103/175] =?UTF-8?q?=E8=B5=B7=E5=8B=95=E6=99=82=E3=81=AB?= =?UTF-8?q?=E3=83=AF=E3=83=B3=E3=82=BF=E3=82=A4=E3=83=A0=E3=83=91=E3=82=B9?= =?UTF-8?q?=E3=83=AF=E3=83=BC=E3=83=89=E3=82=92=E5=89=8A=E9=99=A4=E3=81=99?= =?UTF-8?q?=E3=82=8B=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index 262cacb..8b77e51 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -66,7 +66,6 @@ class DL(): 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 ディレクトリが見つかりません... 作成します。") @@ -79,6 +78,11 @@ class DL(): 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" + + if os.path.isfile(self.onetime_config_path): + print("ワンタイムパスワードが見つかりました。削除します。") + os.remove(self.onetime_config_path) + 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() @@ -745,7 +749,7 @@ class DL(): return {"result": 0, "about": "ok", "output_dict": {"onetime_password": onetime_password, "current_count": current_count, "max_count": max_count}} except Exception as error: - self.log(title=f"[ERROR] PC登録用のワンタイムパスワード発行中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + self.log(title=f"[ERROR] デバイス登録用のワンタイムパスワード発行中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) return {"result": 1, "about": "error"} From 42e36bf1c480aa97e65f0b529f3d8a69564ccc15 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 21:24:49 +0900 Subject: [PATCH 104/175] =?UTF-8?q?if=E6=96=87=E3=81=AE=E3=83=9F=E3=82=B9?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E3=80=81on=5Fmessage=E3=81=AEself=E5=89=8A?= =?UTF-8?q?=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index 8b77e51..c19a2bd 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -75,14 +75,13 @@ class DL(): print("log ディレクトリが見つかりません... 作成します。") os.mkdir(self.log_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" - if os.path.isfile(self.onetime_config_path): print("ワンタイムパスワードが見つかりました。削除します。") os.remove(self.onetime_config_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() @@ -923,7 +922,7 @@ async def on_ready(): await client.change_presence(activity=dislocker_activity) @client.event -async def on_message(self, message): +async def on_message(message): if message.author.bot: pass From 69cb9537ff90a963a34570426bfa170dd99b6b50 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 21:42:33 +0900 Subject: [PATCH 105/175] =?UTF-8?q?=E3=83=9E=E3=82=B9=E3=82=BF=E3=83=BC?= =?UTF-8?q?=E3=83=91=E3=82=B9=E3=83=AF=E3=83=BC=E3=83=89=E3=82=92=E5=8F=96?= =?UTF-8?q?=E5=BE=97=E3=81=99=E3=82=8B=E3=82=B9=E3=83=A9=E3=83=83=E3=82=B7?= =?UTF-8?q?=E3=83=A5=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89=E3=81=A8=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/dislocker_slash.py b/dislocker_slash.py index c19a2bd..057a849 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -751,6 +751,23 @@ class DL(): self.log(title=f"[ERROR] デバイス登録用のワンタイムパスワード発行中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) return {"result": 1, "about": "error"} + def show_pc_master_password(self, **kwargs): + if isinstance(kwargs.get("pc_number"), int): + try: + pc_number = int(kwargs.get("pc_number")) + + cursor = self.db.cursor() + cursor.execute("SELECT master_password FROM pc_list WHERE pc_number = %s", (pc_number,)) + pc_master_password_list = cursor.fetchall() + pc_master_password = pc_master_password_list[0][0] + + return {"result": 0, "about": "ok", "output_dict": {"pc_master_password": pc_master_password}} + except Exception as error: + self.log(title=f"[ERROR] PCのマスターパスワードを取得中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error"} + else: + return {"result": 1, "about": "syntax_error"} + class ReasonModal(discord.ui.Modal): def __init__(self, title: str, pc_number: str, keyboard_number: str, mouse_number: str, timeout=15) -> None: @@ -1296,6 +1313,17 @@ async def button_init(interaction: discord.Interaction, text_channel: discord.Te await interaction.response.send_message(f":white_check_mark: ボタンを送信しました!", ephemeral=True) +@tree.command(name="masterpass", description="PCのマスターパスワードを表示します。") +@discord.app_commands.default_permissions(administrator=True) +async def masterpass(interaction: discord.Interaction, pc_number: int): + pc_master_password_get = dislocker.pc_master_password(pc_number=pc_number) + + if pc_master_password_get["result"] == 0: + pc_master_password = pc_master_password_get["output_dict"]["master_password"] + await interaction.response.send_message(f"# :key: PC番号 {pc_number} 番のマスターパスワードは以下の通りです。\n>>> # マスターパスワード | {pc_master_password}", ephemeral=True) + else: + await interaction.response.send_message("# :skull_crossbones: マスターパスワードの取得に失敗しました。", ephemeral=True) + if dislocker.init_result == "ok": print("Botを起動します...") From 6fae61351db941de56c6ac7a6cb8a048e05489d3 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 21:44:16 +0900 Subject: [PATCH 106/175] =?UTF-8?q?=E9=96=A2=E6=95=B0=E5=90=8D=E9=96=93?= =?UTF-8?q?=E9=81=95=E3=81=88=E3=81=A6=E3=81=9F=E3=81=AE=E3=81=A7=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index 057a849..118dc43 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -1316,7 +1316,7 @@ async def button_init(interaction: discord.Interaction, text_channel: discord.Te @tree.command(name="masterpass", description="PCのマスターパスワードを表示します。") @discord.app_commands.default_permissions(administrator=True) async def masterpass(interaction: discord.Interaction, pc_number: int): - pc_master_password_get = dislocker.pc_master_password(pc_number=pc_number) + pc_master_password_get = dislocker.show_pc_master_password(pc_number=pc_number) if pc_master_password_get["result"] == 0: pc_master_password = pc_master_password_get["output_dict"]["master_password"] From 8775b227388bfdebe06bf462157f7a100f39f20c Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 21:45:56 +0900 Subject: [PATCH 107/175] =?UTF-8?q?=E3=82=AD=E3=83=BC=E5=90=8D=E3=82=82?= =?UTF-8?q?=E9=96=93=E9=81=95=E3=81=88=E3=81=A6=E3=81=9F=E3=81=AE=E3=81=A7?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index 118dc43..4759b18 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -1319,7 +1319,7 @@ async def masterpass(interaction: discord.Interaction, pc_number: int): pc_master_password_get = dislocker.show_pc_master_password(pc_number=pc_number) if pc_master_password_get["result"] == 0: - pc_master_password = pc_master_password_get["output_dict"]["master_password"] + pc_master_password = pc_master_password_get["output_dict"]["pc_master_password"] await interaction.response.send_message(f"# :key: PC番号 {pc_number} 番のマスターパスワードは以下の通りです。\n>>> # マスターパスワード | {pc_master_password}", ephemeral=True) else: await interaction.response.send_message("# :skull_crossbones: マスターパスワードの取得に失敗しました。", ephemeral=True) From 364b32c0e796444949b73edbca950eb2ba6afa85 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 21:57:13 +0900 Subject: [PATCH 108/175] =?UTF-8?q?=E7=AE=A1=E7=90=86=E8=80=85=E3=82=B3?= =?UTF-8?q?=E3=83=9E=E3=83=B3=E3=83=89=E3=81=AE=E3=82=BB=E3=82=AD=E3=83=A5?= =?UTF-8?q?=E3=83=AA=E3=83=86=E3=82=A3=E3=81=AE=E5=90=91=E4=B8=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 166 ++++++++++++++++++++++++--------------------- 1 file changed, 87 insertions(+), 79 deletions(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index 4759b18..0ccb343 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -37,6 +37,7 @@ class DL(): "password": "password" }, "bot": { + "server_id": "TYPE HERE SERVER ID", "token": "TYPE HERE BOTS TOKEN KEY", "activity": { "name": "Dislocker", @@ -1204,125 +1205,132 @@ async def stop(interaction: discord.Interaction): @tree.command(name="userreg", description="ユーザーを登録します。") @discord.app_commands.default_permissions(administrator=True) async def userreg(interaction: discord.Interaction, discord_user_id: str, discord_user_name: str, name: str): - user_register = dislocker.user_register(discord_user_id=discord_user_id, discord_user_name=discord_user_name, name=name) - if user_register["result"] == 0: - await interaction.response.send_message(":white_check_mark: ユーザーを登録しました。", ephemeral=True) - dislocker.log(title=f"[INFO] ユーザーを登録しました。", message=f"名前 | {name}, Discordユーザー名 | {discord_user_name}, DiscordユーザーID | {discord_user_id}", flag=0) - elif user_register["result"] == 1: - if user_register["about"] == "already_exists": - await interaction.response.send_message(":x: 既に登録されているユーザーです。", ephemeral=True) - elif user_register["about"] == "error": - await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) + if interaction.guild_id == dislocker.server_config["bot"]["server_id"] or interaction.user.id == dislocker.server_config["bot"]["admin_user_id"]: + user_register = dislocker.user_register(discord_user_id=discord_user_id, discord_user_name=discord_user_name, name=name) + if user_register["result"] == 0: + await interaction.response.send_message(":white_check_mark: ユーザーを登録しました。", ephemeral=True) + dislocker.log(title=f"[INFO] ユーザーを登録しました。", message=f"名前 | {name}, Discordユーザー名 | {discord_user_name}, DiscordユーザーID | {discord_user_id}", flag=0) + elif user_register["result"] == 1: + if user_register["about"] == "already_exists": + await interaction.response.send_message(":x: 既に登録されているユーザーです。", ephemeral=True) + elif user_register["about"] == "error": + await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) @tree.command(name="pcreg", description="PCをDislockerに登録するためのワンタイムパスワードを発行します。") @discord.app_commands.default_permissions(administrator=True) async def pcreg(interaction: discord.Interaction, how_much: int = 1): - max_count = how_much - - pc_onetime_password_gen = dislocker.pc_onetime_gen(max_count=max_count) - - if pc_onetime_password_gen["result"] == 0: - pc_onetime_password = str(pc_onetime_password_gen["output_dict"]["onetime_password"]) - pc_onetime_password_max_count = str(pc_onetime_password_gen["output_dict"]["max_count"]) - pc_onetime_password_current_count = str(pc_onetime_password_gen["output_dict"]["current_count"]) - pc_onetime_password_remaining_times = str(int(pc_onetime_password_max_count) - int(pc_onetime_password_current_count)) - - await interaction.response.send_message(f"# :dizzy_face: PC登録時のワンタイムパスワードを発行します。\n# パスワード | {pc_onetime_password}\n# 最大使用回数 | {pc_onetime_password_max_count}\n# 残り使用回数 | {pc_onetime_password_remaining_times}", ephemeral=True) - elif pc_onetime_password_gen["result"] == 1: - if pc_onetime_password_gen["about"] == "already_exists": + if interaction.guild_id == dislocker.server_config["bot"]["server_id"] or interaction.user.id == dislocker.server_config["bot"]["admin_user_id"]: + max_count = how_much + + pc_onetime_password_gen = dislocker.pc_onetime_gen(max_count=max_count) + + if pc_onetime_password_gen["result"] == 0: pc_onetime_password = str(pc_onetime_password_gen["output_dict"]["onetime_password"]) pc_onetime_password_max_count = str(pc_onetime_password_gen["output_dict"]["max_count"]) pc_onetime_password_current_count = str(pc_onetime_password_gen["output_dict"]["current_count"]) pc_onetime_password_remaining_times = str(int(pc_onetime_password_max_count) - int(pc_onetime_password_current_count)) - await interaction.response.send_message(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {pc_onetime_password}\n# 残り使用回数 | {pc_onetime_password_remaining_times}", ephemeral=True) - else: - await interaction.response.send_message("# :skull_crossbones: ワンタイムパスワードの発行に失敗しました。", ephemeral=True) + + await interaction.response.send_message(f"# :dizzy_face: PC登録時のワンタイムパスワードを発行します。\n# パスワード | {pc_onetime_password}\n# 最大使用回数 | {pc_onetime_password_max_count}\n# 残り使用回数 | {pc_onetime_password_remaining_times}", ephemeral=True) + elif pc_onetime_password_gen["result"] == 1: + if pc_onetime_password_gen["about"] == "already_exists": + pc_onetime_password = str(pc_onetime_password_gen["output_dict"]["onetime_password"]) + pc_onetime_password_max_count = str(pc_onetime_password_gen["output_dict"]["max_count"]) + pc_onetime_password_current_count = str(pc_onetime_password_gen["output_dict"]["current_count"]) + pc_onetime_password_remaining_times = str(int(pc_onetime_password_max_count) - int(pc_onetime_password_current_count)) + await interaction.response.send_message(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {pc_onetime_password}\n# 残り使用回数 | {pc_onetime_password_remaining_times}", ephemeral=True) + else: + await interaction.response.send_message("# :skull_crossbones: ワンタイムパスワードの発行に失敗しました。", ephemeral=True) @tree.command(name="devicereg", description="デバイスをDislockerに登録するためのワンタイムパスワードを発行します。") @discord.app_commands.default_permissions(administrator=True) async def devicereg(interaction: discord.Interaction, how_much: int): - max_count = how_much + if interaction.guild_id == dislocker.server_config["bot"]["server_id"] or interaction.user.id == dislocker.server_config["bot"]["admin_user_id"]: + max_count = how_much - device_onetime_password_gen = dislocker.device_onetime_gen(max_count=max_count) + device_onetime_password_gen = dislocker.device_onetime_gen(max_count=max_count) - if device_onetime_password_gen["result"] == 0: - device_onetime_password = str(device_onetime_password_gen["output_dict"]["onetime_password"]) - device_onetime_password_max_count = str(device_onetime_password_gen["output_dict"]["max_count"]) - device_onetime_password_current_count = str(device_onetime_password_gen["output_dict"]["current_count"]) - device_onetime_password_remaining_times = str(int(device_onetime_password_max_count) - int(device_onetime_password_current_count)) - - await interaction.response.send_message(f"# :dizzy_face: デバイス登録時のワンタイムパスワードを発行します。\n# パスワード | {device_onetime_password}\n# 最大使用回数 | {device_onetime_password_max_count}\n# 残り使用回数 | {device_onetime_password_remaining_times}", ephemeral=True) - elif device_onetime_password_gen["result"] == 1: - if device_onetime_password_gen["about"] == "already_exists": + if device_onetime_password_gen["result"] == 0: device_onetime_password = str(device_onetime_password_gen["output_dict"]["onetime_password"]) device_onetime_password_max_count = str(device_onetime_password_gen["output_dict"]["max_count"]) device_onetime_password_current_count = str(device_onetime_password_gen["output_dict"]["current_count"]) device_onetime_password_remaining_times = str(int(device_onetime_password_max_count) - int(device_onetime_password_current_count)) - await interaction.response.send_message(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {device_onetime_password}\n# 残り使用回数 | {device_onetime_password_remaining_times}", ephemeral=True) - else: - await interaction.response.send_message("# :skull_crossbones: ワンタイムパスワードの発行に失敗しました。", ephemeral=True) + + await interaction.response.send_message(f"# :dizzy_face: デバイス登録時のワンタイムパスワードを発行します。\n# パスワード | {device_onetime_password}\n# 最大使用回数 | {device_onetime_password_max_count}\n# 残り使用回数 | {device_onetime_password_remaining_times}", ephemeral=True) + elif device_onetime_password_gen["result"] == 1: + if device_onetime_password_gen["about"] == "already_exists": + device_onetime_password = str(device_onetime_password_gen["output_dict"]["onetime_password"]) + device_onetime_password_max_count = str(device_onetime_password_gen["output_dict"]["max_count"]) + device_onetime_password_current_count = str(device_onetime_password_gen["output_dict"]["current_count"]) + device_onetime_password_remaining_times = str(int(device_onetime_password_max_count) - int(device_onetime_password_current_count)) + await interaction.response.send_message(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {device_onetime_password}\n# 残り使用回数 | {device_onetime_password_remaining_times}", ephemeral=True) + else: + await interaction.response.send_message("# :skull_crossbones: ワンタイムパスワードの発行に失敗しました。", ephemeral=True) @tree.command(name="fstop", description="PCの使用登録を強制的に終了します。") @discord.app_commands.default_permissions(administrator=True) async def fstop(interaction: discord.Interaction, pc_number: int, about: str): - force_stop = dislocker.force_stop(pc_number=pc_number, bot_about=about) - if force_stop["result"] == 0: - await interaction.response.send_message(f":white_check_mark: PC {pc_number} の使用を強制終了しました。", ephemeral=True) - dislocker.log(title=f"[INFO] PC {pc_number} の使用を強制終了しました。", message=f"理由 | {about}", flag=0) - elif force_stop["result"] == 1: - if force_stop["about"] == "not_used": - await interaction.response.send_message(":x: そのPCは使用されていません。", ephemeral=True) - elif force_stop["about"] == "bot_about_not_found": - await interaction.response.send_message(":x: 理由が指定されていません。", ephemeral=True) - elif force_stop["about"] == "error": - await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) + if interaction.guild_id == dislocker.server_config["bot"]["server_id"] or interaction.user.id == dislocker.server_config["bot"]["admin_user_id"]: + force_stop = dislocker.force_stop(pc_number=pc_number, bot_about=about) + if force_stop["result"] == 0: + await interaction.response.send_message(f":white_check_mark: PC {pc_number} の使用を強制終了しました。", ephemeral=True) + dislocker.log(title=f"[INFO] PC {pc_number} の使用を強制終了しました。", message=f"理由 | {about}", flag=0) + elif force_stop["result"] == 1: + if force_stop["about"] == "not_used": + await interaction.response.send_message(":x: そのPCは使用されていません。", ephemeral=True) + elif force_stop["about"] == "bot_about_not_found": + await interaction.response.send_message(":x: 理由が指定されていません。", ephemeral=True) + elif force_stop["about"] == "error": + await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) @tree.command(name="report", description="PCの使用履歴をエクスポートします。") @discord.app_commands.default_permissions(administrator=True) async def report(interaction: discord.Interaction): - report_export = dislocker.report_export() - if report_export["result"] == 0: - await interaction.response.send_message(f":white_check_mark: 使用履歴のレポートです。", file=discord.File(report_export["file_path"]), ephemeral=True) - dislocker.log(title=f"[INFO] PCの使用履歴をエクスポートしました。", message=f"ファイルパス | {report_export['file_path']}", flag=0) - elif report_export["result"] == 1: - if report_export["about"] == "error": - await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) + if interaction.guild_id == dislocker.server_config["bot"]["server_id"] or interaction.user.id == dislocker.server_config["bot"]["admin_user_id"]: + report_export = dislocker.report_export() + if report_export["result"] == 0: + await interaction.response.send_message(f":white_check_mark: 使用履歴のレポートです。", file=discord.File(report_export["file_path"]), ephemeral=True) + dislocker.log(title=f"[INFO] PCの使用履歴をエクスポートしました。", message=f"ファイルパス | {report_export['file_path']}", flag=0) + elif report_export["result"] == 1: + if report_export["about"] == "error": + await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) @tree.command(name="init", description="操作チャンネルにボタン一式を送信します。") @discord.app_commands.default_permissions(administrator=True) async def button_init(interaction: discord.Interaction, text_channel: discord.TextChannel): - user_register_button_view = discord.ui.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) + if interaction.guild_id == dislocker.server_config["bot"]["server_id"] or interaction.user.id == dislocker.server_config["bot"]["admin_user_id"]: + user_register_button_view = discord.ui.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 client.get_channel(text_channel.id).send(f'# :index_pointing_at_the_viewer: ユーザー登録はお済ですか?', view=user_register_button_view) + await client.get_channel(text_channel.id).send(f'# :index_pointing_at_the_viewer: ユーザー登録はお済ですか?', view=user_register_button_view) - stop_button_view = discord.ui.View(timeout=None) - stop_button = discord.ui.Button(style=discord.ButtonStyle.danger, label="PCの使用を停止", custom_id="stop") - stop_button_view.add_item(stop_button) + stop_button_view = discord.ui.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 client.get_channel(text_channel.id).send(f'# :index_pointing_at_the_viewer: 使用を停止しますか?', view=stop_button_view) + await client.get_channel(text_channel.id).send(f'# :index_pointing_at_the_viewer: 使用を停止しますか?', view=stop_button_view) - pc_button_view = discord.ui.View(timeout=None) - for i in dislocker.pc_list: - pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"pcregister_{str(i)}") - pc_button_view.add_item(pc_register_button) + pc_button_view = discord.ui.View(timeout=None) + for i in dislocker.pc_list: + pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"pcregister_{str(i)}") + pc_button_view.add_item(pc_register_button) - await client.get_channel(text_channel.id).send(f'# :index_pointing_at_the_viewer: 使いたいPCの番号を選んでください!', view=pc_button_view) - dislocker.log(title=f"[INFO] サーバーで初回処理を実行しました。", flag=0) + await client.get_channel(text_channel.id).send(f'# :index_pointing_at_the_viewer: 使いたいPCの番号を選んでください!', view=pc_button_view) + dislocker.log(title=f"[INFO] サーバーで初回処理を実行しました。", flag=0) - await interaction.response.send_message(f":white_check_mark: ボタンを送信しました!", ephemeral=True) + await interaction.response.send_message(f":white_check_mark: ボタンを送信しました!", ephemeral=True) @tree.command(name="masterpass", description="PCのマスターパスワードを表示します。") @discord.app_commands.default_permissions(administrator=True) async def masterpass(interaction: discord.Interaction, pc_number: int): - pc_master_password_get = dislocker.show_pc_master_password(pc_number=pc_number) - - if pc_master_password_get["result"] == 0: - pc_master_password = pc_master_password_get["output_dict"]["pc_master_password"] - await interaction.response.send_message(f"# :key: PC番号 {pc_number} 番のマスターパスワードは以下の通りです。\n>>> # マスターパスワード | {pc_master_password}", ephemeral=True) - else: - await interaction.response.send_message("# :skull_crossbones: マスターパスワードの取得に失敗しました。", ephemeral=True) + if interaction.guild_id == dislocker.server_config["bot"]["server_id"] or interaction.user.id == dislocker.server_config["bot"]["admin_user_id"]: + pc_master_password_get = dislocker.show_pc_master_password(pc_number=pc_number) + + if pc_master_password_get["result"] == 0: + pc_master_password = pc_master_password_get["output_dict"]["pc_master_password"] + await interaction.response.send_message(f"# :key: PC番号 {pc_number} 番のマスターパスワードは以下の通りです。\n>>> # マスターパスワード | {pc_master_password}", ephemeral=True) + else: + await interaction.response.send_message("# :skull_crossbones: マスターパスワードの取得に失敗しました。", ephemeral=True) if dislocker.init_result == "ok": From 31fd32acc946dbd066c3afe88bab26a9bf171775 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 26 Sep 2024 22:01:05 +0900 Subject: [PATCH 109/175] =?UTF-8?q?admin=5Fuser=E3=81=A8server=5Fid?= =?UTF-8?q?=E3=82=92=E3=83=AA=E3=82=B9=E3=83=88=E3=81=AB=E3=81=97=E3=81=A6?= =?UTF-8?q?=E8=A4=87=E6=95=B0=E6=8C=87=E5=AE=9A=E3=81=A7=E3=81=8D=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index 0ccb343..dcb08fa 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -37,7 +37,7 @@ class DL(): "password": "password" }, "bot": { - "server_id": "TYPE HERE SERVER ID", + "server_id": ["TYPE HERE SERVER ID (YOU MUST USE INT !!!!)"], "token": "TYPE HERE BOTS TOKEN KEY", "activity": { "name": "Dislocker", @@ -54,7 +54,7 @@ class DL(): "fstop_time": "21:00:00" }, "preset_games": ["TEST1", "TEST2", "TEST3", "TEST4", "TEST5"], - "admin_user_id": "TYPE HERE CHANNEL ID (YOU MUST USE INT !!!!)", + "admin_user_id": ["TYPE HERE CHANNEL ID (YOU MUST USE INT !!!!)"], "debug": False } } @@ -945,7 +945,7 @@ async def on_message(message): pass elif isinstance(message.channel, discord.DMChannel): - if message.author.id == dislocker.server_config["bot"]["admin_user_id"]: + if message.author.id in dislocker.server_config["bot"]["admin_user_id"]: msg_split = message.content.split() if msg_split[0] == "/pcreg": @@ -1205,7 +1205,7 @@ async def stop(interaction: discord.Interaction): @tree.command(name="userreg", description="ユーザーを登録します。") @discord.app_commands.default_permissions(administrator=True) async def userreg(interaction: discord.Interaction, discord_user_id: str, discord_user_name: str, name: str): - if interaction.guild_id == dislocker.server_config["bot"]["server_id"] or interaction.user.id == dislocker.server_config["bot"]["admin_user_id"]: + if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: user_register = dislocker.user_register(discord_user_id=discord_user_id, discord_user_name=discord_user_name, name=name) if user_register["result"] == 0: await interaction.response.send_message(":white_check_mark: ユーザーを登録しました。", ephemeral=True) @@ -1219,7 +1219,7 @@ async def userreg(interaction: discord.Interaction, discord_user_id: str, discor @tree.command(name="pcreg", description="PCをDislockerに登録するためのワンタイムパスワードを発行します。") @discord.app_commands.default_permissions(administrator=True) async def pcreg(interaction: discord.Interaction, how_much: int = 1): - if interaction.guild_id == dislocker.server_config["bot"]["server_id"] or interaction.user.id == dislocker.server_config["bot"]["admin_user_id"]: + if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: max_count = how_much pc_onetime_password_gen = dislocker.pc_onetime_gen(max_count=max_count) @@ -1244,7 +1244,7 @@ async def pcreg(interaction: discord.Interaction, how_much: int = 1): @tree.command(name="devicereg", description="デバイスをDislockerに登録するためのワンタイムパスワードを発行します。") @discord.app_commands.default_permissions(administrator=True) async def devicereg(interaction: discord.Interaction, how_much: int): - if interaction.guild_id == dislocker.server_config["bot"]["server_id"] or interaction.user.id == dislocker.server_config["bot"]["admin_user_id"]: + if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: max_count = how_much device_onetime_password_gen = dislocker.device_onetime_gen(max_count=max_count) @@ -1269,7 +1269,7 @@ async def devicereg(interaction: discord.Interaction, how_much: int): @tree.command(name="fstop", description="PCの使用登録を強制的に終了します。") @discord.app_commands.default_permissions(administrator=True) async def fstop(interaction: discord.Interaction, pc_number: int, about: str): - if interaction.guild_id == dislocker.server_config["bot"]["server_id"] or interaction.user.id == dislocker.server_config["bot"]["admin_user_id"]: + if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: force_stop = dislocker.force_stop(pc_number=pc_number, bot_about=about) if force_stop["result"] == 0: await interaction.response.send_message(f":white_check_mark: PC {pc_number} の使用を強制終了しました。", ephemeral=True) @@ -1285,7 +1285,7 @@ async def fstop(interaction: discord.Interaction, pc_number: int, about: str): @tree.command(name="report", description="PCの使用履歴をエクスポートします。") @discord.app_commands.default_permissions(administrator=True) async def report(interaction: discord.Interaction): - if interaction.guild_id == dislocker.server_config["bot"]["server_id"] or interaction.user.id == dislocker.server_config["bot"]["admin_user_id"]: + if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: report_export = dislocker.report_export() if report_export["result"] == 0: await interaction.response.send_message(f":white_check_mark: 使用履歴のレポートです。", file=discord.File(report_export["file_path"]), ephemeral=True) @@ -1297,7 +1297,7 @@ async def report(interaction: discord.Interaction): @tree.command(name="init", description="操作チャンネルにボタン一式を送信します。") @discord.app_commands.default_permissions(administrator=True) async def button_init(interaction: discord.Interaction, text_channel: discord.TextChannel): - if interaction.guild_id == dislocker.server_config["bot"]["server_id"] or interaction.user.id == dislocker.server_config["bot"]["admin_user_id"]: + if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: user_register_button_view = discord.ui.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) @@ -1323,7 +1323,7 @@ async def button_init(interaction: discord.Interaction, text_channel: discord.Te @tree.command(name="masterpass", description="PCのマスターパスワードを表示します。") @discord.app_commands.default_permissions(administrator=True) async def masterpass(interaction: discord.Interaction, pc_number: int): - if interaction.guild_id == dislocker.server_config["bot"]["server_id"] or interaction.user.id == dislocker.server_config["bot"]["admin_user_id"]: + if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: pc_master_password_get = dislocker.show_pc_master_password(pc_number=pc_number) if pc_master_password_get["result"] == 0: From 6bf928ac1389ccd02fa308e77677e92c49398457 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 28 Sep 2024 21:52:25 +0900 Subject: [PATCH 110/175] =?UTF-8?q?=E5=90=84PC,=E3=83=87=E3=83=90=E3=82=A4?= =?UTF-8?q?=E3=82=B9=E3=81=AE=E3=83=86=E3=83=BC=E3=83=96=E3=83=AB=E3=81=AB?= =?UTF-8?q?alt=5Fname=E3=82=AB=E3=83=A9=E3=83=A0=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index dcb08fa..4b15eca 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -104,7 +104,7 @@ class DL(): 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_member_id INTEGER, password_hash VARCHAR(64), pc_uuid VARCHAR(36), pc_token VARCHAR(36), master_password VARCHAR(16), detail TEXT, PRIMARY KEY (pc_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") + cursor.execute("CREATE TABLE pc_list (pc_number INTEGER NOT NULL, using_member_id INTEGER, password_hash VARCHAR(64), pc_uuid VARCHAR(36), pc_token VARCHAR(36), master_password VARCHAR(16), detail TEXT, alt_name TEXT, PRIMARY KEY (pc_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") for i in self.pc_list: print(i) cursor.execute("INSERT INTO pc_list (pc_number) VALUES (%s)", (i,)) @@ -114,7 +114,7 @@ class DL(): find_keyboard_list_table = cursor.fetchall() print(find_keyboard_list_table) if find_keyboard_list_table[0][0] == False: - cursor.execute("CREATE TABLE keyboard_list (keyboard_number INTEGER NOT NULL, using_member_id INTEGER, device_instance_path TEXT, device_name TEXT, detail TEXT, PRIMARY KEY (keyboard_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") + cursor.execute("CREATE TABLE keyboard_list (keyboard_number INTEGER NOT NULL, using_member_id INTEGER, device_instance_path TEXT, device_name TEXT, detail TEXT, alt_name TEXT, PRIMARY KEY (keyboard_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") for i in self.keyboard_list: print(i) cursor.execute("INSERT INTO keyboard_list (keyboard_number) VALUES (%s)", (i,)) @@ -124,7 +124,7 @@ class DL(): find_mouse_list_table = cursor.fetchall() print(find_mouse_list_table) if find_mouse_list_table[0][0] == False: - cursor.execute("CREATE TABLE mouse_list (mouse_number INTEGER NOT NULL, using_member_id INTEGER, device_instance_path TEXT, device_name TEXT, detail TEXT, PRIMARY KEY (mouse_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") + cursor.execute("CREATE TABLE mouse_list (mouse_number INTEGER NOT NULL, using_member_id INTEGER, device_instance_path TEXT, device_name TEXT, detail TEXT, alt_name TEXT, PRIMARY KEY (mouse_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") for i in self.mouse_list: print(i) cursor.execute("INSERT INTO mouse_list (mouse_number) VALUES (%s)", (i,)) From cb3bc05fafe9794796aec789e59b98004600248b Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 28 Sep 2024 22:00:01 +0900 Subject: [PATCH 111/175] =?UTF-8?q?=E3=83=87=E3=83=90=E3=82=A4=E3=82=B9?= =?UTF-8?q?=E3=81=8C=E8=87=AA=E5=89=8D(0=E7=95=AA)=E3=81=AE=E6=99=82?= =?UTF-8?q?=E3=81=AB=E4=BB=96=E4=BA=BA=E3=81=8C=E4=BD=BF=E3=81=A3=E3=81=A6?= =?UTF-8?q?=E3=81=84=E3=82=8B=E5=88=A4=E5=AE=9A=E3=81=AB=E3=81=AA=E3=82=8B?= =?UTF-8?q?=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index 4b15eca..7fe8d71 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -390,12 +390,12 @@ class DL(): # PCリストの該当のレコードを更新 cursor.execute("UPDATE pc_list SET using_member_id = %s, password_hash = %s WHERE pc_number = %s", (member_id, password_hash, user_info["pc_number"])) # キーボードリストの該当のレコードを自前(None)だったらスキップ、借りていたら更新 - if user_info["keyboard_number"] == None: + if user_info["keyboard_number"] == 0: pass else: cursor.execute("UPDATE keyboard_list SET using_member_id = %s WHERE keyboard_number = %s", (member_id, user_info["keyboard_number"])) # マウスも同様に - if user_info["mouse_number"] == None: + if user_info["mouse_number"] == 0: pass else: cursor.execute("UPDATE mouse_list SET using_member_id = %s WHERE mouse_number = %s", (member_id, user_info["mouse_number"])) From ab96092b39c0e49cfeb452fec04bb1b6d1ab5e9a Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 28 Sep 2024 23:41:19 +0900 Subject: [PATCH 112/175] =?UTF-8?q?=E7=AE=A1=E7=90=86=E8=80=85=E5=90=91?= =?UTF-8?q?=E3=81=91=E3=81=AE=E3=82=B9=E3=83=A9=E3=83=83=E3=82=B7=E3=83=A5?= =?UTF-8?q?=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89=E3=81=AE=E8=BF=94=E4=BF=A1?= =?UTF-8?q?=E3=82=92embed=E3=81=B8=E7=A7=BB=E8=A1=8C,=20PC=E3=81=AE?= =?UTF-8?q?=E3=83=8B=E3=83=83=E3=82=AF=E3=83=8D=E3=83=BC=E3=83=A0=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E6=A9=9F=E8=83=BD=E8=BF=BD=E5=8A=A0,=20PC=E3=81=AE?= =?UTF-8?q?=E6=83=85=E5=A0=B1=E5=8F=96=E5=BE=97=E6=A9=9F=E8=83=BD=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 280 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 234 insertions(+), 46 deletions(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index 7fe8d71..7969d37 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -217,7 +217,7 @@ class DL(): except Exception as error: self.log(title=f"[ERROR] ユーザーの登録状態を調査中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} finally: cursor.close() @@ -285,7 +285,7 @@ class DL(): except Exception as error: self.log(title=f"[ERROR] PCの使用状況を調査中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} finally: if cursor: @@ -308,7 +308,7 @@ class DL(): except Exception as error: self.log(title=f"[ERROR] キーボードの使用状況を調査中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} finally: if cursor: @@ -331,7 +331,7 @@ class DL(): except Exception as error: self.log(title=f"[ERROR] マウスの使用状況を調査中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} finally: if cursor: @@ -413,7 +413,7 @@ class DL(): return {"result": 1, "about": "user_data_not_found"} except Exception as error: self.log(title=f"[ERROR] PCの使用登録中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} finally: if cursor: cursor.close() @@ -474,7 +474,7 @@ class DL(): except Exception as error: self.log(title=f"[ERROR] PCの使用停止処理中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} finally: if cursor: @@ -497,7 +497,7 @@ class DL(): except Exception as error: self.log(title=f"[ERROR] ユーザー情報の登録中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} finally: if cursor: @@ -524,7 +524,7 @@ class DL(): except Exception as error: self.log(title=f"[ERROR] PCの情報を登録中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} finally: if cursor: @@ -595,7 +595,7 @@ class DL(): except Exception as error: self.log(title=f"[ERROR] PCの使用履歴をエクスポート中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} finally: if cursor: @@ -643,7 +643,7 @@ class DL(): except Exception as error: self.log(title=f"[ERROR] fstop中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} finally: if cursor: @@ -698,7 +698,7 @@ class DL(): except Exception as error: self.log(title=f"[ERROR] PC登録用のワンタイムパスワード発行中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} def device_onetime_gen(self, **kwargs): @@ -750,11 +750,11 @@ class DL(): except Exception as error: self.log(title=f"[ERROR] デバイス登録用のワンタイムパスワード発行中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} def show_pc_master_password(self, **kwargs): - if isinstance(kwargs.get("pc_number"), int): - try: + try: + if isinstance(kwargs.get("pc_number"), int): pc_number = int(kwargs.get("pc_number")) cursor = self.db.cursor() @@ -763,11 +763,81 @@ class DL(): pc_master_password = pc_master_password_list[0][0] return {"result": 0, "about": "ok", "output_dict": {"pc_master_password": pc_master_password}} - except Exception as error: + + else: + return {"result": 1, "about": "syntax_error"} + except Exception as error: self.log(title=f"[ERROR] PCのマスターパスワードを取得中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} - else: - return {"result": 1, "about": "syntax_error"} + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} + + finally: + if cursor: + cursor.close() + + def get_discord_user_id(self, **kwargs): + try: + member_id = int(kwargs["member_id"]) + cursor = self.db.cursor() + cursor.execute("SELECT discord_user_id FROM club_member WHERE member_id = %s", (member_id,)) + discord_user_id_list = cursor.fetchall() + discord_user_id = discord_user_id_list[0][0] + return {"result": 0, "about": "ok", "discord_user_id": discord_user_id} + + except Exception as error: + self.log(title=f"[ERROR] DiscordのユーザーIDの取得中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} + + finally: + if cursor: + cursor.close() + + def get_pc_list(self, **kwargs): + try: + cursor = self.db.cursor() + + if "pc_number" in kwargs: + pc_number = int(kwargs["pc_number"]) + cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s", (pc_number,)) + pc_list = cursor.fetchall() + + return {"result": 0, "about": "ok", "output_dict": {pc_number: {"pc_number": pc_list[0][0], "using_member_id": pc_list[0][1], "master_password": [0][5], "detail": pc_list[0][6], "alt_name": pc_list[0][7]}}} + else: + cursor.execute("SELECT * FROM pc_list ORDER BY pc_number") + pc_list = cursor.fetchall() + pc_list = {} + for i in pc_list: + pc_list[i[0]] = {"pc_number": i[0], "using_member_id": i[1], "master_password": i[5], "detail": i[6], "alt_name": i[7]} + + return {"result": 0, "about": "ok", "output_dict": pc_list} + + except Exception as error: + self.log(title=f"[ERROR] PCリストの取得中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} + + finally: + if cursor: + cursor.close() + + def set_pc_nickname(self, **kwargs): + try: + cursor = self.db.cursor() + + if 'pc_number' in kwargs and 'alt_name' in kwargs: + pc_number = int(kwargs["pc_number"]) + alt_name = kwargs["alt_name"] + cursor.execute("UPDATE pc_list SET alt_name = %s WHERE pc_number = %s", (alt_name, pc_number)) + self.db.commit() + return {"result": 0, "about": "ok"} + else: + return {"result": 1, "about": "syntax_error"} + + except Exception as error: + self.log(title=f"[ERROR] PCのニックネームの設定中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} + + finally: + if cursor: + cursor.close() class ReasonModal(discord.ui.Modal): @@ -953,7 +1023,7 @@ async def on_message(message): if len(msg_split) == 2: if msg_split[1].isdecimal(): max_count = int(msg_split[1]) - + pc_onetime_password_gen = dislocker.pc_onetime_gen(max_count=max_count) if pc_onetime_password_gen["result"] == 0: @@ -962,16 +1032,28 @@ async def on_message(message): pc_onetime_password_current_count = str(pc_onetime_password_gen["output_dict"]["current_count"]) pc_onetime_password_remaining_times = str(int(pc_onetime_password_max_count) - int(pc_onetime_password_current_count)) - await message.channel.send(f"# :dizzy_face: PC登録時のワンタイムパスワードを発行します。\n# パスワード | {pc_onetime_password}\n# 最大使用回数 | {pc_onetime_password_max_count}\n# 残り使用回数 | {pc_onetime_password_remaining_times}") + result_embed = discord.Embed(title=":dizzy_face: PCの登録", description=f"PC登録時のワンタイムパスワードを発行します。", color=0x2286C9) + result_embed.add_field(name="パスワード", value=pc_onetime_password) + result_embed.add_field(name="最大使用回数", value=pc_onetime_password_max_count) + result_embed.add_field(name="残り使用回数", value=pc_onetime_password_remaining_times) + elif pc_onetime_password_gen["result"] == 1: if pc_onetime_password_gen["about"] == "already_exists": pc_onetime_password = str(pc_onetime_password_gen["output_dict"]["onetime_password"]) pc_onetime_password_max_count = str(pc_onetime_password_gen["output_dict"]["max_count"]) pc_onetime_password_current_count = str(pc_onetime_password_gen["output_dict"]["current_count"]) pc_onetime_password_remaining_times = str(int(pc_onetime_password_max_count) - int(pc_onetime_password_current_count)) - await message.channel.send(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {pc_onetime_password}\n# 残り使用回数 | {pc_onetime_password_remaining_times}") + + result_embed = discord.Embed(title=":dizzy_face: PCの登録", description=f"ワンタイムパスワードはもう発行されており、有効です。", color=0x2286C9) + result_embed.add_field(name="パスワード", value=pc_onetime_password) + result_embed.add_field(name="最大使用回数", value=pc_onetime_password_max_count) + result_embed.add_field(name="残り使用回数", value=pc_onetime_password_remaining_times) + else: - await message.channel.send("# :skull_crossbones: ワンタイムパスワードの発行に失敗しました。") + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。', color=0xC91111) + result_embed.add_field(name=f"{pc_onetime_password_gen['output_dict']['error_class_name']}", value=f"{pc_onetime_password_gen['output_dict']['error_args']}") + + await message.channel.send(embed=result_embed) elif msg_split[0] == "/devreg": max_count = 1 @@ -987,19 +1069,31 @@ async def on_message(message): device_onetime_password_current_count = str(device_onetime_password_gen["output_dict"]["current_count"]) device_onetime_password_remaining_times = str(int(device_onetime_password_max_count) - int(device_onetime_password_current_count)) - await message.channel.send(f"# :dizzy_face: デバイス登録時のワンタイムパスワードを発行します。\n# パスワード | {device_onetime_password}\n# 最大使用回数 | {device_onetime_password_max_count}\n# 残り使用回数 | {device_onetime_password_remaining_times}") + result_embed = discord.Embed(title=":dizzy_face: デバイスの登録", description=f"デバイス登録時のワンタイムパスワードを発行します。", color=0x2286C9) + result_embed.add_field(name="パスワード", value=device_onetime_password) + result_embed.add_field(name="最大使用回数", value=device_onetime_password_max_count) + result_embed.add_field(name="残り使用回数", value=device_onetime_password_remaining_times) + elif device_onetime_password_gen["result"] == 1: if device_onetime_password_gen["about"] == "already_exists": device_onetime_password = str(device_onetime_password_gen["output_dict"]["onetime_password"]) device_onetime_password_max_count = str(device_onetime_password_gen["output_dict"]["max_count"]) device_onetime_password_current_count = str(device_onetime_password_gen["output_dict"]["current_count"]) device_onetime_password_remaining_times = str(int(device_onetime_password_max_count) - int(device_onetime_password_current_count)) - await message.channel.send(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {device_onetime_password}\n# 残り使用回数 | {device_onetime_password_remaining_times}") + result_embed = discord.Embed(title=":dizzy_face: デバイスの登録", description=f"ワンタイムパスワードはもう発行されており、有効です。", color=0x2286C9) + result_embed.add_field(name="パスワード", value=device_onetime_password) + result_embed.add_field(name="最大使用回数", value=device_onetime_password_max_count) + result_embed.add_field(name="残り使用回数", value=device_onetime_password_remaining_times) + else: - await message.channel.send("# :skull_crossbones: ワンタイムパスワードの発行に失敗しました。") + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。', color=0xC91111) + result_embed.add_field(name=f"{device_onetime_password_gen['output_dict']['error_class_name']}", value=f"{device_onetime_password_gen['output_dict']['error_args']}") + + await message.channel.send(embed=result_embed) else: - await message.channel.send("# :warning: DMでの応答は、現在無効化されています。") + result_embed = discord.Embed(title=":x: 警告", description=f'DMでの操作はサポートされていません。', color=0xC91111) + await message.channel.send(embed=result_embed) else: pass @@ -1191,15 +1285,23 @@ async def stop(interaction: discord.Interaction): stop = dislocker.stop(discord_user_id=interaction.user.id) if stop["result"] == 0: await interaction.response.send_message(f":white_check_mark: 使用が終了されました。\n>>> ## PC番号 | {stop['output_dict']['pc_number']}", ephemeral=True) + result_embed = discord.Embed(title=":white_check_mark: 使用停止処理は完了しました。", description=f'PC番号 {stop['output_dict']['pc_number']} 番の使用停止処理が完了しました。', color=0x56FF01) dislocker.log(title=f"[INFO] PC番号{stop['output_dict']['pc_number']} の使用が終了されました。", message=f"名前 | {stop['output_dict']['name']}", flag=0) - await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':white_check_mark: {stop["output_dict"]["name"]} さんがPC {stop["output_dict"]["pc_number"]} の使用を終了しました。\n>>> ## PC番号 | {stop["output_dict"]["pc_number"]}') + log_embed = discord.Embed(title=f":information_source: PC番号 {stop['output_dict']['pc_number']} の使用は終了されました。", description=f"<@{interaction.user.id}> によるリクエスト", color=0x2286C9) + await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(embed=log_embed) + elif stop["result"] == 1: if stop["about"] == "unused": - await interaction.response.send_message("# :shaking_face: あなたはPCを使用されていないようです...", ephemeral=True) + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'あなたはPCを使用されていないようです...', color=0xC91111) + elif stop["about"] == "user_data_not_found": - await interaction.response.send_message("# :dizzy_face: ユーザーとして登録されていないようです。\n最初にサーバーで登録を行ってください。", ephemeral=True) + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'Dislockerのユーザーとして登録されていないようです。\n登録を行ってから、またお試しください。', color=0xC91111) + elif stop["about"] == "error": - await interaction.response.send_message("# :skull_crossbones: 停止できませんでした。\n内部エラーが発生しています。", ephemeral=True) + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。', color=0xC91111) + result_embed.add_field(name=f"{stop['output_dict']['error_class_name']}", value=f"{stop['output_dict']['error_args']}") + + await interaction.response.send_message(embed=result_embed, ephemeral=True) #管理者側のスラッシュコマンド @tree.command(name="userreg", description="ユーザーを登録します。") @@ -1208,13 +1310,18 @@ async def userreg(interaction: discord.Interaction, discord_user_id: str, discor if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: user_register = dislocker.user_register(discord_user_id=discord_user_id, discord_user_name=discord_user_name, name=name) if user_register["result"] == 0: - await interaction.response.send_message(":white_check_mark: ユーザーを登録しました。", ephemeral=True) + result_embed = discord.Embed(title=":white_check_mark: ユーザー登録が完了しました。", description=f'続いて、PCの使用登録を行いましょう!', color=0x56FF01) dislocker.log(title=f"[INFO] ユーザーを登録しました。", message=f"名前 | {name}, Discordユーザー名 | {discord_user_name}, DiscordユーザーID | {discord_user_id}", flag=0) + elif user_register["result"] == 1: if user_register["about"] == "already_exists": - await interaction.response.send_message(":x: 既に登録されているユーザーです。", ephemeral=True) + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'既に登録されているユーザーです。', color=0xC91111) + elif user_register["about"] == "error": - await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。', color=0xC91111) + result_embed.add_field(name=f"{user_register['output_dict']['error_class_name']}", value=f"{user_register['output_dict']['error_args']}") + + await interaction.response.send_message(embed=result_embed, ephemeral=True) @tree.command(name="pcreg", description="PCをDislockerに登録するためのワンタイムパスワードを発行します。") @discord.app_commands.default_permissions(administrator=True) @@ -1230,16 +1337,28 @@ async def pcreg(interaction: discord.Interaction, how_much: int = 1): pc_onetime_password_current_count = str(pc_onetime_password_gen["output_dict"]["current_count"]) pc_onetime_password_remaining_times = str(int(pc_onetime_password_max_count) - int(pc_onetime_password_current_count)) - await interaction.response.send_message(f"# :dizzy_face: PC登録時のワンタイムパスワードを発行します。\n# パスワード | {pc_onetime_password}\n# 最大使用回数 | {pc_onetime_password_max_count}\n# 残り使用回数 | {pc_onetime_password_remaining_times}", ephemeral=True) + result_embed = discord.Embed(title=":dizzy_face: PCの登録", description=f"PC登録時のワンタイムパスワードを発行します。", color=0x2286C9) + result_embed.add_field(name="パスワード", value=pc_onetime_password) + result_embed.add_field(name="最大使用回数", value=pc_onetime_password_max_count) + result_embed.add_field(name="残り使用回数", value=pc_onetime_password_remaining_times) + elif pc_onetime_password_gen["result"] == 1: if pc_onetime_password_gen["about"] == "already_exists": pc_onetime_password = str(pc_onetime_password_gen["output_dict"]["onetime_password"]) pc_onetime_password_max_count = str(pc_onetime_password_gen["output_dict"]["max_count"]) pc_onetime_password_current_count = str(pc_onetime_password_gen["output_dict"]["current_count"]) pc_onetime_password_remaining_times = str(int(pc_onetime_password_max_count) - int(pc_onetime_password_current_count)) - await interaction.response.send_message(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {pc_onetime_password}\n# 残り使用回数 | {pc_onetime_password_remaining_times}", ephemeral=True) + + result_embed = discord.Embed(title=":dizzy_face: PCの登録", description=f"ワンタイムパスワードはもう発行されており、有効です。", color=0x2286C9) + result_embed.add_field(name="パスワード", value=pc_onetime_password) + result_embed.add_field(name="最大使用回数", value=pc_onetime_password_max_count) + result_embed.add_field(name="残り使用回数", value=pc_onetime_password_remaining_times) + else: - await interaction.response.send_message("# :skull_crossbones: ワンタイムパスワードの発行に失敗しました。", ephemeral=True) + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。', color=0xC91111) + result_embed.add_field(name=f"{pc_onetime_password_gen['output_dict']['error_class_name']}", value=f"{pc_onetime_password_gen['output_dict']['error_args']}") + + await interaction.response.send_message(embed=result_embed, ephemeral=True) @tree.command(name="devicereg", description="デバイスをDislockerに登録するためのワンタイムパスワードを発行します。") @discord.app_commands.default_permissions(administrator=True) @@ -1255,16 +1374,28 @@ async def devicereg(interaction: discord.Interaction, how_much: int): device_onetime_password_current_count = str(device_onetime_password_gen["output_dict"]["current_count"]) device_onetime_password_remaining_times = str(int(device_onetime_password_max_count) - int(device_onetime_password_current_count)) - await interaction.response.send_message(f"# :dizzy_face: デバイス登録時のワンタイムパスワードを発行します。\n# パスワード | {device_onetime_password}\n# 最大使用回数 | {device_onetime_password_max_count}\n# 残り使用回数 | {device_onetime_password_remaining_times}", ephemeral=True) + result_embed = discord.Embed(title=":dizzy_face: デバイスの登録", description=f"デバイス登録時のワンタイムパスワードを発行します。", color=0x2286C9) + result_embed.add_field(name="パスワード", value=device_onetime_password) + result_embed.add_field(name="最大使用回数", value=device_onetime_password_max_count) + result_embed.add_field(name="残り使用回数", value=device_onetime_password_remaining_times) + elif device_onetime_password_gen["result"] == 1: if device_onetime_password_gen["about"] == "already_exists": device_onetime_password = str(device_onetime_password_gen["output_dict"]["onetime_password"]) device_onetime_password_max_count = str(device_onetime_password_gen["output_dict"]["max_count"]) device_onetime_password_current_count = str(device_onetime_password_gen["output_dict"]["current_count"]) device_onetime_password_remaining_times = str(int(device_onetime_password_max_count) - int(device_onetime_password_current_count)) - await interaction.response.send_message(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {device_onetime_password}\n# 残り使用回数 | {device_onetime_password_remaining_times}", ephemeral=True) + result_embed = discord.Embed(title=":dizzy_face: デバイスの登録", description=f"ワンタイムパスワードはもう発行されており、有効です。", color=0x2286C9) + result_embed.add_field(name="パスワード", value=device_onetime_password) + result_embed.add_field(name="最大使用回数", value=device_onetime_password_max_count) + result_embed.add_field(name="残り使用回数", value=device_onetime_password_remaining_times) + else: - await interaction.response.send_message("# :skull_crossbones: ワンタイムパスワードの発行に失敗しました。", ephemeral=True) + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。', color=0xC91111) + result_embed.add_field(name=f"{device_onetime_password_gen['output_dict']['error_class_name']}", value=f"{device_onetime_password_gen['output_dict']['error_args']}") + + await interaction.response.send_message(embed=result_embed, ephemeral=True) + @tree.command(name="fstop", description="PCの使用登録を強制的に終了します。") @discord.app_commands.default_permissions(administrator=True) @@ -1272,15 +1403,21 @@ async def fstop(interaction: discord.Interaction, pc_number: int, about: str): if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: force_stop = dislocker.force_stop(pc_number=pc_number, bot_about=about) if force_stop["result"] == 0: - await interaction.response.send_message(f":white_check_mark: PC {pc_number} の使用を強制終了しました。", ephemeral=True) + result_embed = discord.Embed(title=":white_check_mark: 処理が完了しました。", description=f'PC番号 {str(pc_number)} 番の使用登録は抹消されました。', color=0x56FF01) + dislocker.log(title=f"[INFO] PC {pc_number} の使用を強制終了しました。", message=f"理由 | {about}", flag=0) elif force_stop["result"] == 1: if force_stop["about"] == "not_used": - await interaction.response.send_message(":x: そのPCは使用されていません。", ephemeral=True) + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'指定されたPCは使用されていないようです...', color=0xC91111) + elif force_stop["about"] == "bot_about_not_found": - await interaction.response.send_message(":x: 理由が指定されていません。", ephemeral=True) + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'強制停止する理由を入力してください。', color=0xC91111) + elif force_stop["about"] == "error": - await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。', color=0xC91111) + result_embed.add_field(name=f"{force_stop['output_dict']['error_class_name']}", value=f"{force_stop['output_dict']['error_args']}") + + await interaction.response.send_message(embed=result_embed, ephemeral=True) @tree.command(name="report", description="PCの使用履歴をエクスポートします。") @discord.app_commands.default_permissions(administrator=True) @@ -1318,7 +1455,9 @@ async def button_init(interaction: discord.Interaction, text_channel: discord.Te await client.get_channel(text_channel.id).send(f'# :index_pointing_at_the_viewer: 使いたいPCの番号を選んでください!', view=pc_button_view) dislocker.log(title=f"[INFO] サーバーで初回処理を実行しました。", flag=0) - await interaction.response.send_message(f":white_check_mark: ボタンを送信しました!", ephemeral=True) + result_embed = discord.Embed(title=":white_check_mark: 初回処理が完了しました。", description=f'指定したテキストチャンネルをご確認ください。', color=0x56FF01) + + await interaction.response.send_message(embed=result_embed, ephemeral=True) @tree.command(name="masterpass", description="PCのマスターパスワードを表示します。") @discord.app_commands.default_permissions(administrator=True) @@ -1328,9 +1467,58 @@ async def masterpass(interaction: discord.Interaction, pc_number: int): if pc_master_password_get["result"] == 0: pc_master_password = pc_master_password_get["output_dict"]["pc_master_password"] - await interaction.response.send_message(f"# :key: PC番号 {pc_number} 番のマスターパスワードは以下の通りです。\n>>> # マスターパスワード | {pc_master_password}", ephemeral=True) + + result_embed = discord.Embed(title=":information_source: マスターパスワード", description=f"PC番号 {str(pc_number)} 番のマスターパスワードを表示します。", color=0x2286C9) + result_embed.add_field(name=f"マスターパスワード", value=f"{str(pc_master_password)}") + else: - await interaction.response.send_message("# :skull_crossbones: マスターパスワードの取得に失敗しました。", ephemeral=True) + result_embed = discord.Embed(title=":x: 取得に失敗しました。", description=f'サーバーでエラーが発生しています。', color=0xC91111) + result_embed.add_field(name=f"{pc_master_password_get['output_dict']['error_class_name']}", value=f"{pc_master_password_get['output_dict']['error_args']}") + + await interaction.response.send_message(embed=result_embed, ephemeral=True) + +@tree.command(name="pcinfo", description="PCの情報を表示します。") +@discord.app_commands.default_permissions(administrator=True) +async def pcinfo(interaction: discord.Interaction): + if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: + pc_list = dislocker.get_pc_list() + + if pc_list["result"] == 0: + result_embed = discord.Embed(title=":information_source: 現在のPCリスト", description="PCリストです。", color=0x2286C9) + + for i in pc_list['output_dict'].keys(): + if i['alt_name'] == None: + pc_name_title = f'{i['pc_number']} 番' + else: + pc_name_title = f'{i['pc_number']} 番 ({i['alt_name']})' + + if i['using_member_id'] == None: + pc_using_value = f'未使用' + else: + discord_user_id = dislocker.get_discord_user_id(i['using_member_id']) + pc_using_value = f'<@{discord_user_id}> が使用中' + + result_embed.add_field(name=f'{pc_name_title}', value=f'{pc_using_value}') + + else: + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。', color=0xC91111) + result_embed.add_field(name=f"{pc_list['output_dict']['error_class_name']}", value=f"{pc_list['output_dict']['error_args']}") + + await interaction.response.send_message(embed=result_embed, ephemeral=True) + +@tree.command(name="pcnickname", description="PCにニックネームを設定します。") +@discord.app_commands.default_permissions(administrator=True) +async def pcnickname(interaction: discord.Interaction, pc_number: int, nickname: str): + if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: + pc_nickname_set = dislocker.set_pc_nickname(pc_number=pc_number, nickname=nickname) + if pc_nickname_set["result"] == 0: + result_embed = discord.Embed(title=":white_check_mark: 操作が完了しました。", description=f'PC番号 {str(pc_number)} のニックネームは {str(nickname)} に設定されました。', color=0x56FF01) + + else: + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。ニックネームは変更されません。', color=0xC91111) + result_embed.add_field(name=f"{pc_nickname_set['output_dict']['error_class_name']}", value=f"{pc_nickname_set['output_dict']['error_args']}") + + await interaction.response.send_message(embed=result_embed, ephemeral=True) if dislocker.init_result == "ok": From 5f175064477f95d24f127a0b9716525d64e01c89 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 00:08:56 +0900 Subject: [PATCH 113/175] =?UTF-8?q?PC,=E3=83=87=E3=83=90=E3=82=A4=E3=82=B9?= =?UTF-8?q?=E3=81=AE=E7=99=BB=E9=8C=B2=E3=83=9C=E3=82=BF=E3=83=B3=E3=81=8C?= =?UTF-8?q?db=E3=81=A8=E9=80=A3=E5=8B=95=E3=81=99=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 101 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 86 insertions(+), 15 deletions(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index 7969d37..941bc21 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -804,11 +804,71 @@ class DL(): else: cursor.execute("SELECT * FROM pc_list ORDER BY pc_number") pc_list = cursor.fetchall() - pc_list = {} + pc_list_base = {} for i in pc_list: - pc_list[i[0]] = {"pc_number": i[0], "using_member_id": i[1], "master_password": i[5], "detail": i[6], "alt_name": i[7]} + pc_list_base[i[0]] = {"pc_number": i[0], "using_member_id": i[1], "master_password": i[5], "detail": i[6], "alt_name": i[7]} - return {"result": 0, "about": "ok", "output_dict": pc_list} + return {"result": 0, "about": "ok", "output_dict": pc_list_base} + + except Exception as error: + self.log(title=f"[ERROR] PCリストの取得中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} + + finally: + if cursor: + cursor.close() + + def get_keyboard_list(self, **kwargs): + try: + cursor = self.db.cursor() + + if "keyboard_number" in kwargs: + keyboard_number = int(kwargs["keyboard_number"]) + cursor.execute("SELECT * FROM keyboard_list WHERE keyboard_number = %s", (keyboard_number,)) + keyboard_list = cursor.fetchall() + + return {"result": 0, "about": "ok", "output_dict": {keyboard_number: {"keyboard_number": keyboard_list[0][0], "using_member_id": keyboard_list[0][1], "device_instance_path": keyboard_list[0][2], "device_name": keyboard_list[0][3], "detail": keyboard_list[0][4], "alt_name": keyboard_list[0][5]}}} + else: + cursor.execute("SELECT * FROM keyboard_list ORDER BY keyboard_number") + keyboard_list = cursor.fetchall() + keyboard_list_base = {} + for i in keyboard_list: + if i['keyboard_number'] == 0: + pass + else: + keyboard_list_base[i[0]] = {"keyboard_number": i[0], "using_member_id": i[1], "device_instance_path": i[2], "device_name": i[3], "detail": i[4], "alt_name": i[5]} + + return {"result": 0, "about": "ok", "output_dict": keyboard_list_base} + + except Exception as error: + self.log(title=f"[ERROR] PCリストの取得中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} + + finally: + if cursor: + cursor.close() + + def get_mouse_list(self, **kwargs): + try: + cursor = self.db.cursor() + + if "mouse_number" in kwargs: + mouse_number = int(kwargs["mouse_number"]) + cursor.execute("SELECT * FROM mouse_list WHERE mouse_number = %s", (mouse_number,)) + mouse_list = cursor.fetchall() + + return {"result": 0, "about": "ok", "output_dict": {mouse_number: {"mouse_number": mouse_list[0][0], "using_member_id": mouse_list[0][1], "device_instance_path": mouse_list[0][2], "device_name": mouse_list[0][3], "detail": mouse_list[0][4], "alt_name": mouse_list[0][5]}}} + else: + cursor.execute("SELECT * FROM mouse_list ORDER BY mouse_number") + mouse_list = cursor.fetchall() + mouse_list_base = {} + for i in mouse_list: + if i['mouse_number'] == 0: + pass + else: + mouse_list_base[i[0]] = {"keyboard_number": i[0], "using_member_id": i[1], "device_instance_path": i[2], "device_name": i[3], "detail": i[4], "alt_name": i[5]} + + return {"result": 0, "about": "ok", "output_dict": mouse_list_base} except Exception as error: self.log(title=f"[ERROR] PCリストの取得中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) @@ -1113,12 +1173,15 @@ async def on_button(interaction: discord.Interaction): if custom_id_split[0] == "pcregister": keyboard_register_view = discord.ui.View(timeout=15) pc_number = custom_id_split[1] - for i in dislocker.keyboard_list: - if i == 0: - pass + keyboard_list = dislocker.get_keyboard_list() + + for i in keyboard_list["output_dict"].keys(): + if i['alt_name'] == None: + keyboard_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i['keyboard_number'])} 番", custom_id=f"keyboardregister_{str(pc_number)}_{str(i['keyboard_number'])}") else: - keyboard_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"keyboardregister_{str(pc_number)}_{str(i)}") - keyboard_register_view.add_item(keyboard_register_button) + keyboard_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i['keyboard_number'])} 番 | ({i['alt_name']})", custom_id=f"keyboardregister_{str(pc_number)}_{str(i['keyboard_number'])}") + keyboard_register_view.add_item(keyboard_register_button) + keyboard_not_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="キーボードは自前", custom_id=f"keyboardregister_{str(pc_number)}_own") keyboard_register_view.add_item(keyboard_not_register_button) @@ -1128,16 +1191,19 @@ async def on_button(interaction: discord.Interaction): mouse_register_view = discord.ui.View(timeout=15) pc_number = custom_id_split[1] keyboard_number = custom_id_split[2] + mouse_list = dislocker.get_mouse_list() if keyboard_number == "own": keyboard_number_show = "自前" else: keyboard_number_show = keyboard_number - for i in dislocker.mouse_list: - if i == 0: - pass + + for i in mouse_list["output_dict"].keys(): + if i['alt_name'] == None: + mouse_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i['mouse_number'])} 番", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_{str(i['mouse_number'])}") else: - mouse_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_{str(i)}") - mouse_register_view.add_item(mouse_register_button) + mouse_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i['mouse_number'])} 番 | ({i['alt_name']})", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_{str(i['mouse_number'])}") + mouse_register_view.add_item(mouse_register_button) + mouse_not_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="マウスは自前", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_own") mouse_register_view.add_item(mouse_not_register_button) @@ -1435,6 +1501,8 @@ async def report(interaction: discord.Interaction): @discord.app_commands.default_permissions(administrator=True) async def button_init(interaction: discord.Interaction, text_channel: discord.TextChannel): if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: + pc_list = dislocker.get_pc_list() + user_register_button_view = discord.ui.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) @@ -1448,8 +1516,11 @@ async def button_init(interaction: discord.Interaction, text_channel: discord.Te await client.get_channel(text_channel.id).send(f'# :index_pointing_at_the_viewer: 使用を停止しますか?', view=stop_button_view) pc_button_view = discord.ui.View(timeout=None) - for i in dislocker.pc_list: - pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"pcregister_{str(i)}") + for i in pc_list["output_dict"].keys(): + if i['alt_name'] == None: + pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i['pc_number'])} 番", custom_id=f"pcregister_{str(i['pc_number'])}") + else: + pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i['pc_number'])} 番 | ({i['alt_name']})", custom_id=f"pcregister_{str(i['pc_number'])}") pc_button_view.add_item(pc_register_button) await client.get_channel(text_channel.id).send(f'# :index_pointing_at_the_viewer: 使いたいPCの番号を選んでください!', view=pc_button_view) From c82351e1868576300bffdf64a14046be91213ec9 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 00:18:30 +0900 Subject: [PATCH 114/175] =?UTF-8?q?=E3=83=9C=E3=82=BF=E3=83=B3=E3=82=92?= =?UTF-8?q?=E5=91=BC=E3=81=B3=E5=87=BA=E3=81=9B=E3=81=AA=E3=81=84=E3=83=90?= =?UTF-8?q?=E3=82=B0=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index 941bc21..13680cf 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -1176,10 +1176,11 @@ async def on_button(interaction: discord.Interaction): keyboard_list = dislocker.get_keyboard_list() for i in keyboard_list["output_dict"].keys(): - if i['alt_name'] == None: - keyboard_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i['keyboard_number'])} 番", custom_id=f"keyboardregister_{str(pc_number)}_{str(i['keyboard_number'])}") + current_keyboard_list = keyboard_list['output_dict'][i] + if current_keyboard_list['alt_name'] == None: + keyboard_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(current_keyboard_list['keyboard_number'])} 番", custom_id=f"keyboardregister_{str(pc_number)}_{str(current_keyboard_list['keyboard_number'])}") else: - keyboard_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i['keyboard_number'])} 番 | ({i['alt_name']})", custom_id=f"keyboardregister_{str(pc_number)}_{str(i['keyboard_number'])}") + keyboard_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(current_keyboard_list['keyboard_number'])} 番 | ({current_keyboard_list['alt_name']})", custom_id=f"keyboardregister_{str(pc_number)}_{str(current_keyboard_list['keyboard_number'])}") keyboard_register_view.add_item(keyboard_register_button) keyboard_not_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="キーボードは自前", custom_id=f"keyboardregister_{str(pc_number)}_own") @@ -1198,10 +1199,11 @@ async def on_button(interaction: discord.Interaction): keyboard_number_show = keyboard_number for i in mouse_list["output_dict"].keys(): + current_mouse_list = mouse_list['output_dict'][i] if i['alt_name'] == None: - mouse_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i['mouse_number'])} 番", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_{str(i['mouse_number'])}") + mouse_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(current_mouse_list['mouse_number'])} 番", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_{str(current_mouse_list['mouse_number'])}") else: - mouse_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i['mouse_number'])} 番 | ({i['alt_name']})", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_{str(i['mouse_number'])}") + mouse_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(current_mouse_list['mouse_number'])} 番 | ({current_mouse_list['alt_name']})", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_{str(current_mouse_list['mouse_number'])}") mouse_register_view.add_item(mouse_register_button) mouse_not_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="マウスは自前", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_own") @@ -1517,10 +1519,11 @@ async def button_init(interaction: discord.Interaction, text_channel: discord.Te pc_button_view = discord.ui.View(timeout=None) for i in pc_list["output_dict"].keys(): - if i['alt_name'] == None: - pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i['pc_number'])} 番", custom_id=f"pcregister_{str(i['pc_number'])}") + current_pc_list = pc_list['output_dict'][i] + if current_pc_list['alt_name'] == None: + pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(current_pc_list['pc_number'])} 番", custom_id=f"pcregister_{str(current_pc_list['pc_number'])}") else: - pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i['pc_number'])} 番 | ({i['alt_name']})", custom_id=f"pcregister_{str(i['pc_number'])}") + pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(current_pc_list['pc_number'])} 番 | ({current_pc_list['alt_name']})", custom_id=f"pcregister_{str(current_pc_list['pc_number'])}") pc_button_view.add_item(pc_register_button) await client.get_channel(text_channel.id).send(f'# :index_pointing_at_the_viewer: 使いたいPCの番号を選んでください!', view=pc_button_view) @@ -1558,15 +1561,16 @@ async def pcinfo(interaction: discord.Interaction): result_embed = discord.Embed(title=":information_source: 現在のPCリスト", description="PCリストです。", color=0x2286C9) for i in pc_list['output_dict'].keys(): - if i['alt_name'] == None: - pc_name_title = f'{i['pc_number']} 番' + current_pc_list = pc_list['output_dict'][i] + if current_pc_list['alt_name'] == None: + pc_name_title = f'{current_pc_list['pc_number']} 番' else: - pc_name_title = f'{i['pc_number']} 番 ({i['alt_name']})' + pc_name_title = f'{current_pc_list['pc_number']} 番 ({current_pc_list['alt_name']})' - if i['using_member_id'] == None: + if current_pc_list['using_member_id'] == None: pc_using_value = f'未使用' else: - discord_user_id = dislocker.get_discord_user_id(i['using_member_id']) + discord_user_id = dislocker.get_discord_user_id(current_pc_list['using_member_id']) pc_using_value = f'<@{discord_user_id}> が使用中' result_embed.add_field(name=f'{pc_name_title}', value=f'{pc_using_value}') From dfb21e3a257780c4b1e94f921ef0d7ab11617aec Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 00:24:15 +0900 Subject: [PATCH 115/175] =?UTF-8?q?=E3=82=AD=E3=83=BC=E3=83=9C=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=80=81=E3=83=9E=E3=82=A6=E3=82=B9=E3=81=AE=E3=83=9C?= =?UTF-8?q?=E3=82=BF=E3=83=B3=E3=81=8C=E5=91=BC=E3=81=B3=E5=87=BA=E3=81=9B?= =?UTF-8?q?=E3=81=AA=E3=81=84=E3=83=90=E3=82=B0=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index 13680cf..c0e53fa 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -833,7 +833,7 @@ class DL(): keyboard_list = cursor.fetchall() keyboard_list_base = {} for i in keyboard_list: - if i['keyboard_number'] == 0: + if i[0] == 0: pass else: keyboard_list_base[i[0]] = {"keyboard_number": i[0], "using_member_id": i[1], "device_instance_path": i[2], "device_name": i[3], "detail": i[4], "alt_name": i[5]} @@ -841,7 +841,7 @@ class DL(): return {"result": 0, "about": "ok", "output_dict": keyboard_list_base} except Exception as error: - self.log(title=f"[ERROR] PCリストの取得中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + self.log(title=f"[ERROR] キーボードリストの取得中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} finally: @@ -863,7 +863,7 @@ class DL(): mouse_list = cursor.fetchall() mouse_list_base = {} for i in mouse_list: - if i['mouse_number'] == 0: + if i[0] == 0: pass else: mouse_list_base[i[0]] = {"keyboard_number": i[0], "using_member_id": i[1], "device_instance_path": i[2], "device_name": i[3], "detail": i[4], "alt_name": i[5]} @@ -871,7 +871,7 @@ class DL(): return {"result": 0, "about": "ok", "output_dict": mouse_list_base} except Exception as error: - self.log(title=f"[ERROR] PCリストの取得中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + self.log(title=f"[ERROR] マウスリストの取得中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} finally: From b0865354a3bcb8aaf5bb4385cba0abb4644188eb Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 00:28:59 +0900 Subject: [PATCH 116/175] =?UTF-8?q?PC=E3=81=AE=E3=83=8B=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=83=8D=E3=83=BC=E3=83=A0=E3=81=8C=E7=99=BB=E9=8C=B2=E3=81=A7?= =?UTF-8?q?=E3=81=8D=E3=81=AA=E3=81=84=E3=83=90=E3=82=B0=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index c0e53fa..82036a6 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -1585,7 +1585,7 @@ async def pcinfo(interaction: discord.Interaction): @discord.app_commands.default_permissions(administrator=True) async def pcnickname(interaction: discord.Interaction, pc_number: int, nickname: str): if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: - pc_nickname_set = dislocker.set_pc_nickname(pc_number=pc_number, nickname=nickname) + pc_nickname_set = dislocker.set_pc_nickname(pc_number=pc_number, alt_name=nickname) if pc_nickname_set["result"] == 0: result_embed = discord.Embed(title=":white_check_mark: 操作が完了しました。", description=f'PC番号 {str(pc_number)} のニックネームは {str(nickname)} に設定されました。', color=0x56FF01) From 970e04bc75cddd1c47dde72fa815de611962c62b Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 00:31:32 +0900 Subject: [PATCH 117/175] =?UTF-8?q?=E3=83=9E=E3=82=A6=E3=82=B9=E3=81=AE?= =?UTF-8?q?=E7=99=BB=E9=8C=B2=E3=83=9C=E3=82=BF=E3=83=B3=E3=81=8C=E5=87=BA?= =?UTF-8?q?=E3=81=AA=E3=81=84=E3=83=90=E3=82=B0=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index 82036a6..a080391 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -1200,7 +1200,7 @@ async def on_button(interaction: discord.Interaction): for i in mouse_list["output_dict"].keys(): current_mouse_list = mouse_list['output_dict'][i] - if i['alt_name'] == None: + if current_mouse_list['alt_name'] == None: mouse_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(current_mouse_list['mouse_number'])} 番", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_{str(current_mouse_list['mouse_number'])}") else: mouse_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(current_mouse_list['mouse_number'])} 番 | ({current_mouse_list['alt_name']})", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_{str(current_mouse_list['mouse_number'])}") From 0b718125ee64312c14296eafd51ccfd902b79474 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 00:40:13 +0900 Subject: [PATCH 118/175] =?UTF-8?q?=E3=83=9E=E3=82=A6=E3=82=B9=E3=81=AE?= =?UTF-8?q?=E7=99=BB=E9=8C=B2=E3=83=9C=E3=82=BF=E3=83=B3=E3=81=8C=E5=87=BA?= =?UTF-8?q?=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C=E4=BF=AE=E6=AD=A3(2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_slash.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker_slash.py b/dislocker_slash.py index a080391..8b01d1d 100644 --- a/dislocker_slash.py +++ b/dislocker_slash.py @@ -866,7 +866,7 @@ class DL(): if i[0] == 0: pass else: - mouse_list_base[i[0]] = {"keyboard_number": i[0], "using_member_id": i[1], "device_instance_path": i[2], "device_name": i[3], "detail": i[4], "alt_name": i[5]} + mouse_list_base[i[0]] = {"mouse_number": i[0], "using_member_id": i[1], "device_instance_path": i[2], "device_name": i[3], "detail": i[4], "alt_name": i[5]} return {"result": 0, "about": "ok", "output_dict": mouse_list_base} From f237d4ce0686723342263b7e1d25f0ac2d1bea9e Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 00:52:32 +0900 Subject: [PATCH 119/175] =?UTF-8?q?dislocker=E3=81=AE=E5=9C=A7=E7=B8=AE?= =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=82=92=E5=89=8A=E9=99=A4?= =?UTF-8?q?=E3=81=99=E3=82=8B=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/download.cmd | 1 + 1 file changed, 1 insertion(+) diff --git a/script/download.cmd b/script/download.cmd index 5584a13..6644a85 100644 --- a/script/download.cmd +++ b/script/download.cmd @@ -12,4 +12,5 @@ tar -xf %dir%\dislocker_client.zip -C %dir%\temp_ds mkdir C:\ProgramData\Dislocker xcopy /e %dir%\temp_ds C:\ProgramData\Dislocker rmdir /s /q %dir%\temp_ds +del %dir%\dislocker_client.zip C:\ProgramData\Dislocker\setup.cmd \ No newline at end of file From d6e046178ee83e0bf7c539c479c3ac8ffb6cbb84 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 00:52:52 +0900 Subject: [PATCH 120/175] =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=82=BF=E3=83=BC?= =?UTF-8?q?=E3=83=8D=E3=83=83=E3=83=88=E7=B5=8C=E7=94=B1=E3=81=A7=E3=82=A2?= =?UTF-8?q?=E3=83=83=E3=83=97=E3=83=87=E3=83=BC=E3=83=88=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=81=9F=E3=82=81=E3=81=AE=E3=82=B9=E3=82=AF=E3=83=AA=E3=83=97?= =?UTF-8?q?=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/update_online.cmd | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 script/update_online.cmd diff --git a/script/update_online.cmd b/script/update_online.cmd new file mode 100644 index 0000000..0cee3d8 --- /dev/null +++ b/script/update_online.cmd @@ -0,0 +1,18 @@ +@echo off +set dir=%~dp0 +cd %dir% + +set download_url="" + +curl -L %download_url% -o %dir%\dislocker_client.zip + +mkdir %dir%\temp_ds +tar -xf %dir%\dislocker_client.zip -C %dir%\temp_ds + +rmdir /s /q C:\ProgramData\Dislocker\_internal +xcopy /e %dir%\temp_ds C:\ProgramData\Dislocker +xcopy /Y %dir%\temp_ds\dislocker_client.exe C:\ProgramData\Dislocker\dislocker_client.exe +rmdir /s /q %dir%\temp_ds +del %dir%\dislocker_client.zip +echo Abvf[gBDefendeřx߁AxNĂĂB +pause \ No newline at end of file From d93111b7237eaf532e5a969968fb3d1c671f8594 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 00:59:55 +0900 Subject: [PATCH 121/175] =?UTF-8?q?=E3=82=B3=E3=83=94=E3=83=BC=E3=82=92=5F?= =?UTF-8?q?internal=E3=83=87=E3=82=A3=E3=83=AC=E3=82=AF=E3=83=88=E3=83=AA?= =?UTF-8?q?=E3=81=A0=E3=81=91=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/update_online.cmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/update_online.cmd b/script/update_online.cmd index 0cee3d8..66c9d6a 100644 --- a/script/update_online.cmd +++ b/script/update_online.cmd @@ -10,7 +10,7 @@ mkdir %dir%\temp_ds tar -xf %dir%\dislocker_client.zip -C %dir%\temp_ds rmdir /s /q C:\ProgramData\Dislocker\_internal -xcopy /e %dir%\temp_ds C:\ProgramData\Dislocker +xcopy /e %dir%\temp_ds\_internal C:\ProgramData\Dislocker\_internal xcopy /Y %dir%\temp_ds\dislocker_client.exe C:\ProgramData\Dislocker\dislocker_client.exe rmdir /s /q %dir%\temp_ds del %dir%\dislocker_client.zip From 1385eff922bbde13cf592753d2108d38eafdc550 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 01:02:51 +0900 Subject: [PATCH 122/175] =?UTF-8?q?=E6=96=87=E8=A8=80=E3=82=92=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker_client.py b/dislocker_client.py index d3517ab..a2f6ed0 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -284,7 +284,7 @@ class Lock(customtkinter.CTkToplevel): 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, 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 = customtkinter.CTkLabel(self.msg_subtitle_frame, text='※ パスワードの有効期限は発行から5分間です。', 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') From 76df98f6d880d49a4fe59ff2b85716ace7e60f40 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 02:01:05 +0900 Subject: [PATCH 123/175] =?UTF-8?q?=E3=83=87=E3=83=BC=E3=82=BF=E3=81=8C?= =?UTF-8?q?=E5=89=8A=E9=99=A4=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84=E3=83=90?= =?UTF-8?q?=E3=82=B0=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dislocker_client.py b/dislocker_client.py index a2f6ed0..ebf098d 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -531,12 +531,12 @@ class Stop(): if client_config["eraser"] == True: 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") + epic_del = stop.delete_appdata(process_name="EpicGamesLauncher.exe", dir_path=f"{appdata_local}\\EpicGamesLauncher\\Saved") + chrome_del = stop.delete_appdata(process_name="chrome.exe", dir_path=f"{appdata_local}\\Google\\Chrome\\User Data") + discord_del = stop.delete_appdata(process_name="discord.exe", dir_path=f"{appdata_roaming}\\discord") + steam_del = stop.delete_appdata(process_name="steam.exe", dir_path=f"{appdata_local}\\Steam") + ea_del = stop.delete_appdata(process_name="EADesktop.exe", dir_path=f"{appdata_local}\\Electronic Arts") + riot_del = stop.delete_appdata(process_name="RiotClientServices.exe", dir_path=f"{appdata_local}\\Riot Games\\Riot Client") else: print("削除処理をスキップ。") stop_url = client_config["auth_host_url"] + "/stop" From b96496018f845bbb9c44799f91a5e14dcd2d7c83 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 02:01:37 +0900 Subject: [PATCH 124/175] =?UTF-8?q?self=E3=81=A7=E3=81=84=E3=81=84?= =?UTF-8?q?=E3=81=98=E3=82=83=E3=82=93=EF=BC=81=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dislocker_client.py b/dislocker_client.py index ebf098d..1828bb8 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -531,12 +531,12 @@ class Stop(): if client_config["eraser"] == True: appdata_local = os.path.expandvars("%LOCALAPPDATA%") appdata_roaming = os.path.expandvars("%APPDATA%") - epic_del = stop.delete_appdata(process_name="EpicGamesLauncher.exe", dir_path=f"{appdata_local}\\EpicGamesLauncher\\Saved") - chrome_del = stop.delete_appdata(process_name="chrome.exe", dir_path=f"{appdata_local}\\Google\\Chrome\\User Data") - discord_del = stop.delete_appdata(process_name="discord.exe", dir_path=f"{appdata_roaming}\\discord") - steam_del = stop.delete_appdata(process_name="steam.exe", dir_path=f"{appdata_local}\\Steam") - ea_del = stop.delete_appdata(process_name="EADesktop.exe", dir_path=f"{appdata_local}\\Electronic Arts") - riot_del = stop.delete_appdata(process_name="RiotClientServices.exe", dir_path=f"{appdata_local}\\Riot Games\\Riot Client") + epic_del = self.delete_appdata(process_name="EpicGamesLauncher.exe", dir_path=f"{appdata_local}\\EpicGamesLauncher\\Saved") + chrome_del = self.delete_appdata(process_name="chrome.exe", dir_path=f"{appdata_local}\\Google\\Chrome\\User Data") + discord_del = self.delete_appdata(process_name="discord.exe", dir_path=f"{appdata_roaming}\\discord") + steam_del = self.delete_appdata(process_name="steam.exe", dir_path=f"{appdata_local}\\Steam") + ea_del = self.delete_appdata(process_name="EADesktop.exe", dir_path=f"{appdata_local}\\Electronic Arts") + riot_del = self.delete_appdata(process_name="RiotClientServices.exe", dir_path=f"{appdata_local}\\Riot Games\\Riot Client") else: print("削除処理をスキップ。") stop_url = client_config["auth_host_url"] + "/stop" From 8a69b25d5267a3d094d6f7a1fbaeaa286a17818d Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 11:33:12 +0900 Subject: [PATCH 125/175] =?UTF-8?q?=E3=82=B7=E3=83=A3=E3=83=83=E3=83=88?= =?UTF-8?q?=E3=83=80=E3=82=A6=E3=83=B3=E6=99=82=E3=81=AE=E5=87=A6=E7=90=86?= =?UTF-8?q?=E3=82=92=E5=88=A5=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=81=AB?= =?UTF-8?q?=E5=88=86=E5=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client_shutdown.py | 152 +++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 dislocker_client_shutdown.py diff --git a/dislocker_client_shutdown.py b/dislocker_client_shutdown.py new file mode 100644 index 0000000..54f50f7 --- /dev/null +++ b/dislocker_client_shutdown.py @@ -0,0 +1,152 @@ +import os +import json +import tkinter.messagebox +import customtkinter +import subprocess +import requests +import tkinter +import sys +import time +import shutil + +app_name = "Dislocker" +dislocker_dir = os.path.dirname(os.path.abspath(sys.argv[0])) + +os.chdir(dislocker_dir) + +sp_startupinfo = subprocess.STARTUPINFO() +sp_startupinfo.dwFlags = subprocess.STARTF_USESHOWWINDOW +sp_startupinfo.wShowWindow = subprocess.SW_HIDE + +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): + msgbox = tkinter.messagebox.showerror(title=f"{app_name} | エラー", message=f"設定ファイルが正しく構成されていません。") + +elif os.path.isfile(client_config_path): + with open(client_config_path, "r") as r: + client_config = json.load(r) + +def init(**kwargs): + task_exist = subprocess.run('tasklist /fi "IMAGENAME eq dislocker_shutdown.exe"', startupinfo=sp_startupinfo, stdout=subprocess.PIPE, text=True) + if 'dislocker_shutdown.exe' in task_exist.stdout: + task_count = task_exist.stdout.count("dislocker_shutdown.exe") + if task_count == 1: + pass + else: + return 1 + + if client_config["initial"] == True: + msgbox = tkinter.messagebox.showerror(title=f"{app_name} | エラー", message=f"設定ファイルが正しく構成されていません。") + return 2 + else: + return 0 + + +class App(customtkinter.CTk): + def __init__(self): + super().__init__() + + if client_config["testing"] == True: + pass + else: + pass + + self.geometry("100x100") + + self.title(f"{app_name} | てすと") + self.iconbitmap(default=resource_path + '\\icon\\dislocker.ico') + + self.protocol("WM_DELETE_WINDOW", self.handler_close) + + self.frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') + self.frame.grid(row=0, column=0, sticky='nsew') + + self.withdraw() + + 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 + + i = 0 + i_max = 10 + result = 1 + while i != i_max: + i += 1 + try: + # プロセスの終了 + subprocess.run(f'taskkill /f /t /im {process_name}', startupinfo=sp_startupinfo, stdout=subprocess.PIPE, text=True) + print(f"{process_name} を終了しました。") + time.sleep(0.1) + # ディレクトリの削除 + shutil.rmtree(dir_path) + if os.path.isdir(dir_path): + pass + else: + print(f"{dir_path} を削除しました。") + result = 0 + i = i_max + + except subprocess.CalledProcessError as e: + print(f"プロセス終了エラー: {e}") + + except PermissionError as e: + print(f"権限エラー: {e}") + + except Exception as e: + print("エラーが発生しました。\nエラー内容:") + print(f"エラータイプ: {e.__class__.__name__}") + print(f"エラー引数: {e.args}") + print(f"エラーメッセージ: {str(e)}") + + def handler_close(self): + print("停止処理を実行。") + if client_config["eraser"] == True: + appdata_local = os.path.expandvars("%LOCALAPPDATA%") + appdata_roaming = os.path.expandvars("%APPDATA%") + epic_del = self.delete_appdata(process_name="EpicGamesLauncher.exe", dir_path=f"{appdata_local}\\EpicGamesLauncher\\Saved") + chrome_del = self.delete_appdata(process_name="chrome.exe", dir_path=f"{appdata_local}\\Google\\Chrome\\User Data") + discord_del = self.delete_appdata(process_name="discord.exe", dir_path=f"{appdata_roaming}\\discord") + steam_del = self.delete_appdata(process_name="steam.exe", dir_path=f"{appdata_local}\\Steam") + ea_del = self.delete_appdata(process_name="EADesktop.exe", dir_path=f"{appdata_local}\\Electronic Arts") + riot_del = self.delete_appdata(process_name="RiotClientServices.exe", dir_path=f"{appdata_local}\\Riot Games\\Riot Client") + else: + print("削除処理をスキップ。") + stop_url = client_config["auth_host_url"] + "/stop" + stop_json = { + "pc_number": int(client_config["pc_number"]), + "pc_uuid": str(client_config["pc_uuid"]), + "pc_token": str(client_config["pc_token"]) + } + try: + responce = requests.post(stop_url, json=stop_json) + if responce.status_code == 200: + print("停止処理は成功しました。") + elif responce.status_code == 401: + print("認証に失敗しました。") + else: + print("内部エラーにより停止処理に失敗しました。") + except: + print("ネットワークエラーにより停止処理に失敗しました。") + finally: + self.destroy() + + def icon(self): + self.iconify() + + +if __name__ == '__main__': + init_result = init() + if init_result == 1: + warning_msgbox = tkinter.messagebox.showwarning(title=f"{app_name} | 多重起動エラー", message=f"すでに {app_name} は実行されています。\n正常に起動しない場合は、既に起動しているプロセスを終了してから、もう一度起動してみてください。") + elif init_result == 2: + pass + else: + app = App() + app.protocol("WM_DELETE_WINDOW", app.handler_close) + app.mainloop() From d2bb01533b8d44ebd773aac105b2e6b9bbf279b9 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 11:40:25 +0900 Subject: [PATCH 126/175] =?UTF-8?q?=E3=83=AD=E3=83=83=E3=82=AF=E8=A7=A3?= =?UTF-8?q?=E9=99=A4=E6=99=82=E3=81=AB=E3=82=B7=E3=83=A3=E3=83=83=E3=83=88?= =?UTF-8?q?=E3=83=80=E3=82=A6=E3=83=B3=E6=99=82=E3=81=AE=E5=AE=9F=E8=A1=8C?= =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=82=92=E5=AE=9F=E8=A1=8C?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dislocker_client.py b/dislocker_client.py index 1828bb8..40d42f0 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -447,7 +447,15 @@ class Lock(customtkinter.CTkToplevel): msgbox = tkinter.messagebox.showinfo(title=f"{app_name} | 未実装", message=f"ヘルプページは製作途中です。\nDiscordサーバーの指示に従って、認証を進めてください。") self.deiconify() + def wakeup_shutdown_background(self): + wakeup = subprocess.Popen(f'{dislocker_dir}\\dislocker_client_shutdown.exe', startupinfo=sp_startupinfo) + def exit(self): + try: + self.wakeup_shutdown_background() + except: + pass + self.destroy() app.exit() From 98e45292abb9eb7a3516ebf8501ad60a906a5677 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 11:48:23 +0900 Subject: [PATCH 127/175] =?UTF-8?q?shutdown=E5=AE=9F=E8=A1=8C=E3=83=95?= =?UTF-8?q?=E3=82=A1=E3=82=A4=E3=83=AB=E7=94=A8=E3=81=AE=E3=82=A2=E3=82=A4?= =?UTF-8?q?=E3=82=B3=E3=83=B3=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/icon/dislocker_shutdown.ico | Bin 0 -> 360414 bytes resource/icon/png/shutdown/128.png | Bin 0 -> 11109 bytes resource/icon/png/shutdown/16.png | Bin 0 -> 727 bytes resource/icon/png/shutdown/256.png | Bin 0 -> 35086 bytes resource/icon/png/shutdown/32.png | Bin 0 -> 1531 bytes resource/icon/png/shutdown/512.png | Bin 0 -> 105422 bytes resource/icon/png/shutdown/64.png | Bin 0 -> 3917 bytes 7 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 resource/icon/dislocker_shutdown.ico create mode 100644 resource/icon/png/shutdown/128.png create mode 100644 resource/icon/png/shutdown/16.png create mode 100644 resource/icon/png/shutdown/256.png create mode 100644 resource/icon/png/shutdown/32.png create mode 100644 resource/icon/png/shutdown/512.png create mode 100644 resource/icon/png/shutdown/64.png diff --git a/resource/icon/dislocker_shutdown.ico b/resource/icon/dislocker_shutdown.ico new file mode 100644 index 0000000000000000000000000000000000000000..c05d727cf59d8531390817a9c16541c5f003c9d8 GIT binary patch literal 360414 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 literal 0 HcmV?d00001 diff --git a/resource/icon/png/shutdown/128.png b/resource/icon/png/shutdown/128.png new file mode 100644 index 0000000000000000000000000000000000000000..aaf2e18e5972f75e9eb2677b0dfe2341c094e24a GIT binary patch literal 11109 zcmV-rE1J}aP)TckbxLOB(TtK|BT*404ST#5_yF$acE2THA;&cX^rAM#NpUL!1SyCbiR~5wvma z+D^c&y^L3vk+5Tzv{{9?D-C_58Yp1w#FhjY5s1T>H#S&<1hS;j{ddn}zs`T}NF!-x zG!nC6bnpG|`OkAdzPdB8_yWIY7B=N9Tp7m zcb(-9ovbVxEllJLb%_nL;VDul3th?c~WgtUrzAmT*KUiq$vm^A$0B@=!AQth8V>mMu!XoY#3e}7yX#H=(H++ zydtlYElgg?ys-YdCoPk;y?&qWpx{0;qwp<#=SMV&uQy-sgpsg2tQ+r+-@E*w502sx zpjpoVn=k**!vi5))1h!>6RmB~U$WNJMxa}i(ga+XCC0p@X+D`Z$8=&Cnr z62xE#sR7~ogIp?R!@gY?0^M=e{U02`ET&n@08hq+kL$B0F3oVDj@e@3iBFW@mp6K& z|2}^<2{pT4YJxBle0|Iedb?Y4cdeL-R;OLtsZ1w+Xn;-e`mJX_@PRup3u)Fdz>}AM zeYgn&YeR$60bV+mRI%TpenYj0DBMA&qO%<@`hJ3Yxdkx;4l5)g6>qoLj-kMKLA8-; zofgJOZBrQS1l)Ay16R)~Mwm4WkQP36XH5il!|AhEy!xJMu?wOBNIo#Jxb|QIS0Flr z)DJ6Yo!TrZgB1QEC<8dPP~ifur(ng}fFLnKr@8j5`#&^-SwIJ!0XAQ$uRn5C>JbDDE0;K~NBHpU-9 zH3roXzMamxZZmN6!3K*5l>r{V^lLZ74*Xu6pQZT%iGeqvWK)MjDN{j9HtEfl){VflmC-h_ih2ZaH$)Q6CzUgnNp+cvt= zp!^{?LrjY5RU;rcEn-$g6uPbzYn4vX3gj>z;{}KA%_vw|`64(CZ)V8xgWwb$BT6-X z+@sOGUAg?BYezA|)WHnXxU*J1)v=_Z)N z7acWfLSx^TrHR+`tfqT&$#hRHoBvAP#L9+p8>DR!_aW8SlsfXFHe7G%1LgWkJ=O;C zViJQ+s}CffA?4*>%Ht>}a%(13 zm2HJ|VW^)O$vDVt1`K#ojrz&O5kiF<*)&OC%XiveHDJgls3;Q`mX0QYFdj|9uZ?1w zY1$d!i3`89qUprNM>xI7>NV8jD(?(2R~WnoaA?CjA+xKD99f!3*25Hx3;akceKio7 z-T?YH%neGEQQE1sKneymff+40_0&szW2Xqjk0>Wj;&SM#;z)uaZ7hS%g3;-IRp)|T@YeY=GZB(TKu zQp#FR!p%)#NYJ%sfA_jOFby;<46ymaFRhM)y4^rqI}~fzd{-=XLUKj#%a=WPLcI`e zsje`BLIe}XP=}V`8mZUPw+krd5F4-cLaGi@iNh!?=USiVL~r}~%M!eDHG!fgofamT zat3%bF8%<>B;RG^^crZd?fOMmw9k@e=wHEDrHA<&iw3gkamhZGE|t;>n4(pVTJvOJ z)Ph7>Pe0H)$#L=pWy1EoozPi5)l4v@4Dj$p|8doPqPw68HF6ClFjN*(5wy3<4sn}y zPLdf_IyE)Q23WVUq^G)xZK$^oYI7^2+X@L&zq)N^v>i4xl6+!ZB74c$lLI{Cav#K| zqe?rQfsv#UHyB(HD>(I*-;PuFs<(gd#wo)DQ^o+BFS%t1-GL3Ur-S}FRrXL)r2Vsc zG(qRcl|gl_DqX%W5(uY28F^RVw<31bw?17&7U?Ee%84WoA}5Sv1kE8Zr=pc;0Q|Ny zH@50?4TiULL-(C;`>Pu_VhU(V7@#bESRci3NTf}%RzzMtYh405S!T7$ zd|%T5qHnk&GessyWn#-#L@nd52jSM)TdHCLRO?~i(A=3AI(GC21>VihDnyx zY`og%(7V;UKQb@%iCVY)fO{Zi#}>BG{@D1@NCA{PJ&aDKFDkl>MU`BsuCD%8Bt zXa!sR@Mz)LCxj!;{yAFkmJ`B)lNV#r**}-RnSavas^s_!ThZON1Kq7VvGa)^WBmD7 z@%s0k#*V-FF_b|(&d|2~WV*h)&Z&ddng)<22u-+Lx9#+YbjujNGx zlnysgaHr8ezu{EmfhWhI|KQoVXbIIcl}l^s^AH6vtc{meVn1lVG{6s5eCnpy@b8ID zgzslX7JiWsErLS&K$Uz4V&xmE?^|`WxT*9YmOt_xr-u_h_6s=j=TF6S(zcN&F!rrS zGDArF#VcUH!vKVKP?ye7j+(W+hy~p{<5AaFLS5dcv6v&5PW*7=+cy5|eKrOA%m7*S zqe-fN2{}2KY;$$$MjbK(b8UaReJ$VKQCl&1<=evvAN>W)KY0mel9Gw|$!-4+WB>gT zwC12kjmsEx&^lcESH9EtGCin5!YvA}IU4I=*ZIpg-aLwZpnYb5DE(30&}zoa3frCC z1~V^@lHV)%)z99#R|z5;et_3|DkE9qhr+Ud``#H}_%;B2+OWE$-&kx8o^ZA9>rR-(87)pnYP1M^^m1;i$;hXG_%(9NJMv$b*PE z*)(YNc37+Xh>tXG$g#yREa4^xMK8`jd1+qwsPZwJXzW`Lu+ozZt}~L;kv~Hp)=N|MfoAAcf02$1DmbUbhm*{>sHT6zG*deh4pp z_8zz0t(H+Q48EWf=SS%H9+j;8gfoJ%V%h*sK8oG z)VbczW0w)Ss9w8F@&;L-M4Er{U^wjyAB_I{n{cSmasTWR9ChJPc;<#XvFrIQ!J)mN zdl=aOIMvQJ0D={iafsmcYcNaEnmeBRPLl=o3`~M1r2&%kN0Gc;^X}eeJ7TR?Rqa$f z<2nw{`@L1*&y~^7_8w>4erMfWr|c zM4`@hlNe)KdG4d18rfSPCzlMO?)AloC)c?~k~o^23AW!IWnX@iGL8HE5kz%c;#DTv-ECH$|Mks(FItOF)nty%@2R9*$?C|VUMj4V$~KKAhEDy$ z)rZaECrYO5)LXB{z%dJo^vfJUGy!XYjOuwV`LoP%pf2frqgz}FNhd=!Fl*%|n~wPd z;hMemZI2AFdBrD&K)bIg9tl~8GKXBhHRlKMo&@%wBl zPWr@qu;`qVaJbQ;b56zy*S$;3Bdhs_Q~+c?BsJ}hv6Y|;f|^%0lRE#Uo;=sOPEDTs zRU3wG*rO!vkpaecHN(txD$zw!Dp?2!RtE7zEGikfOdQ!@8~FBVSCo$ZPf$RJVfQ`HY@9JiC0kS;Aed5})}-i#AYhWXX0|fW$T_cJS}nteo)c|1hR*7vXTHfyGB)*{41b6!i;+Qg0w@ z!{F4s^O%~95!}`=@eIh{Tgn&f5;_>(lNp$h0miX&n57uH@WqEK?p!F+n-?%Jcp)d* zMe;-7W7TTYq#W7%V^>{BOIN)EZxlM}qQ4gdMQ?^2IcLpUnSi7zd9hiIHgJ0g;kARC zPe@s63TRCA%v(4xp&6Li1Q4!-a;W*CO~aoXzmViS0^hm9{!eMrG?)5r+=DX6BD%Q=wJS246a^{d2gEju=9dA=3`e~L_fXjJ0*T!0{ONZJZsG0 z1EVtEdLE59on)y7s=~-+0rJ}w6ZNC7!XPArWLJ$Yw z%5}f7K#Io5LBKoDfBcIh?LPE30fh6MJrXou{vdnq8v`DVlY zFCJXF*6B9q_3J}l28fjotADA{r^W%ehRQzwhRoK>mTqtVE#MPephK=NT{Zpb-L#m= z*6%B&dy>|eZhF&i{3GaGk`D%zjTJIqp=q3J*tGrtamRzYljdd?N0Wy8wcnos(5w(t z$sLo<1&{J!ek&BcI*&lKh`27&!BpphWlN(5csr((cE9*yW`O-A!3)z#M_qIl297?W zBu88%6{UcYjwo|1C?2m^u+-^43K?BNgmh6YPR#jg}+J!yuT8$T68{ zj!iK0>*@XC0lgYv-fJu1f(#PK38MN*&$m$(_N{DDqtQS}5Q8IE4acjOy!$*%H|>w= zZ_`8`!&K6eUs_Skck6L>!F>la0X)AJb!Bx=!I+?w(>2O#TT>4MbV4}KH)b~5BwMZr z(-Np`y~hcx6^d=*>|nfh)WvVXbkbC*?zH&HluRcbvHTPS%czQ(&DShmQZ#Ofs-~h{ z5`YKIB6YuZL!%PXw2U@j%^g%dZ#hSRFN_Q+(wZENYa1@ zjyV$3NmD7cDQGmEG_d$cELwhQ(D}+)Qw{e9O`6Gu8VRr!$!=?5f<5HYqKoB@y=#D| zq*ny5-X)|cn!qJx@pn!We~Mp0x0H1 zRUl9Wt6MF!YG4n}QSTIrUrho7l*69En*arQ%89Jz_27eXoo#QDbN%X+Lh496ex{oi zoOvduie`cVj#z#goYY%vFNNQW768v7$&?0Eo*HY|Ch&SdJpn1#^qPR)P;h?43rFoZ zaHG*Y4oqeq2pnrHEGj2h_{Bn7q!GcCK`dLI@?fW#QaCp)+*D|SMJrZdx@p0(<5~K7 zqcyvE)lOMHjZ*@E*|5r7YoIVMfF3o@*Ldx6690^LyS*A9F#u`iPWu4ctO(t*M7-li zKqLPJ$s&@)2qZv}UI&gj0@F=t@uyprigMYYHN_HO$hN?eOKm;-fGVwD|q+$AQvJ zOx+HSWG2Ivyh4uPY2{8dAG7zlN$ue4V{0q6d88>720^K|UTKb~>Gz6OOP zH9V6<&~q1wLOEZn3F3TUs)%+d`vf#e%k$1lpJO(B?9R=3-|6pR0Fp>6P=9|CU*0^cL}fRHXkC(`x~awsmES)=WoH4z1M7Drtrr7;UKIq&$H72) zI6>@-M3y)nEvZM2V3h!pfyC`!noC}{!F2K~M}@%MlKpe=ry6P>4^d44hs z#;89ykez^fVCk#U!%kdXjs|_afL;wiG!{!0v{>Q}X2#h(6jJrVCWEyED<(i51;lv` zG_qL*2g3}^cIs|T@rp*!oZBHp&Xki)KWpQR?dt7c`d|sLp5S-s{~xv6?WX}Cn+{1h zi4!^c>LO6&j)bufqh5m^RZ@^r>fHX=DCVf9?$+(>uIZCSb+13cC3R38&l6vt0vhqs zNXo#%$5hJFSi9Xmn?h+6Wj}y`4tDDlWkCJV+#W}#NTN>A&t`DQ;fiPio_Ps#RMYk! z{9RJ}Z0Z}^+7}p%F_;dvFzTtb<~w7v2KI<0Sj2Ge3=kU|71_@hNFUbVo@WF8imEUq zxKmPw-2l=P#C-i{KlnS$QB7%14aY*hcemQ{aWE_^m>&|)m-4ax$!LIB zs31#aG;%(qJ?Dqn!ucXW%LYBBEKDHxr_b9Tn`3}BMFt?-1YX>{a#Xx=*_84wK)z!a zAlQW4bBdW+g5w=R&nAf;256e@Ces+=Ww0)iNRab8xs1J%_KN+F{D{n;Ocme$?2#W* zckAmj780KA6z2llAN?VVugXd&=si6ivk@s~0kilVv0Dzw&G5`_6M8g24+v~R zGfF<8pkPE&l?njMdiqeM<*+3_T^NEvh=zzm6SmKp0i|uMM=ZXi|rko&2I8Daw?)GSa9tM~T}pH5NX5jzTfJCUmmjU*;LS_mAI;z9-4x7QZg8-HxAaYPa3P z0Oyb19Wg*NN^Um_IG!ZgK>2-LqT1!-G;lE2gcw9hN16f(+$| zG_dU5L?dSb8u^@Vxwl{Y6C4er5vXgy=|r_VMdjpQ(H4$Xy8{b6RJMuhQdO0xq38eb zYj~s6EC1zxLDW1dGf<=$I;ZXr8t@@6`SKGw@{8&wuqiIk9qHHp1V_VY6Q4FAmSB`L zvF#7!s1-*78^r{=b4b97=Ujdoh+v1;9^8PP&rI)@>)A|c>|=j%KOd%P)`KzXY?`Q9UHAo=cuAhFe zYwUs-*Y`C66FwgxYXQ)(y6-usOhKMW%jmB<#vCBI2N?jlX9<)2jBBo=ynp4+|ACW! z^TSwp>dZX~RyK)vnu(pio|5#xa_3(F7Fy5OCA=I*?b&pKckBJ!o&E0XKh8YMzwgQ#NA8D+K#9u9#)n)sF1u3#R3L2Cs92@lg==Gn* z+5hbem?=tm|AS4>e{LbYHjQN-;W-_2E0+|<*4=2UnX&#b>0}{ z6~G;24<=)-qSDEZ<-?!TfVt)Bj>olW#&_L2QC}uxfb&O_&qyPU0J*$Lp=yN&f>6Y29)zws%~i67py`_*lD;d8f_^sS`&At7^vv&mA=>}vHHt^%Kr^cFIxF-tujIDnTiFs@;awr+SJrel zcGeK-*Ok`osXu#UfcfJK?}!b(Dz1j9Zw?ESd(eC(>AcYP)M0bm%OGmxC0oj=XXXlwS5v;@1K1U#SFSxqm=jT5ApJHb0NgQsp{gEf} z^zZx;Q2FcjTt7FLX4hr@Dp$`=3fo>kz|FA=YST39_N4w3?e%Hl6bcy3U_y>6yMT{H z04?Ij3`5nCF$erKVbsD7>2nGZKl!2G!p^5(z~Ms4ygm8h-vnB=9xjewls)Og95lOPkd+S@kBfBZ&-bi;-&DWPu}^_dajvxuJS$k zV~<3>@#3xeUr&s07oKtwo&Bw^goURa+D{Hoi~snQAH%MvpD$wrNOySXm?fN2BJCaC z@nKFOZ?rAbrcnV{!e8gjCO2%n=#{^|U~m1K)Gvl~18%NTzvR!H^Yz9Z9d~~M1o?i| z2AnX0K081;zu<-)&pa0%f8W2z6L_dnS^SU2*XQcFgO84~DNOU%NNk@U$4C5Rc32d! zo?n|&|4fGryt}(66+21xXsr7NC_FQ;>yFL2C?oJ`xJBSqNV_fii5?pO0z5CF1! zIDo&6iA=n*;mtD1V>5 zb?3uCwgjUWZ`pVTCP9?dvEQM`IlpE#*TVaKW?S(!P?yEx*3j zE!h&{`^C-UI!OR0hLzu#82j6=zq3gqi4MAr!MR~O z_uxDSv;Cox39)bX>G-Qx;gnBamodV@qy+GOc-`;dwFmwh-n|hwNf3TfgQQ#jO}Y8` zfZp!2sMeb51@^E?1!RXl*IhF9gKM!5w9gFi?ZI<~K%EV-V@uHfX;g?~;%t4eOl1Xs zLc8r?$C-oF$sT7(i>UA986+}7SoYh$KI4p#7XG=I0&wJ+s6{yWbjz`SB zv+sBct9lDsw7G=?h~>L2)M+lbbnNj_>;vsH10;HA@a!8RHo2v5Z4UkOpgq0~br}kE z7b@AX3VXUq##&rt=|6r~WQMD7?0epY>7=dqe;3bw`is~WUke3$@d&;zd0}?LtmlSd zL8+(6dw>hUxUzz|a*3NR-un2>*hktg21xYKlC#%?gF|F%6d1YK!WMfF+9tymSITt! zj2PJr7%RAIh8PQ%B~5V5;l3FWoUZiv6JdW`INwEk5&x*u88DbeoqLaR}&v3wpvHc;cAb zYk6-&ZO4 zNCjFpzXAoKamfjKy@IWgHGPy%t8cAaD!>9+gAQC8q^FbQ22nkjDTsx4eB*~yYYD|g zzyugVp@5ceZoTBS(Hn69bifR-VQ^@0=WeX0^fNP+WR~i651SYVK(EOoFG(R|jT_2) zwB|!~Z?z(*ZG|x#-><95a#QOhXJedd((yRcgWlvb8Olzo3E-)g#)Vv5P8U z+Y0OeaDg)k8U@HR!%;ntfjNF`XS3^ql{;P>#T3w#F+idRk2!T!;XjjCR= zJlh0WEiL@yCtt5g__QVL)>W#siWCSw>`E@Y-KrZGAJ&Lfu!FbNN~7e)sR2MlX29o^N%RV&ATGOfQ{IqeLP zC^5mjrn5e({vmGE+3vy$K9tMJY!|0KWzRy_VaawO8UdbU19fCD|d{IVw!127(nR3g~#3!C*g(w=gp(`=u9jD z28N+W3)j%VNjIs+Zvik zN{sb9&u2PlMj0T{{R;=vUV=4I#0D!^!0AvwPbmY}R2n(dd4TF3v%wA$qS7=TR*F?o zviO1pa5Uhk*Q8xV77k!A7$KMHg=bm~i^E}+d|&=%Y@maiF55XabLZr75EvlQdPI{j zpVr4oI$ipqs@h4TN9=u6adm~viGW4cdhgM7Cn#1Ut$~2Y zJJm1)yX(RX)U4mNd*D~2)Q@6@>7X!x&;tvOUK6#!^>GrGs;&2Xn{{7l zWPEowO8t&e97HqEyI;E(vw&s|0|?zWaMbEJQ)}Yi z>2Uokb7&s|T6^%kBziR`R$qo4 zvtr?eW*q|v-8ZmkbxeL-AKWiqvv|Tpoa${^;6+mlVKzmWfr3ZnHEwW0Ussnj>ad2x zihwmJ7@`zZ4Kvc|Mms;t7G7x9GJw#7SaikiPO~~n=QVn<%ApH9Xj72qW?0pY6u@9V`3%Jj-mPLxur76=oQS zOc1V&%bl2F&_i;-Ifv9x75s1K5};yB{`+K zZMqM*A}-d7PGpJ(&5E3JAoJ4z@4U6LPL;=i9*3?<4pH+|tSCJ}HA*@*{vt4|)%#@V zuwZ~TMGY|+bLi*A!6g7WSeOG#x+UP0_ACyia>4C6i2rPf9>S~qeiY>|{f6Sk^f}(U r=}?sLIhvz6nxi?IqdA(Re)RtVzPH>}|8Hxw00000NkvXXu0mjfJ?w;e literal 0 HcmV?d00001 diff --git a/resource/icon/png/shutdown/16.png b/resource/icon/png/shutdown/16.png new file mode 100644 index 0000000000000000000000000000000000000000..03d458342cf0d3426592071b4864f52209b0e647 GIT binary patch literal 727 zcmV;|0x127P)+*!BArS)WDs=eQ0ZL* zDG^Fd(Tm^<)>u&JwYzWI-S=j`W@dNaVO#Ng4DXw7e!p*i^UXI5HEj9v>qrxtuQ4BI zzyXi^RTh#f9rGi0HI`Kq4;Mp|jPV4B)FLAtCxavXO_LN89gDZ^{}OEWzOWdvIU=$c zIFXv5!q)WiUzx4e;vI{3?I6Ks?1@FZZca@ty=Az&4beLn(Re-ze)JIBZ%L$QHt>69 z&1B#pJ1?_cT`Lm~*8w34Mr^0~|%SQ~FsQK+}bD0kCCkEbt?VMBq2J zpH`c>OmUbjW-MMI>mkx^01Wr{*YrcpM<9%1g;iV*Kl4*35o+Ytw{VbUzWmTcA}>Sm z+!fCA-UreuRMwqmuMSMG_wEyneBrV@`?JcsoOwrh?zApU`tt&QJ$*_0k$2SE-rM)2 zdO%;QV@0DRTrLRs^_9NZ?M)|gJ{0g3*Sj2x3+=gM_kBVl~vuaUz*80WvSD z#MH+58?Pz!AnrI$g~Mu{RmreI2z9FC#PnQ(*%IPo9B`_!{{Y_qLRY8VLInT-002ov JPDHLkV1h*COql=x literal 0 HcmV?d00001 diff --git a/resource/icon/png/shutdown/256.png b/resource/icon/png/shutdown/256.png new file mode 100644 index 0000000000000000000000000000000000000000..534913f01b465ba6924aff96bbfe6eb210fd70bb GIT binary patch literal 35086 zcmV)$K#sqOP)|~0%V`o4Cd(Zl*pOIWD+K6}NXiNw z3~ROIEW%7|B?00zjqM~PCJB`6cp^BcJlmL{=Sj#mwn~FFRMk7X&pH1dzJ2a}Rj=k( zukM!W-FwgQk9+v`-v9Xzu%sm|X-P|3(vp_6q$MqBNlRMNl9sfjB`s-5OIp&Bmb9cL zEon(hTGEoX3kjBV-qT(C-?VxZSXFeZE7);a*LABh!(LVF?N9I3{@i$BKii`Hb^Qq$ zzV+1q5g9*JhHw4fwON0>Qyl7#9~w^fXV+BZQ2*snhxPZ(`p+HMv3AuRFI&Ts&TCpK zfb)dz-2bM%mjL@_m_26@@P6icf!BR92)Bv}Ffe`>mHZs;w)6d`j-Mv=gY=IV+1>3x zBel<_0OCyCJRELE*ZN;;eE|piwykxu!|!_#I}ToR$IA|3Nn4(l3SbM;AnX_6@ctdx zb!DgB{$9-X^7xil3s(no=0ig1ZA6${^4JOjlUBs5rq}l}#KkbcJJ8kB~$U z&SZ9QU5>t`!>)r@FI|XpPD=%Fj_Bk2|KI(jUD+dqzN|Yi z!&{zq$6vn#OFHYcQ~+m*`as>@BfXWMt*?AbF9X;2|MoHzA1$W&;bA?^9*=@+{=9_k zHLLAnI#Mf-t#?rI8pH&=iF`X8x~9~ny5N5M9SX(T9|CS|H?#ncNkpHhkJg;wl6(6g z=G#POZ+>95@|LB$aCT^^0L~zdLccyhdwF^PbKfBJ3x#u}se}=EK(AaaCPj8nkyg59 zEY$@~Twx%TPxu8{{{;S*LKn4t6!xyU9JPN`X2d0$bEBlp7M2OTS-RPqzxtiuaXXfD z#%QSk&H#PnMZdX!MP@JUqv9{`<>GQIaK7U^;K6%2)^vE#UQBufvnFT;5#lODTrMnf z98EZHp}k3+L%AXi-dnj5i*V>KEoPtsDSbh@4IFe(K92}IP`_B&nA>4`!iu?1JAJe4 zl-rkX!s(}_0ys@{=l<94z3Awk8&|OM_d4l?ejpNv47gRHXk@847b`4K7EDxbEoDyN zDy9(f^&?0(ej+{dNloDL?AmM}g#!oWF^i_nDTnb;!kDo7bR8*!Qg6l=ur`yGn~xkm z^0uqr^S9Qpq*JD)0ystbcyI0dVBV{Hw!Pl{Xf$Vc0koBpQiDsN-v`R)BU5>99@&Ra zF-CDI!iTr}CCz`V1@O<|R2Qj1Ox*~DD+-re3rac?@%Dr}!sx@Y7ZbN6Y&$9p2$2tT z0dUGO&LS1Z8+vVe`_ta>x9-4_PMVeq;3Vl!zUeov?>c#PKZ5%~zP6Z;+fBc_A0MIj zMcK44h<1Q{^^$XMw9uz-XW-JACD&u^h^6}tmHRpYMf&xA^Az24DUk(mw*%@5&RrL) z-PbA<*gqkmf_*&b%5ZtC#6GpSqARz3^*g`wb}Z?HX{i8CfEvu-bt824@}ButMP!0i z4Pc}B*HTcdV*dINP#xg^D0D7?RTE?oVA`nhIBMv zh6`sd(A;LzET%++C3&uH8yk$}bP@odKQOc4p%zs(9mTOBYkkb+=A|MyF@A4Kg#>Bw>!q}j5r8^uPc&g# zp!8#!v1nB`rC=#QatE?JS`tuW7|KhEy=)C6Bcv6)?cCStu^4;v9ooIJwz5jQ93St(e7=7HhJ}xJ2oUSFH0BEa7*07G7 zzxF-fwNwPhprr!XfIjkqU%z1{xTznYD|OD;&l*&<1v!}e%@kf;ey5`u0POgp=1$+KVu>@M=9%A^zR<057M_we;4g0?QkWFYEcOpp3B@ z5{SouYaR0m2udSbU5+5E3@s*JvRM?`Po$I98JM2;N0fcsV!1IP`=}cdQN!Z=!(1R# zHB1VP%P#Yx5h{4YdUxcOtKajAH7seV00#Ql3vR!;k7fToX`5$EEKoi?!{l-e(S>8U zXw7@MxA^U5%XIP_fV${S(t87k&*uH$W457bA3sB@5wowYE%aQ0-4n}r!_+SBbWw)h z�Hvl%4H`OA9>O1 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 literal 0 HcmV?d00001 diff --git a/resource/icon/png/shutdown/512.png b/resource/icon/png/shutdown/512.png new file mode 100644 index 0000000000000000000000000000000000000000..605ebb3f7d4c8810a8fe57aab5e74651b4dc7397 GIT binary patch literal 105422 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 z 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 literal 0 HcmV?d00001 diff --git a/resource/icon/png/shutdown/64.png b/resource/icon/png/shutdown/64.png new file mode 100644 index 0000000000000000000000000000000000000000..9f1262dcd5e5bd317d1071c0085d3dc3bcd1b745 GIT binary patch literal 3917 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 bz{CFl1q5G@P%W0_00000NkvXXu0mjfP&r0O literal 0 HcmV?d00001 From 781e408910d31e07d1e4201c721ef775e5273974 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 11:59:17 +0900 Subject: [PATCH 128/175] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=AF=BE=E8=B1=A1?= =?UTF-8?q?=E3=81=AE=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=82=92=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/update_online.cmd | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/script/update_online.cmd b/script/update_online.cmd index 66c9d6a..4b72f06 100644 --- a/script/update_online.cmd +++ b/script/update_online.cmd @@ -6,13 +6,16 @@ set download_url="" curl -L %download_url% -o %dir%\dislocker_client.zip -mkdir %dir%\temp_ds -tar -xf %dir%\dislocker_client.zip -C %dir%\temp_ds +mkdir %dir%\temp_ds\ +tar -xf %dir%\dislocker_client.zip -C %dir%\temp_ds\ -rmdir /s /q C:\ProgramData\Dislocker\_internal -xcopy /e %dir%\temp_ds\_internal C:\ProgramData\Dislocker\_internal +rmdir /s /q C:\ProgramData\Dislocker\_internal\ +xcopy /e %dir%\temp_ds\_internal\ C:\ProgramData\Dislocker\_internal\ xcopy /Y %dir%\temp_ds\dislocker_client.exe C:\ProgramData\Dislocker\dislocker_client.exe -rmdir /s /q %dir%\temp_ds +xcopy /Y %dir%\temp_ds\dislocker_client_shutdown.exe C:\ProgramData\Dislocker\dislocker_client_shutdown.exe +xcopy /Y %dir%\temp_ds\shortcut.vbs C:\ProgramData\Dislocker\shortcut.vbs +xcopy /Y %dir%\temp_ds\setup.cmd C:\ProgramData\Dislocker\setup.cmd +rmdir /s /q %dir%\temp_ds\ del %dir%\dislocker_client.zip echo Abvf[gBDefendeřx߁AxNĂĂB pause \ No newline at end of file From 35aad379a4c106c75497b7065a6d274b016c0ad5 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 12:03:25 +0900 Subject: [PATCH 129/175] =?UTF-8?q?=E3=82=BF=E3=82=B9=E3=82=AF=E3=81=AE?= =?UTF-8?q?=E7=B5=82=E4=BA=86=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/update_online.cmd | 3 +++ 1 file changed, 3 insertions(+) diff --git a/script/update_online.cmd b/script/update_online.cmd index 4b72f06..dd44704 100644 --- a/script/update_online.cmd +++ b/script/update_online.cmd @@ -4,6 +4,9 @@ cd %dir% set download_url="" +taskkill /f /t /im dislocker_client_shutdown.exe +taskkill /f /t /im dislocker_client.exe + curl -L %download_url% -o %dir%\dislocker_client.zip mkdir %dir%\temp_ds\ From b835fe5a740fdc9101efe3a7ce55e5d1886769d3 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 15:15:27 +0900 Subject: [PATCH 130/175] =?UTF-8?q?=E7=84=A1=E9=A7=84=E3=81=AA=E4=BA=8C?= =?UTF-8?q?=E9=87=8D=E3=82=B3=E3=83=BC=E3=83=89=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client_shutdown.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dislocker_client_shutdown.py b/dislocker_client_shutdown.py index 54f50f7..4082e15 100644 --- a/dislocker_client_shutdown.py +++ b/dislocker_client_shutdown.py @@ -148,5 +148,4 @@ if __name__ == '__main__': pass else: app = App() - app.protocol("WM_DELETE_WINDOW", app.handler_close) app.mainloop() From ae117944c09b99dd28c9a550a578241b85ffda67 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 15:29:42 +0900 Subject: [PATCH 131/175] =?UTF-8?q?customtkinter=E3=81=8B=E3=82=89tkinter?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E3=81=88=E3=81=A6=E3=81=BF=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client_shutdown.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dislocker_client_shutdown.py b/dislocker_client_shutdown.py index 4082e15..a82839f 100644 --- a/dislocker_client_shutdown.py +++ b/dislocker_client_shutdown.py @@ -2,6 +2,7 @@ import os import json import tkinter.messagebox import customtkinter +import tkinter import subprocess import requests import tkinter @@ -44,7 +45,7 @@ def init(**kwargs): return 0 -class App(customtkinter.CTk): +class App(tkinter.Tk): def __init__(self): super().__init__() @@ -60,10 +61,10 @@ class App(customtkinter.CTk): self.protocol("WM_DELETE_WINDOW", self.handler_close) - self.frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') + self.frame = tkinter.Frame(self) self.frame.grid(row=0, column=0, sticky='nsew') - self.withdraw() + self.iconify() def delete_appdata(self, **kwargs): process_name = kwargs["process_name"] From 5c7611481c8d2e95e6e793a43699f2828573b904 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 15:30:03 +0900 Subject: [PATCH 132/175] =?UTF-8?q?customtkinter=E3=82=92import=E3=81=8B?= =?UTF-8?q?=E3=82=89=E9=99=A4=E5=A4=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client_shutdown.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dislocker_client_shutdown.py b/dislocker_client_shutdown.py index a82839f..7411b3b 100644 --- a/dislocker_client_shutdown.py +++ b/dislocker_client_shutdown.py @@ -1,7 +1,6 @@ import os import json import tkinter.messagebox -import customtkinter import tkinter import subprocess import requests From aa413cee198ba4629c8560f17fae7e59bf18bb98 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 21:56:17 +0900 Subject: [PATCH 133/175] =?UTF-8?q?=E5=AF=BE=E5=BF=9C=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=82=B7=E3=82=B0=E3=83=8A=E3=83=AB=E3=82=92=E5=A4=89=E3=81=88?= =?UTF-8?q?=E3=81=A6=E3=81=BF=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client_shutdown.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker_client_shutdown.py b/dislocker_client_shutdown.py index 7411b3b..eb67e06 100644 --- a/dislocker_client_shutdown.py +++ b/dislocker_client_shutdown.py @@ -58,7 +58,7 @@ class App(tkinter.Tk): self.title(f"{app_name} | てすと") self.iconbitmap(default=resource_path + '\\icon\\dislocker.ico') - self.protocol("WM_DELETE_WINDOW", self.handler_close) + self.protocol("WM_SAVE_YOURSELF", self.handler_close) self.frame = tkinter.Frame(self) self.frame.grid(row=0, column=0, sticky='nsew') From 5488be9ec97562ba6c0459473122f3aba17b30f6 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 22:07:01 +0900 Subject: [PATCH 134/175] =?UTF-8?q?=E3=82=A2=E3=82=A4=E3=82=B3=E3=83=B3?= =?UTF-8?q?=E3=82=92=E5=A4=89=E6=9B=B4=E3=80=81=E3=82=A6=E3=82=A3=E3=83=B3?= =?UTF-8?q?=E3=83=89=E3=82=A6=E3=82=92=E5=AE=8C=E5=85=A8=E3=81=AB=E9=9D=9E?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client_shutdown.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dislocker_client_shutdown.py b/dislocker_client_shutdown.py index eb67e06..75f75b5 100644 --- a/dislocker_client_shutdown.py +++ b/dislocker_client_shutdown.py @@ -53,17 +53,17 @@ class App(tkinter.Tk): else: pass - self.geometry("100x100") + self.geometry("160x100") - self.title(f"{app_name} | てすと") - self.iconbitmap(default=resource_path + '\\icon\\dislocker.ico') + self.title(f"{app_name} | シャットダウンを監視") + self.iconbitmap(default=resource_path + '\\icon\\dislocker_shutdown.ico') self.protocol("WM_SAVE_YOURSELF", self.handler_close) self.frame = tkinter.Frame(self) self.frame.grid(row=0, column=0, sticky='nsew') - self.iconify() + self.deiconify() def delete_appdata(self, **kwargs): process_name = kwargs["process_name"] From b9b55ac2f93c9394e66c079f864e74518f1df19a Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sun, 29 Sep 2024 22:13:39 +0900 Subject: [PATCH 135/175] =?UTF-8?q?withdraw=E3=81=A7=E3=82=A6=E3=82=A3?= =?UTF-8?q?=E3=83=B3=E3=83=89=E3=82=A6=E3=82=92=E5=AE=8C=E5=85=A8=E3=81=AB?= =?UTF-8?q?=E9=9D=9E=E8=A1=A8=E7=A4=BA=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client_shutdown.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker_client_shutdown.py b/dislocker_client_shutdown.py index 75f75b5..b7e5419 100644 --- a/dislocker_client_shutdown.py +++ b/dislocker_client_shutdown.py @@ -63,7 +63,7 @@ class App(tkinter.Tk): self.frame = tkinter.Frame(self) self.frame.grid(row=0, column=0, sticky='nsew') - self.deiconify() + self.withdraw() def delete_appdata(self, **kwargs): process_name = kwargs["process_name"] From 9474eaa73931b517b9766fbd535874a8533fdad3 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Mon, 30 Sep 2024 17:26:56 +0900 Subject: [PATCH 136/175] =?UTF-8?q?=E6=96=87=E5=AD=97=E3=80=81=E6=96=87?= =?UTF-8?q?=E5=AD=97=E3=81=A4=E3=81=8D=E3=83=AD=E3=82=B4=E3=81=AE=E7=94=BB?= =?UTF-8?q?=E5=83=8F=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/icon/dislocker_label.png | Bin 0 -> 6773 bytes resource/icon/dislocker_label_logo.png | Bin 0 -> 44570 bytes resource/icon/dislocker_label_logo_white.png | Bin 0 -> 44982 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 resource/icon/dislocker_label.png create mode 100644 resource/icon/dislocker_label_logo.png create mode 100644 resource/icon/dislocker_label_logo_white.png diff --git a/resource/icon/dislocker_label.png b/resource/icon/dislocker_label.png new file mode 100644 index 0000000000000000000000000000000000000000..a162ef718913b248b2e997fc6c479a86abefb2b0 GIT binary patch literal 6773 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 literal 0 HcmV?d00001 diff --git a/resource/icon/dislocker_label_logo.png b/resource/icon/dislocker_label_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ff7917c3f4ad78c50f8087353fed4c3f7af46db1 GIT binary patch literal 44570 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 F{{v1(pVR;V literal 0 HcmV?d00001 diff --git a/resource/icon/dislocker_label_logo_white.png b/resource/icon/dislocker_label_logo_white.png new file mode 100644 index 0000000000000000000000000000000000000000..03fdff32de9d3cbb80bfda9ff6c773399dd58a9b GIT binary patch literal 44982 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 z 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 J6_O@F{~!HQf`9-3 literal 0 HcmV?d00001 From 12415c9674be1cddc0bc960fe9bdf516834acb25 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Mon, 30 Sep 2024 17:32:44 +0900 Subject: [PATCH 137/175] =?UTF-8?q?dislocker=5Fslash.py=E3=81=8B=E3=82=89?= =?UTF-8?q?=E7=A7=BB=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 1467 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 938 insertions(+), 529 deletions(-) diff --git a/dislocker.py b/dislocker.py index 19a7cfc..8b01d1d 100644 --- a/dislocker.py +++ b/dislocker.py @@ -1,14 +1,14 @@ -import json import discord -from discord import Interaction, TextStyle, app_commands -from discord.ui import TextInput, View, Modal import os +import json import psycopg2 from psycopg2 import sql -import hashlib +from datetime import datetime, timedelta +import asyncio import string import random -from datetime import datetime, timedelta +import hashlib +import openpyxl from openpyxl import Workbook import threading import time @@ -37,6 +37,7 @@ class DL(): "password": "password" }, "bot": { + "server_id": ["TYPE HERE SERVER ID (YOU MUST USE INT !!!!)"], "token": "TYPE HERE BOTS TOKEN KEY", "activity": { "name": "Dislocker", @@ -53,7 +54,7 @@ class DL(): "fstop_time": "21:00:00" }, "preset_games": ["TEST1", "TEST2", "TEST3", "TEST4", "TEST5"], - "admin_user_id": "TYPE HERE CHANNEL ID (YOU MUST USE INT !!!!)", + "admin_user_id": ["TYPE HERE CHANNEL ID (YOU MUST USE INT !!!!)"], "debug": False } } @@ -66,7 +67,6 @@ class DL(): 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 ディレクトリが見つかりません... 作成します。") @@ -76,6 +76,10 @@ class DL(): print("log ディレクトリが見つかりません... 作成します。") os.mkdir(self.log_dir_path) + if os.path.isfile(self.onetime_config_path): + print("ワンタイムパスワードが見つかりました。削除します。") + os.remove(self.onetime_config_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" @@ -100,7 +104,7 @@ class DL(): 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_member_id INTEGER, password_hash VARCHAR(64), pc_uuid VARCHAR(36), pc_token VARCHAR(36), master_password VARCHAR(16), detail TEXT, PRIMARY KEY (pc_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") + cursor.execute("CREATE TABLE pc_list (pc_number INTEGER NOT NULL, using_member_id INTEGER, password_hash VARCHAR(64), pc_uuid VARCHAR(36), pc_token VARCHAR(36), master_password VARCHAR(16), detail TEXT, alt_name TEXT, PRIMARY KEY (pc_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") for i in self.pc_list: print(i) cursor.execute("INSERT INTO pc_list (pc_number) VALUES (%s)", (i,)) @@ -110,7 +114,7 @@ class DL(): find_keyboard_list_table = cursor.fetchall() print(find_keyboard_list_table) if find_keyboard_list_table[0][0] == False: - cursor.execute("CREATE TABLE keyboard_list (keyboard_number INTEGER NOT NULL, using_member_id INTEGER, device_instance_path TEXT, device_name TEXT, detail TEXT, PRIMARY KEY (keyboard_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") + cursor.execute("CREATE TABLE keyboard_list (keyboard_number INTEGER NOT NULL, using_member_id INTEGER, device_instance_path TEXT, device_name TEXT, detail TEXT, alt_name TEXT, PRIMARY KEY (keyboard_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") for i in self.keyboard_list: print(i) cursor.execute("INSERT INTO keyboard_list (keyboard_number) VALUES (%s)", (i,)) @@ -120,7 +124,7 @@ class DL(): find_mouse_list_table = cursor.fetchall() print(find_mouse_list_table) if find_mouse_list_table[0][0] == False: - cursor.execute("CREATE TABLE mouse_list (mouse_number INTEGER NOT NULL, using_member_id INTEGER, device_instance_path TEXT, device_name TEXT, detail TEXT, PRIMARY KEY (mouse_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") + cursor.execute("CREATE TABLE mouse_list (mouse_number INTEGER NOT NULL, using_member_id INTEGER, device_instance_path TEXT, device_name TEXT, detail TEXT, alt_name TEXT, PRIMARY KEY (mouse_number), FOREIGN KEY (using_member_id) REFERENCES club_member(member_id))") for i in self.mouse_list: print(i) cursor.execute("INSERT INTO mouse_list (mouse_number) VALUES (%s)", (i,)) @@ -183,10 +187,7 @@ class DL(): w.write(detail) except: print("LOGGING ERROR mode w") - - -class Bot(discord.Client): - + def password_generate(self, length): numbers = string.digits # (1) password = ''.join(random.choice(numbers) for _ in range(length)) # (2) @@ -200,7 +201,7 @@ class Bot(discord.Client): try: discord_user_id = str(kwargs["discord_user_id"]) - cursor = dislocker.db.cursor() + cursor = self.db.cursor() cursor.execute("SELECT * FROM club_member WHERE discord_user_id = %s", (discord_user_id,)) user_record = cursor.fetchall() @@ -215,8 +216,8 @@ class Bot(discord.Client): return {"result": 1, "about": "user_data_not_found"} except Exception as error: - dislocker.log(title=f"[ERROR] ユーザーの登録状態を調査中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} + self.log(title=f"[ERROR] ユーザーの登録状態を調査中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} finally: cursor.close() @@ -236,7 +237,7 @@ class Bot(discord.Client): else: member_id = None - cursor = dislocker.db.cursor() + cursor = self.db.cursor() if pc_number != None: # pc番号を指定してpc_listから探す @@ -283,8 +284,8 @@ class Bot(discord.Client): return {"result": 1, "about": "search_options_error"} except Exception as error: - dislocker.log(title=f"[ERROR] PCの使用状況を調査中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} + self.log(title=f"[ERROR] PCの使用状況を調査中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} finally: if cursor: @@ -292,7 +293,7 @@ class Bot(discord.Client): def keyboard_used_check(self, **kwargs): try: - cursor = dislocker.db.cursor() + cursor = self.db.cursor() if kwargs["keyboard_number"] == None: return {"result": 0, "about": "ok"} else: @@ -306,8 +307,8 @@ class Bot(discord.Client): return {"result": 1, "about": "keyboard_already_in_use_by_other"} except Exception as error: - dislocker.log(title=f"[ERROR] キーボードの使用状況を調査中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} + self.log(title=f"[ERROR] キーボードの使用状況を調査中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} finally: if cursor: @@ -315,7 +316,7 @@ class Bot(discord.Client): def mouse_used_check(self, **kwargs): try: - cursor = dislocker.db.cursor() + cursor = self.db.cursor() if kwargs["mouse_number"] == None: return {"result": 0, "about": "ok"} else: @@ -329,8 +330,8 @@ class Bot(discord.Client): return {"result": 1, "about": "mouse_already_in_use_by_other"} except Exception as error: - dislocker.log(title=f"[ERROR] マウスの使用状況を調査中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} + self.log(title=f"[ERROR] マウスの使用状況を調査中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} finally: if cursor: @@ -339,14 +340,14 @@ class Bot(discord.Client): def register(self, **kwargs): try: - cursor = dislocker.db.cursor() + cursor = self.db.cursor() user_info = { "id": str(kwargs["discord_user_id"]), "name": str(kwargs["name"]), "display_name": str(kwargs["display_name"]), "pc_number": int(kwargs["pc_number"]), - "keyboard_number": None, - "mouse_number": None, + "keyboard_number": 0, + "mouse_number": 0, "detail": None } if "detail" in kwargs: @@ -389,16 +390,16 @@ class Bot(discord.Client): # PCリストの該当のレコードを更新 cursor.execute("UPDATE pc_list SET using_member_id = %s, password_hash = %s WHERE pc_number = %s", (member_id, password_hash, user_info["pc_number"])) # キーボードリストの該当のレコードを自前(None)だったらスキップ、借りていたら更新 - if user_info["keyboard_number"] == None: + if user_info["keyboard_number"] == 0: pass else: cursor.execute("UPDATE keyboard_list SET using_member_id = %s WHERE keyboard_number = %s", (member_id, user_info["keyboard_number"])) # マウスも同様に - if user_info["mouse_number"] == None: + if user_info["mouse_number"] == 0: pass else: cursor.execute("UPDATE mouse_list SET using_member_id = %s WHERE mouse_number = %s", (member_id, user_info["mouse_number"])) - dislocker.db.commit() + self.db.commit() return {"result": 0, "about": "ok", "output_dict": {"password": str(password), "name": str(name)}} else: return {"result": 1, "about": "mouse_already_in_use"} @@ -411,8 +412,8 @@ class Bot(discord.Client): else: return {"result": 1, "about": "user_data_not_found"} except Exception as error: - dislocker.log(title=f"[ERROR] PCの使用登録中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} + self.log(title=f"[ERROR] PCの使用登録中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} finally: if cursor: cursor.close() @@ -420,7 +421,7 @@ class Bot(discord.Client): def stop(self, **kwargs): try: - cursor = dislocker.db.cursor() + cursor = self.db.cursor() discord_user_id = str(kwargs["discord_user_id"]) if "bot_about" in kwargs: bot_about = kwargs["bot_about"] @@ -462,7 +463,7 @@ class Bot(discord.Client): else: # mouse_listの使用中ユーザーを消す cursor.execute("UPDATE mouse_list SET using_member_id = NULL WHERE mouse_number = %s", (mouse_number,)) - dislocker.db.commit() + self.db.commit() return {"result": 0, "about": "ok", "output_dict": {"pc_number": str(pc_number), "name": str(name)}} else: return {"result": 1, "about": "unused"} @@ -472,8 +473,8 @@ class Bot(discord.Client): return {"result": 1, "about": "user_data_not_found"} except Exception as error: - dislocker.log(title=f"[ERROR] PCの使用停止処理中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} + self.log(title=f"[ERROR] PCの使用停止処理中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} finally: if cursor: @@ -484,19 +485,19 @@ class Bot(discord.Client): 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 = self.db.cursor() cursor.execute("SELECT * FROM club_member WHERE discord_user_id = %s", (discord_user_id,)) user_record = cursor.fetchall() if not user_record: cursor.execute("INSERT INTO club_member (name, discord_user_name, discord_user_id) VALUES (%s, %s, %s)", (name, discord_user_name, discord_user_id)) - dislocker.db.commit() + self.db.commit() return {"result": 0, "about": "ok"} else: return {"result": 1, "about": "already_exists"} except Exception as error: - dislocker.log(title=f"[ERROR] ユーザー情報の登録中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} + self.log(title=f"[ERROR] ユーザー情報の登録中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} finally: if cursor: @@ -511,19 +512,19 @@ class Bot(discord.Client): def pc_register(self, **kwargs): try: pc_number = int(kwargs["pc_number"]) - cursor = dislocker.db.cursor() + cursor = self.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() + self.db.commit() return {"result": 0, "about": "ok"} else: return {"result": 1, "about": "already_exists"} except Exception as error: - dislocker.log(title=f"[ERROR] PCの情報を登録中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} + self.log(title=f"[ERROR] PCの情報を登録中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} finally: if cursor: @@ -531,11 +532,11 @@ class Bot(discord.Client): def report_export(self, **kwargs): try: - cursor = dislocker.db.cursor() - csv_file_path = dislocker.export_dir_path + "pc_usage_history.csv" + cursor = self.db.cursor() + csv_file_path = self.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" + excel_file_path = self.export_dir_path + "pc_usage_history.xlsx" # メインテーブルの列情報を取得(user_idを除く) cursor.execute(sql.SQL("SELECT * FROM {} LIMIT 0").format(sql.Identifier(main_table))) @@ -588,13 +589,13 @@ class Bot(discord.Client): # Excelファイルを保存 wb.save(excel_file_path) - dislocker.log(title=f"[SUCCESS] PCの使用履歴をエクスポートしました。", message=f"ファイルパス | {excel_file_path}", flag=0) + self.log(title=f"[SUCCESS] PCの使用履歴をエクスポートしました。", message=f"ファイルパス | {excel_file_path}", flag=0) return {"result": 0, "about": "ok", "file_path": excel_file_path} except Exception as error: - dislocker.log(title=f"[ERROR] PCの使用履歴をエクスポート中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} + self.log(title=f"[ERROR] PCの使用履歴をエクスポート中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} finally: if cursor: @@ -604,7 +605,7 @@ class Bot(discord.Client): def force_stop(self, **kwargs): try: pc_number = kwargs["pc_number"] - cursor = dislocker.db.cursor() + cursor = self.db.cursor() if "bot_about" in kwargs: bot_about = kwargs["bot_about"] cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s", (pc_number,)) @@ -635,521 +636,298 @@ class Bot(discord.Client): # mouse_listの使用中ユーザーを消す cursor.execute("UPDATE mouse_list SET using_member_id = NULL WHERE mouse_number = %s", (mouse_number,)) cursor.execute("UPDATE pc_usage_history SET end_use_time = clock_timestamp(), bot_about = %s WHERE id = %s", (bot_about, pc_usage_history_record_id)) - dislocker.db.commit() + self.db.commit() return {"result": 0, "about": "ok"} else: return {"result": 1, "about": "bot_about_not_found"} except Exception as error: - dislocker.log(title=f"[ERROR] fstop中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - return {"result": 1, "about": "error"} + self.log(title=f"[ERROR] fstop中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} finally: if cursor: cursor.close() + + def pc_onetime_gen(self, **kwargs): + if kwargs.get("max_count") == None: + max_count = 1 + elif isinstance(kwargs.get("max_count"), int): + max_count = int(kwargs.get("max_count")) + else: + max_count = 1 - async def timeout_notify(self, **kwargs): try: - pc_number = kwargs["pc_number"] - discord_display_name = kwargs["discord_display_name"] - - await self.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':negative_squared_cross_mark: {discord_display_name} さんのPC {pc_number} の使用登録はタイムアウトにより解除されました。') - return {"result": 0, "result": "ok"} - + if os.path.isfile(dislocker.onetime_config_path): + with open(dislocker.onetime_config_path, "r") as r: + onetime_config = json.load(r) + onetime_password = onetime_config["onetime"]["pc_register"]["password"] + if onetime_password == None: + onetime_password = str(self.password_generate(8)) + onetime_config["onetime"]["pc_register"]["password"] = onetime_password + onetime_config["onetime"]["pc_register"]["max_count"] = max_count + onetime_config["onetime"]["pc_register"]["current_count"] = 0 + current_count = onetime_config["onetime"]["pc_register"]["current_count"] + with open(dislocker.onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) + return {"result": 0, "about": "ok", "output_dict": {"onetime_password": onetime_password, "current_count": current_count, "max_count": max_count}} + else: + current_count = onetime_config["onetime"]["pc_register"]["current_count"] + max_count = onetime_config["onetime"]["pc_register"]["max_count"] + return {"result": 1, "about": "already_exists", "output_dict": {"onetime_password": onetime_password, "current_count": current_count, "max_count": max_count}} + else: + onetime_password = str(self.password_generate(8)) + onetime_config = { + "onetime": { + "pc_register": { + "password": onetime_password, + "current_count": 0, + "max_count": int(max_count) + }, + "device_register": { + "password": None, + "current_count": None, + "max_count": None + } + } + } + current_count = onetime_config["onetime"]["pc_register"]["current_count"] + with open(dislocker.onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) + return {"result": 0, "about": "ok", "output_dict": {"onetime_password": onetime_password, "current_count": current_count, "max_count": max_count}} + except Exception as error: - print("自動停止処理中にエラーが発生しました。\nエラー内容") - print(str(error.__class__.__name__)) - print(str(error.args)) - print(str(error)) - return {"result": 1, "about": "error"} + self.log(title=f"[ERROR] PC登録用のワンタイムパスワード発行中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} - - async def on_ready(self): - dislocker.log(title=f"[SUCCESS] DiscordのBotが起動しました。", flag=1) - 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): - if message.author.bot: - pass - - elif isinstance(message.channel, discord.DMChannel): - if message.author.id == dislocker.server_config["bot"]["admin_user_id"]: - msg_split = message.content.split() - if msg_split[0] == "/pcreg": - if os.path.isfile(dislocker.onetime_config_path): - with open(dislocker.onetime_config_path, "r") as r: - onetime_config = json.load(r) - onetime = onetime_config["onetime"]["pc_register"] - if onetime == None: - onetime = str(self.password_generate(8)) - onetime_config["onetime"]["pc_register"] = onetime - with open(dislocker.onetime_config_path, "w") as w: - json.dump(onetime_config, w, indent=4) - await message.channel.send(f"# :dizzy_face: PC登録時のワンタイムパスワードを発行します。\n# パスワード | {onetime}") - else: - await message.channel.send(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {onetime}") - else: - onetime = str(self.password_generate(8)) - onetime_config = { - "onetime": { - "pc_register": onetime, - "device_register": None - } - } - with open(dislocker.onetime_config_path, "w") as w: - json.dump(onetime_config, w, indent=4) - await message.channel.send(f"# :dizzy_face: PC登録時のワンタイムパスワードを発行します。\n# パスワード | {onetime}") - elif msg_split[0] == "/devreg": - if os.path.isfile(dislocker.onetime_config_path): - with open(dislocker.onetime_config_path, "r") as r: - onetime_config = json.load(r) - onetime = onetime_config["onetime"]["device_register"] - if onetime == None: - onetime = str(self.password_generate(8)) - onetime_config["onetime"]["device_register"] = onetime - with open(dislocker.onetime_config_path, "w") as w: - json.dump(onetime_config, w, indent=4) - await message.channel.send(f"# :dizzy_face: デバイス登録時のワンタイムパスワードを発行します。\n# パスワード | {onetime}") - else: - await message.channel.send(f"# :dizzy_face: 既にワンタイムパスワードは発行されています。\n# パスワード | {onetime}") - else: - onetime = str(self.password_generate(8)) - onetime_config = { - "onetime": { - "pc_register": None, - "device_register": onetime - } - } - with open(dislocker.onetime_config_path, "w") as w: - json.dump(onetime_config, w, indent=4) - await message.channel.send(f"# :dizzy_face: デバイス登録時のワンタイムパスワードを発行します。\n# パスワード | {onetime}") - - else: - await message.channel.send("# :warning: DMでの応答は、現在無効化されています。") - else: - await message.channel.send("# :warning: DMでの応答は、現在無効化されています。") - - """ - if msg_split[0] == "/password" or msg_split[0] == "/start": - #メッセージの要素が2つ以下の場合は拒否 - if len(msg_split) <= 2: - await message.channel.send("# :warning: PC番号、もしくはデバイス番号が入力されていません。") - #メッセージの要素が3つ以上の場合 - elif len(msg_split) >= 3: - #番号が数字であることを確認 - if msg_split[1].isdigit() and msg_split[2].isdigit(): - #PC番号が1以上10以下であることを確認 - if int(msg_split[1]) <= 10 and int(msg_split[1]) >= 1: - if len(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]) - elif len(msg_split) == 4: - 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 len(msg_split) == 3: - 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: - 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(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": - await message.channel.send("# :dizzy_face: ユーザーとして登録されていないようです。\n最初にサーバーで登録を行ってください。") - elif register["result"] == "pc_already_in_use_by_you": - 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": - await message.channel.send(f"# :man_gesturing_no: そのPCは他のメンバーによって使用されています。\n別のPC番号を指定して、再度お試しください。") - else: - await message.channel.send("# :dizzy_face: 番号がおかしいようです。") - else: - await message.channel.send("# :dizzy_face: 指定された番号は不正です。") - - elif msg_split[0] == "/stop": - stop = self.stop(user_id=message.author.id) - if stop["result"] == "unused": - await message.channel.send("# :shaking_face: 使用されていないようです...") - elif stop["result"] == "ok": - await message.channel.send(f":white_check_mark: 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 == dislocker.server_config["bot"]["config_channel_id"]: - msg_split = message.content.split() - if msg_split[0] == "/register": - 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) - if register["about"] == "ok": - await message.channel.send(f"# :white_check_mark: ユーザー情報が登録されました。\n>>> ユーザー名:{message.author.display_name}") - dislocker.log(title=f"[INFO] ユーザー情報が登録されました。", message=f"名前 | {message.author.display_name}, DiscordユーザーID | {message.author.id}, Discordユーザーネーム | {message.author.name}", flag=0) - elif register["about"] == "already_exists": - await message.channel.send("# :no_entry: 登録できませんでした。\nもう登録されている可能性があります。") - dislocker.log(title=f"[INFO] 既存のユーザー情報は登録されませんでした。", message=f"名前 | {message.author.display_name}, DiscordユーザーID | {message.author.id}, Discordユーザーネーム | {message.author.name}", flag=0) - else: - await message.channel.send("# :no_entry: 登録できませんでした。\n内部エラーが発生しています。") - dislocker.log(title=f"[INFO] 登録できなかったユーザーの情報です。", message=f"名前 | {message.author.display_name}, DiscordユーザーID | {message.author.id}, Discordユーザーネーム | {message.author.name}", flag=1) - - elif len(msg_split) <= 3: - await message.channel.send("# :japanese_goblin: 入力内容に不備があります。\n名前、Discordのユーザー名、DiscordのユーザーIDのいずれかが入力されていません。") - elif len(msg_split) == 4: - 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]) - if register["about"] == "ok": - 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["about"] == "already_exists": - await message.channel.send("# :skull_crossbones: 登録できませんでした。\nそのDiscordアカウントはすでに登録されています。") - else: - await message.channel.send("# :skull_crossbones: 登録できませんでした。\nDiscordのユーザーIDが不正です。") - else: - await message.channel.send("# :skull_crossbones: 登録できませんでした。\n内部エラーが発生しています。") - - elif msg_split[0] == "/export": - export = self.report_export() - if export["about"] == "ok": - await message.channel.send("# :page_facing_up: 使用履歴のレポートです。", file=discord.File(export["file_path"])) - pass - elif export["about"] == "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["about"] == "ok": - await message.channel.send(f"# :white_check_mark: PC番号 {msg_split[1]} の使用登録を解除しました。") - elif fstop["about"] == "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["about"] == "ok": - await message.channel.send(f"# :white_check_mark: PCを登録しました。\n>>> # PC番号 | {msg_split[1]}") - elif pc_register["about"] == "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番号") - - # /init ボタン月のメッセージ一式を指定チャンネルに送信 - elif msg_split[0] == "/init": - 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) - - 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) - - pc_button_view = View(timeout=None) - for i in dislocker.pc_list: - pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"mouseregister_{str(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) - dislocker.log(title=f"[INFO] サーバーで初回処理を実行しました。", flag=0) - - # /registerbutton PCの選択ボタンを指定チャンネルに送信 - elif msg_split[0] == "/registerbutton": - pc_button_view = View(timeout=None) - for i in dislocker.pc_list: - pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"mouseregister_{str(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) - dislocker.log(title=f"[INFO] サーバーでPC番号のボタンを送信しました。", flag=0) - - # /stopbutton 利用停止のボタンを指定チャンネルに送信 - 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) - dislocker.log(title=f"[INFO] サーバーでPCの使用停止ボタンを送信しました。", flag=0) - - # /userbutton ユーザー登録のボタンを指定チャンネルに送信 - 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) - dislocker.log(title=f"[INFO] サーバーでユーザー登録ボタンを送信しました。", flag=0) - - - elif message.channel.id == dislocker.server_config["bot"]["config_public_channel_id"]: - msg_split = message.content.split() - if msg_split[0] == "/register": - 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) - if register["about"] == "ok": - await message.channel.send(f"# :white_check_mark: ユーザー情報が登録されました。\nユーザー名:{message.author.display_name}") - elif register["about"] == "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("_") - dislocker.log(title=f"[INFO] ボタンが押されました。", message=f"custom_id | {custom_id}, DiscordユーザーID | {interaction.user.id}", flag=0) - if custom_id_split[0] == "pcregister": - keyboard_register_view = View(timeout=15) - pc_number = custom_id_split[1] - for i in dislocker.keyboard_list: - keyboard_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"keyboardregister_{str(pc_number)}_{str(i)}") - keyboard_register_view.add_item(keyboard_register_button) - keyboard_not_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="キーボードは自前", custom_id=f"keyboardregister_{str(pc_number)}_own") - keyboard_register_view.add_item(keyboard_not_register_button) + def device_onetime_gen(self, **kwargs): + if kwargs.get("max_count") == None: + max_count = 1 + elif isinstance(kwargs.get("max_count"), int): + max_count = int(kwargs.get("max_count")) + else: + max_count = 1 - await interaction.response.send_message(f"# :keyboard: キーボードのデバイス番号を選んでください!\n>>> # PC番号 | {str(pc_number)}", view=keyboard_register_view, ephemeral=True) - - elif custom_id_split[0] == "keyboardregister": - mouse_register_view = View(timeout=15) - pc_number = custom_id_split[1] - keyboard_number = custom_id_split[2] - if keyboard_number == "own": - keyboard_number_show = "自前" - else: - keyboard_number_show = keyboard_number - for i in dislocker.mouse_list: - mouse_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_{str(i)}") - mouse_register_view.add_item(mouse_register_button) - mouse_not_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="マウスは自前", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_own") - mouse_register_view.add_item(mouse_not_register_button) - - await interaction.response.send_message(f"# :mouse_three_button: マウスのデバイス番号を選んでください!\n>>> # PC番号 | {str(pc_number)}\n# キーボード番号 | {str(keyboard_number_show)}", view=mouse_register_view, ephemeral=True) - - elif custom_id_split[0] == "mouseregister": - pc_number = custom_id_split[1] - - reason_register_view = View(timeout=15) - for i in dislocker.preset_games: - reason_quick_button = reason_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"reasonregister_{str(pc_number)}_quick_{str(i)}") - reason_register_view.add_item(reason_quick_button) - reason_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="使用目的を入力する", custom_id=f"reasonregister_{str(pc_number)}") - reason_register_view.add_item(reason_button) - - await interaction.response.send_message(f"# :regional_indicator_q: 使用目的を書いてください!\n>>> # PC番号 | {str(pc_number)}", view=reason_register_view, ephemeral=True) - - elif custom_id_split[0] == "reasonregister": - pc_number = custom_id_split[1] - keyboard_number = "own" - mouse_number = "own" - - if len(custom_id_split) >= 3: - if custom_id_split[2] == "quick": - reason = custom_id_split[3] - register = bot.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=reason) - if register["about"] == "ok": - await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## 使用目的 | {reason}", ephemeral=True) - await bot.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':white_check_mark: {register["output_dict"]["name"]} さんがPC {pc_number} の使用を開始しました。\n>>> ## PC番号 | {pc_number}\n## 使用目的 | {reason}') - dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, 使用目的 | {reason}", flag=0) - elif register["about"] == "pc_already_in_use_by_you": - pc_usage_history = register["pc_usage_history"] - if pc_usage_history["keyboard_number"] == None: - keyboard_number_show = "未認証" - elif pc_usage_history["keyboard_number"] == 0: - keyboard_number_show = "自前" - else: - keyboard_number_show = str(pc_usage_history["keyboard_number"]) - - if pc_usage_history["mouse_number"] == None: - mouse_number_show = "未認証" - elif pc_usage_history["mouse_number"] == 0: - mouse_number_show = "自前" - else: - mouse_number_show = str(pc_usage_history["mouse_number"]) - await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {keyboard_number_show}\n# マウス番号 | {mouse_number_show}\n# 使用開始時刻 | {pc_usage_history["start_time"]}", ephemeral=True) - #await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {keyboard_number_show}\n# マウス番号 | {mouse_number_show}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) - elif register["about"] == "pc_already_in_use_by_other": - await interaction.response.send_message(f"# :man_gesturing_no: そのPCは他のメンバーによって使用されています。\n別のPC番号を指定して、再度お試しください。", ephemeral=True) - elif register["about"] == "keyboard_already_in_use": - await interaction.response.send_message(f"# :man_gesturing_no: そのキーボードは他のメンバーによって使用されています。\n別のキーボードのデバイス番号を指定して、再度お試しください。", ephemeral=True) - elif register["about"] == "mouse_already_in_use": - await interaction.response.send_message(f"# :man_gesturing_no: そのマウスは他のメンバーによって使用されています。\n別のマウスのデバイス番号を指定して、再度お試しください。", ephemeral=True) - elif register["about"] == "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) - else: - await interaction.response.send_message("# :skull_crossbones: 登録できませんでした。\n内部エラーが発生しています。", ephemeral=True) - else: - reason_input_form = Reason(title="Dislocker | 登録", pc_number=str(pc_number), keyboard_number=str(keyboard_number), mouse_number=str(mouse_number)) - await interaction.response.send_modal(reason_input_form) - - elif custom_id_split[0] == "stop": - pc_stop = self.stop(discord_user_id=interaction.user.id) - stop_view = View(timeout=15) - if pc_stop["about"] == "unused": - await interaction.response.send_message("# :shaking_face: 使用されていないようです...", ephemeral=True) - elif pc_stop["about"] == "user_data_not_found": - await interaction.response.send_message("# :dizzy_face: ユーザーとして登録されていないようです。\n最初にサーバーで登録を行ってください。", ephemeral=True) - elif pc_stop["about"] == "ok": - await interaction.response.send_message(f":white_check_mark: PC番号 {pc_stop["output_dict"]["pc_number"]} の使用が終了されました。", ephemeral=True) - await self.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':negative_squared_cross_mark: {pc_stop["output_dict"]["name"]} さんがPC {pc_stop["output_dict"]["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": - 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["about"] == "ok": - await interaction.response.send_message(f"# :white_check_mark: ユーザー情報が登録されました。\n>>> ユーザー名:{interaction.user.display_name}", ephemeral=True) - elif user_register["about"] == "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() - - - def search(self): try: - time.sleep(self.init_wait_time) - while True: - cursor = dislocker.db.cursor() - cursor.execute("SELECT * FROM pc_list WHERE password_hash IS NOT NULL") + if os.path.isfile(dislocker.onetime_config_path): + with open(dislocker.onetime_config_path, "r") as r: + onetime_config = json.load(r) + onetime_password = onetime_config["onetime"]["device_register"]["password"] + if onetime_password == None: + onetime_password = str(self.password_generate(8)) + onetime_config["onetime"]["device_register"]["password"] = onetime_password + onetime_config["onetime"]["device_register"]["max_count"] = max_count + onetime_config["onetime"]["device_register"]["current_count"] = 0 + current_count = onetime_config["onetime"]["device_register"]["current_count"] + with open(dislocker.onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) + return {"result": 0, "about": "ok", "output_dict": {"onetime_password": onetime_password, "current_count": current_count, "max_count": max_count}} + else: + current_count = onetime_config["onetime"]["device_register"]["current_count"] + max_count = onetime_config["onetime"]["device_register"]["max_count"] + return {"result": 1, "about": "already_exists", "output_dict": {"onetime_password": onetime_password, "current_count": current_count, "max_count": max_count}} + else: + onetime_password = str(self.password_generate(8)) + onetime_config = { + "onetime": { + "pc_register": { + "password": None, + "current_count": None, + "max_count": None + }, + "device_register": { + "password": onetime_password, + "current_count": 0, + "max_count": int(max_count) + } + } + } + current_count = onetime_config["onetime"]["device_register"]["current_count"] + with open(dislocker.onetime_config_path, "w") as w: + json.dump(onetime_config, w, indent=4) + return {"result": 0, "about": "ok", "output_dict": {"onetime_password": onetime_password, "current_count": current_count, "max_count": max_count}} + + except Exception as error: + self.log(title=f"[ERROR] デバイス登録用のワンタイムパスワード発行中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} + + def show_pc_master_password(self, **kwargs): + try: + if isinstance(kwargs.get("pc_number"), int): + pc_number = int(kwargs.get("pc_number")) + + cursor = self.db.cursor() + cursor.execute("SELECT master_password FROM pc_list WHERE pc_number = %s", (pc_number,)) + pc_master_password_list = cursor.fetchall() + pc_master_password = pc_master_password_list[0][0] + + return {"result": 0, "about": "ok", "output_dict": {"pc_master_password": pc_master_password}} + + else: + return {"result": 1, "about": "syntax_error"} + except Exception as error: + self.log(title=f"[ERROR] PCのマスターパスワードを取得中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} + + finally: + if cursor: + cursor.close() + + def get_discord_user_id(self, **kwargs): + try: + member_id = int(kwargs["member_id"]) + cursor = self.db.cursor() + cursor.execute("SELECT discord_user_id FROM club_member WHERE member_id = %s", (member_id,)) + discord_user_id_list = cursor.fetchall() + discord_user_id = discord_user_id_list[0][0] + return {"result": 0, "about": "ok", "discord_user_id": discord_user_id} + + except Exception as error: + self.log(title=f"[ERROR] DiscordのユーザーIDの取得中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} + + finally: + if cursor: + cursor.close() + + def get_pc_list(self, **kwargs): + try: + cursor = self.db.cursor() + + if "pc_number" in kwargs: + pc_number = int(kwargs["pc_number"]) + cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s", (pc_number,)) pc_list = cursor.fetchall() - current_datetime = datetime.now() - fstop_time = self.fstop_time - if current_datetime.time().strftime("%H:%M:%S") == fstop_time: - dislocker.log(title=f"[INFO] 定期のPCの使用停止処理を開始します。", flag=0) - for i in dislocker.pc_list: - stop = bot.force_stop(pc_number=i, bot_about="使用停止忘れによるBotによる強制停止。") - result = {"result": "FSTOP"} - dislocker.log(title=f"[SUCCESS] 定期のPCの使用停止処理は完了しました。", flag=0) - else: - if pc_list: - if len(pc_list) == 1: - member_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", (member_id,)) - pc_usage = cursor.fetchall() - start_time = pc_usage[0][5] - time_difference = current_datetime - start_time - dislocker.log(title=f"[INFO] 現在確認されているパスワード未使用のユーザー", message=f"レコード | {str(pc_usage)}, 経過時間(Sec) | {time_difference.seconds}/{timedelta(seconds=self.allowable_time).seconds}", flag=0) - if time_difference.seconds >= timedelta(seconds=self.allowable_time).seconds: - cursor.execute("SELECT * FROM club_member WHERE member_id = %s", (member_id,)) - user_info = cursor.fetchall() - stop = bot.stop(discord_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]) - dislocker.log(title=f"[INFO] パスワードのタイムアウト時間に達したため、強制停止されました。", flag=0) - result = {"result": "STOP", "details": str(pc_usage)} - else: - result = {"result": "BUT SAFE", "details": str(pc_usage)} - - - elif len(pc_list) >= 2: - for i in pc_list: - member_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", (member_id,)) - pc_usage = cursor.fetchall() - start_time = pc_usage[0][5] - time_difference = current_datetime - start_time - dislocker.log(title=f"[INFO] 現在確認されているパスワード未使用のユーザー", message=f"レコード | {str(pc_usage)}, 経過時間(Sec) | {time_difference.seconds}/{timedelta(seconds=self.allowable_time).seconds}", flag=0) - if time_difference.seconds >= timedelta(seconds=self.allowable_time).seconds: - cursor.execute("SELECT * FROM club_member WHERE member_id = %s", (member_id,)) - user_info = cursor.fetchall() - stop = bot.stop(discord_user_id=user_info[0][3], bot_about="タイムアウトでBotによる強制停止。") - - #bot.timeout_notify(pc_number=i[0], discord_display_name=user_info[0][1]) - dislocker.log(title=f"[INFO] パスワードのタイムアウト時間に達したため、強制停止されました。", flag=0) - result = {"result": "STOP", "details": str(pc_usage)} - else: - result = {"result": "BUT SAFE", "details": str(pc_usage)} - - else: - result = {"result": "NONE"} - else: - result = {"result": "NONE"} - - if result["result"] == "NONE": - pass - else: - pass - time.sleep(self.search_frequency) - + return {"result": 0, "about": "ok", "output_dict": {pc_number: {"pc_number": pc_list[0][0], "using_member_id": pc_list[0][1], "master_password": [0][5], "detail": pc_list[0][6], "alt_name": pc_list[0][7]}}} + else: + cursor.execute("SELECT * FROM pc_list ORDER BY pc_number") + pc_list = cursor.fetchall() + pc_list_base = {} + for i in pc_list: + pc_list_base[i[0]] = {"pc_number": i[0], "using_member_id": i[1], "master_password": i[5], "detail": i[6], "alt_name": i[7]} + + return {"result": 0, "about": "ok", "output_dict": pc_list_base} + except Exception as error: - dislocker.log(title=f"[ERROR] 自動停止処理中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) - result = {"result": "error"} - dislocker.db.rollback() - + self.log(title=f"[ERROR] PCリストの取得中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} + finally: if cursor: cursor.close() - return result + + def get_keyboard_list(self, **kwargs): + try: + cursor = self.db.cursor() + + if "keyboard_number" in kwargs: + keyboard_number = int(kwargs["keyboard_number"]) + cursor.execute("SELECT * FROM keyboard_list WHERE keyboard_number = %s", (keyboard_number,)) + keyboard_list = cursor.fetchall() + + return {"result": 0, "about": "ok", "output_dict": {keyboard_number: {"keyboard_number": keyboard_list[0][0], "using_member_id": keyboard_list[0][1], "device_instance_path": keyboard_list[0][2], "device_name": keyboard_list[0][3], "detail": keyboard_list[0][4], "alt_name": keyboard_list[0][5]}}} + else: + cursor.execute("SELECT * FROM keyboard_list ORDER BY keyboard_number") + keyboard_list = cursor.fetchall() + keyboard_list_base = {} + for i in keyboard_list: + if i[0] == 0: + pass + else: + keyboard_list_base[i[0]] = {"keyboard_number": i[0], "using_member_id": i[1], "device_instance_path": i[2], "device_name": i[3], "detail": i[4], "alt_name": i[5]} + + return {"result": 0, "about": "ok", "output_dict": keyboard_list_base} + except Exception as error: + self.log(title=f"[ERROR] キーボードリストの取得中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} + + finally: + if cursor: + cursor.close() + + def get_mouse_list(self, **kwargs): + try: + cursor = self.db.cursor() + + if "mouse_number" in kwargs: + mouse_number = int(kwargs["mouse_number"]) + cursor.execute("SELECT * FROM mouse_list WHERE mouse_number = %s", (mouse_number,)) + mouse_list = cursor.fetchall() + + return {"result": 0, "about": "ok", "output_dict": {mouse_number: {"mouse_number": mouse_list[0][0], "using_member_id": mouse_list[0][1], "device_instance_path": mouse_list[0][2], "device_name": mouse_list[0][3], "detail": mouse_list[0][4], "alt_name": mouse_list[0][5]}}} + else: + cursor.execute("SELECT * FROM mouse_list ORDER BY mouse_number") + mouse_list = cursor.fetchall() + mouse_list_base = {} + for i in mouse_list: + if i[0] == 0: + pass + else: + mouse_list_base[i[0]] = {"mouse_number": i[0], "using_member_id": i[1], "device_instance_path": i[2], "device_name": i[3], "detail": i[4], "alt_name": i[5]} + + return {"result": 0, "about": "ok", "output_dict": mouse_list_base} + + except Exception as error: + self.log(title=f"[ERROR] マウスリストの取得中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} + + finally: + if cursor: + cursor.close() + + def set_pc_nickname(self, **kwargs): + try: + cursor = self.db.cursor() + + if 'pc_number' in kwargs and 'alt_name' in kwargs: + pc_number = int(kwargs["pc_number"]) + alt_name = kwargs["alt_name"] + cursor.execute("UPDATE pc_list SET alt_name = %s WHERE pc_number = %s", (alt_name, pc_number)) + self.db.commit() + return {"result": 0, "about": "ok"} + else: + return {"result": 1, "about": "syntax_error"} + + except Exception as error: + self.log(title=f"[ERROR] PCのニックネームの設定中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} + + finally: + if cursor: + cursor.close() -class Reason(Modal): +class ReasonModal(discord.ui.Modal): def __init__(self, title: str, pc_number: str, keyboard_number: str, mouse_number: str, timeout=15) -> None: super().__init__(title=title, timeout=timeout) - self.reason_input_form = TextInput(label="使用目的を入力してください", style=TextStyle.short, custom_id=f"register_{pc_number}_{keyboard_number}_{mouse_number}") + self.reason_input_form = discord.ui.TextInput(label="使用目的を入力してください", style=discord.TextStyle.short, custom_id=f"register_{pc_number}_{keyboard_number}_{mouse_number}") self.add_item(self.reason_input_form) - async def on_submit(self, interaction: Interaction) -> None: + async def on_submit(self, interaction: discord.Interaction) -> None: custom_id = interaction.data["components"][0]["components"][0]["custom_id"] custom_id_split = custom_id.split("_") pc_number = custom_id_split[1] keyboard_number = custom_id_split[2] + mouse_number = custom_id_split[3] + if keyboard_number == "own": keyboard_number_show = "自前" else: keyboard_number_show = keyboard_number - mouse_number = custom_id_split[3] + if mouse_number == "own": mouse_number_show = "自前" else: mouse_number_show = mouse_number - register = bot.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=self.reason_input_form.value) + register = dislocker.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=self.reason_input_form.value) if register["about"] == "ok": - await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_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["output_dict"]["name"]} さんがPC {pc_number} の使用を開始しました。\n>>> ## PC番号 | {pc_number}\n## 使用目的 | {self.reason_input_form.value}') + await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\n## 使用目的 | {self.reason_input_form.value}", ephemeral=True) + await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':white_check_mark: {register["output_dict"]["name"]} さんがPC {pc_number} の使用を開始しました。\n>>> ## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\n## 使用目的 | {self.reason_input_form.value}') dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, 使用目的 | {self.reason_input_form.value}", flag=0) elif register["about"] == "pc_already_in_use_by_you": pc_usage_history = register["pc_usage_history"] @@ -1182,15 +960,646 @@ class Reason(Modal): await interaction.response.send_message("# :skull_crossbones: 登録できませんでした。\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() + + def search(self): + try: + time.sleep(self.init_wait_time) + while True: + 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: + dislocker.log(title=f"[INFO] 定期のPCの使用停止処理を開始します。", flag=0) + for i in dislocker.pc_list: + stop = dislocker.force_stop(pc_number=i, bot_about="使用停止忘れによるBotによる強制停止。") + result = {"result": "FSTOP"} + dislocker.log(title=f"[SUCCESS] 定期のPCの使用停止処理は完了しました。", flag=0) + else: + if pc_list: + if len(pc_list) == 1: + member_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", (member_id,)) + pc_usage = cursor.fetchall() + start_time = pc_usage[0][5] + time_difference = current_datetime - start_time + dislocker.log(title=f"[INFO] 現在確認されているパスワード未使用のユーザー", message=f"レコード | {str(pc_usage)}, 経過時間(Sec) | {time_difference.seconds}/{timedelta(seconds=self.allowable_time).seconds}", flag=0) + if time_difference.seconds >= timedelta(seconds=self.allowable_time).seconds: + cursor.execute("SELECT * FROM club_member WHERE member_id = %s", (member_id,)) + user_info = cursor.fetchall() + stop = dislocker.stop(discord_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]) + dislocker.log(title=f"[INFO] パスワードのタイムアウト時間に達したため、強制停止されました。", flag=0) + result = {"result": "STOP", "details": str(pc_usage)} + else: + result = {"result": "BUT SAFE", "details": str(pc_usage)} + + + elif len(pc_list) >= 2: + for i in pc_list: + member_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", (member_id,)) + pc_usage = cursor.fetchall() + start_time = pc_usage[0][5] + time_difference = current_datetime - start_time + dislocker.log(title=f"[INFO] 現在確認されているパスワード未使用のユーザー", message=f"レコード | {str(pc_usage)}, 経過時間(Sec) | {time_difference.seconds}/{timedelta(seconds=self.allowable_time).seconds}", flag=0) + if time_difference.seconds >= timedelta(seconds=self.allowable_time).seconds: + cursor.execute("SELECT * FROM club_member WHERE member_id = %s", (member_id,)) + user_info = cursor.fetchall() + stop = dislocker.stop(discord_user_id=user_info[0][3], bot_about="タイムアウトでBotによる強制停止。") + + #bot.timeout_notify(pc_number=i[0], discord_display_name=user_info[0][1]) + dislocker.log(title=f"[INFO] パスワードのタイムアウト時間に達したため、強制停止されました。", flag=0) + result = {"result": "STOP", "details": str(pc_usage)} + else: + result = {"result": "BUT SAFE", "details": str(pc_usage)} + + else: + result = {"result": "NONE"} + else: + result = {"result": "NONE"} + + if result["result"] == "NONE": + pass + else: + pass + time.sleep(self.search_frequency) + + + except Exception as error: + dislocker.log(title=f"[ERROR] 自動停止処理中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + result = {"result": "error"} + dislocker.db.rollback() + + finally: + if cursor: + cursor.close() + return result + dislocker = DL() + +intents = discord.Intents.default() +intents.message_content = True + +client = discord.Client(intents=intents) +tree = discord.app_commands.CommandTree(client) + +@client.event +async def on_ready(): + dislocker.log(title=f"[SUCCESS] DiscordのBotが起動しました。", message=f"{client.user.name} としてログインしています。", flag=1) + await tree.sync() + 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 client.change_presence(activity=dislocker_activity) + +@client.event +async def on_message(message): + if message.author.bot: + pass + + elif isinstance(message.channel, discord.DMChannel): + if message.author.id in dislocker.server_config["bot"]["admin_user_id"]: + msg_split = message.content.split() + + if msg_split[0] == "/pcreg": + max_count = 1 + if len(msg_split) == 2: + if msg_split[1].isdecimal(): + max_count = int(msg_split[1]) + + pc_onetime_password_gen = dislocker.pc_onetime_gen(max_count=max_count) + + if pc_onetime_password_gen["result"] == 0: + pc_onetime_password = str(pc_onetime_password_gen["output_dict"]["onetime_password"]) + pc_onetime_password_max_count = str(pc_onetime_password_gen["output_dict"]["max_count"]) + pc_onetime_password_current_count = str(pc_onetime_password_gen["output_dict"]["current_count"]) + pc_onetime_password_remaining_times = str(int(pc_onetime_password_max_count) - int(pc_onetime_password_current_count)) + + result_embed = discord.Embed(title=":dizzy_face: PCの登録", description=f"PC登録時のワンタイムパスワードを発行します。", color=0x2286C9) + result_embed.add_field(name="パスワード", value=pc_onetime_password) + result_embed.add_field(name="最大使用回数", value=pc_onetime_password_max_count) + result_embed.add_field(name="残り使用回数", value=pc_onetime_password_remaining_times) + + elif pc_onetime_password_gen["result"] == 1: + if pc_onetime_password_gen["about"] == "already_exists": + pc_onetime_password = str(pc_onetime_password_gen["output_dict"]["onetime_password"]) + pc_onetime_password_max_count = str(pc_onetime_password_gen["output_dict"]["max_count"]) + pc_onetime_password_current_count = str(pc_onetime_password_gen["output_dict"]["current_count"]) + pc_onetime_password_remaining_times = str(int(pc_onetime_password_max_count) - int(pc_onetime_password_current_count)) + + result_embed = discord.Embed(title=":dizzy_face: PCの登録", description=f"ワンタイムパスワードはもう発行されており、有効です。", color=0x2286C9) + result_embed.add_field(name="パスワード", value=pc_onetime_password) + result_embed.add_field(name="最大使用回数", value=pc_onetime_password_max_count) + result_embed.add_field(name="残り使用回数", value=pc_onetime_password_remaining_times) + + else: + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。', color=0xC91111) + result_embed.add_field(name=f"{pc_onetime_password_gen['output_dict']['error_class_name']}", value=f"{pc_onetime_password_gen['output_dict']['error_args']}") + + await message.channel.send(embed=result_embed) + + elif msg_split[0] == "/devreg": + max_count = 1 + if len(msg_split) == 2: + if msg_split[1].isdecimal(): + max_count = int(msg_split[1]) + + device_onetime_password_gen = dislocker.device_onetime_gen(max_count=max_count) + + if device_onetime_password_gen["result"] == 0: + device_onetime_password = str(device_onetime_password_gen["output_dict"]["onetime_password"]) + device_onetime_password_max_count = str(device_onetime_password_gen["output_dict"]["max_count"]) + device_onetime_password_current_count = str(device_onetime_password_gen["output_dict"]["current_count"]) + device_onetime_password_remaining_times = str(int(device_onetime_password_max_count) - int(device_onetime_password_current_count)) + + result_embed = discord.Embed(title=":dizzy_face: デバイスの登録", description=f"デバイス登録時のワンタイムパスワードを発行します。", color=0x2286C9) + result_embed.add_field(name="パスワード", value=device_onetime_password) + result_embed.add_field(name="最大使用回数", value=device_onetime_password_max_count) + result_embed.add_field(name="残り使用回数", value=device_onetime_password_remaining_times) + + elif device_onetime_password_gen["result"] == 1: + if device_onetime_password_gen["about"] == "already_exists": + device_onetime_password = str(device_onetime_password_gen["output_dict"]["onetime_password"]) + device_onetime_password_max_count = str(device_onetime_password_gen["output_dict"]["max_count"]) + device_onetime_password_current_count = str(device_onetime_password_gen["output_dict"]["current_count"]) + device_onetime_password_remaining_times = str(int(device_onetime_password_max_count) - int(device_onetime_password_current_count)) + result_embed = discord.Embed(title=":dizzy_face: デバイスの登録", description=f"ワンタイムパスワードはもう発行されており、有効です。", color=0x2286C9) + result_embed.add_field(name="パスワード", value=device_onetime_password) + result_embed.add_field(name="最大使用回数", value=device_onetime_password_max_count) + result_embed.add_field(name="残り使用回数", value=device_onetime_password_remaining_times) + + else: + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。', color=0xC91111) + result_embed.add_field(name=f"{device_onetime_password_gen['output_dict']['error_class_name']}", value=f"{device_onetime_password_gen['output_dict']['error_args']}") + + await message.channel.send(embed=result_embed) + + else: + result_embed = discord.Embed(title=":x: 警告", description=f'DMでの操作はサポートされていません。', color=0xC91111) + await message.channel.send(embed=result_embed) + else: + pass + +@client.event +async def on_interaction(interaction: discord.Interaction): + try: + if interaction.data["component_type"] == 2: + await on_button(interaction) + except KeyError: + pass + +async def on_button(interaction: discord.Interaction): + custom_id = interaction.data["custom_id"] + custom_id_split = custom_id.split("_") + dislocker.log(title=f"[INFO] ボタンが押されました。", message=f"custom_id | {custom_id}, DiscordユーザーID | {interaction.user.id}", flag=0) + + if custom_id_split[0] == "pcregister": + keyboard_register_view = discord.ui.View(timeout=15) + pc_number = custom_id_split[1] + keyboard_list = dislocker.get_keyboard_list() + + for i in keyboard_list["output_dict"].keys(): + current_keyboard_list = keyboard_list['output_dict'][i] + if current_keyboard_list['alt_name'] == None: + keyboard_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(current_keyboard_list['keyboard_number'])} 番", custom_id=f"keyboardregister_{str(pc_number)}_{str(current_keyboard_list['keyboard_number'])}") + else: + keyboard_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(current_keyboard_list['keyboard_number'])} 番 | ({current_keyboard_list['alt_name']})", custom_id=f"keyboardregister_{str(pc_number)}_{str(current_keyboard_list['keyboard_number'])}") + keyboard_register_view.add_item(keyboard_register_button) + + keyboard_not_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="キーボードは自前", custom_id=f"keyboardregister_{str(pc_number)}_own") + keyboard_register_view.add_item(keyboard_not_register_button) + + await interaction.response.send_message(f"# :keyboard: キーボードのデバイス番号を選んでください!\n>>> # PC番号 | {str(pc_number)}", view=keyboard_register_view, ephemeral=True) + + elif custom_id_split[0] == "keyboardregister": + mouse_register_view = discord.ui.View(timeout=15) + pc_number = custom_id_split[1] + keyboard_number = custom_id_split[2] + mouse_list = dislocker.get_mouse_list() + if keyboard_number == "own": + keyboard_number_show = "自前" + else: + keyboard_number_show = keyboard_number + + for i in mouse_list["output_dict"].keys(): + current_mouse_list = mouse_list['output_dict'][i] + if current_mouse_list['alt_name'] == None: + mouse_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(current_mouse_list['mouse_number'])} 番", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_{str(current_mouse_list['mouse_number'])}") + else: + mouse_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(current_mouse_list['mouse_number'])} 番 | ({current_mouse_list['alt_name']})", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_{str(current_mouse_list['mouse_number'])}") + mouse_register_view.add_item(mouse_register_button) + + mouse_not_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="マウスは自前", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_own") + mouse_register_view.add_item(mouse_not_register_button) + + await interaction.response.send_message(f"# :mouse_three_button: マウスのデバイス番号を選んでください!\n>>> # PC番号 | {str(pc_number)}\n# キーボード番号 | {str(keyboard_number_show)}", view=mouse_register_view, ephemeral=True) + + elif custom_id_split[0] == "mouseregister": + pc_number = custom_id_split[1] + keyboard_number = custom_id_split[2] + mouse_number = custom_id_split[3] + + if keyboard_number == "own": + keyboard_number_show = "自前" + else: + keyboard_number_show = keyboard_number + + if mouse_number == "own": + mouse_number_show = "自前" + else: + mouse_number_show = mouse_number + + reason_register_view = discord.ui.View(timeout=15) + for i in dislocker.preset_games: + reason_quick_button = reason_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(i)}", custom_id=f"quickreasonregister_{str(pc_number)}_{str(keyboard_number)}_{str(mouse_number)}_{str(i)}") + reason_register_view.add_item(reason_quick_button) + reason_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="使用目的を入力する", custom_id=f"reasonregister_{str(pc_number)}_{str(keyboard_number)}_{str(mouse_number)}") + reason_register_view.add_item(reason_button) + + await interaction.response.send_message(f"# :regional_indicator_q: 使用目的を書いてください!\n>>> # PC番号 | {str(pc_number)}\n# キーボード番号 | {str(keyboard_number_show)}\n# マウス番号 | {str(mouse_number_show)}", view=reason_register_view, ephemeral=True) + + elif custom_id_split[0] == "quickreasonregister": + pc_number = custom_id_split[1] + keyboard_number = custom_id_split[2] + mouse_number = custom_id_split[3] + + if keyboard_number == "own": + keyboard_number_show = "自前" + else: + keyboard_number_show = keyboard_number + + if mouse_number == "own": + mouse_number_show = "自前" + else: + mouse_number_show = mouse_number + + reason = custom_id_split[4] + + register = dislocker.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=reason) + if register["about"] == "ok": + await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## キーボード番号 | {str(keyboard_number_show)}\n## マウス番号 | {str(mouse_number_show)}\n## 使用目的 | {reason}", ephemeral=True) + await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':white_check_mark: {register["output_dict"]["name"]} さんがPC {pc_number} の使用を開始しました。\n>>> ## PC番号 | {pc_number}\n## 使用目的 | {reason}') + dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, 使用目的 | {reason}", flag=0) + elif register["about"] == "pc_already_in_use_by_you": + pc_usage_history = register["pc_usage_history"] + if pc_usage_history["keyboard_number"] == None: + keyboard_number_show = "未認証" + elif pc_usage_history["keyboard_number"] == 0: + keyboard_number_show = "自前" + else: + keyboard_number_show = str(pc_usage_history["keyboard_number"]) + + if pc_usage_history["mouse_number"] == None: + mouse_number_show = "未認証" + elif pc_usage_history["mouse_number"] == 0: + mouse_number_show = "自前" + else: + mouse_number_show = str(pc_usage_history["mouse_number"]) + await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {keyboard_number_show}\n# マウス番号 | {mouse_number_show}\n# 使用開始時刻 | {pc_usage_history["start_time"]}", ephemeral=True) + #await interaction.response.send_message(f"# :exploding_head: あなたはPCをもう使用されているようです。\n使用状態を解除するには 終了ボタン で使用終了をお知らせください。\n>>> # PC番号 | {pc_usage_history["pc_number"]}\n# キーボード番号 | {keyboard_number_show}\n# マウス番号 | {mouse_number_show}\n# 使用開始時刻 | {pc_usage_history["start_time"]}\n# 使用目的 | {pc_usage_history["use_detail"]}", ephemeral=True) + elif register["about"] == "pc_already_in_use_by_other": + await interaction.response.send_message(f"# :man_gesturing_no: そのPCは他のメンバーによって使用されています。\n別のPC番号を指定して、再度お試しください。", ephemeral=True) + elif register["about"] == "keyboard_already_in_use": + await interaction.response.send_message(f"# :man_gesturing_no: そのキーボードは他のメンバーによって使用されています。\n別のキーボードのデバイス番号を指定して、再度お試しください。", ephemeral=True) + elif register["about"] == "mouse_already_in_use": + await interaction.response.send_message(f"# :man_gesturing_no: そのマウスは他のメンバーによって使用されています。\n別のマウスのデバイス番号を指定して、再度お試しください。", ephemeral=True) + elif register["about"] == "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) + + elif custom_id_split[0] == "reasonregister": + pc_number = custom_id_split[1] + keyboard_number = custom_id_split[2] + mouse_number = custom_id_split[3] + + if keyboard_number == "own": + keyboard_number_show = "自前" + else: + keyboard_number_show = keyboard_number + + if mouse_number == "own": + mouse_number_show = "自前" + else: + mouse_number_show = mouse_number + + reason_input_form = ReasonModal(title="Dislocker | 登録", pc_number=str(pc_number), keyboard_number=str(keyboard_number), mouse_number=str(mouse_number)) + await interaction.response.send_modal(reason_input_form) + + elif custom_id_split[0] == "stop": + pc_stop = dislocker.stop(discord_user_id=interaction.user.id) + stop_view = discord.ui.View(timeout=15) + if pc_stop["about"] == "unused": + await interaction.response.send_message("# :shaking_face: 使用されていないようです...", ephemeral=True) + elif pc_stop["about"] == "user_data_not_found": + await interaction.response.send_message("# :dizzy_face: ユーザーとして登録されていないようです。\n最初にサーバーで登録を行ってください。", ephemeral=True) + elif pc_stop["about"] == "ok": + await interaction.response.send_message(f":white_check_mark: PC番号 {pc_stop["output_dict"]["pc_number"]} の使用が終了されました。", ephemeral=True) + await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':negative_squared_cross_mark: {pc_stop["output_dict"]["name"]} さんがPC {pc_stop["output_dict"]["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": + user_register = dislocker.user_register(name=interaction.user.display_name, discord_user_name=interaction.user.name, discord_user_id=interaction.user.id) + if user_register["about"] == "ok": + await interaction.response.send_message(f"# :white_check_mark: ユーザー情報が登録されました。\n>>> ユーザー名:{interaction.user.display_name}", ephemeral=True) + elif user_register["about"] == "already_exists": + await interaction.response.send_message("# :no_entry: 登録できませんでした。\nもう登録されている可能性があります。", ephemeral=True) + else: + await interaction.response.send_message("# :no_entry: 登録できませんでした。\n内部エラーが発生しています。", ephemeral=True) + + +#使用者側のスラッシュコマンド +@tree.command(name="use", description="パソコンの使用登録をします。通常はこのコマンドを使用する必要はありません。") +async def use(interaction: discord.Interaction, pc_number: int, keyboard_number: int, mouse_number: int, detail: str): + register = dislocker.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=detail) + if register["result"] == 0: + await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## 使用目的 | {detail}", ephemeral=True) + dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, 使用目的 | {detail}", flag=0) + await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':white_check_mark: {register["output_dict"]["name"]} さんがPC {pc_number} の使用を開始しました。\n>>> ## PC番号 | {pc_number}\n## 使用目的 | {detail}') + elif register["result"] == 1: + if register["about"] == "pc_already_in_use_by_other": + await interaction.response.send_message(":x: 他の方がそのPCを使用中です。", ephemeral=True) + elif register["about"] == "pc_already_in_use_by_you": + await interaction.response.send_message(f":x: あなたは既にPC {register['pc_usage_history']['pc_number']} を使用中です。\n>>> ## PC番号 | {register['pc_usage_history']['pc_number']}\n## 使用目的 | {register['pc_usage_history']['use_detail']}", ephemeral=True) + elif register["about"] == "keyboard_already_in_use": + await interaction.response.send_message(":x: キーボードは既に使用中です。", ephemeral=True) + elif register["about"] == "mouse_already_in_use": + await interaction.response.send_message(":x: マウスは既に使用中です。", ephemeral=True) + elif register["about"] == "user_data_not_found": + await interaction.response.send_message(":x: ユーザーデータが見つかりませんでした。", ephemeral=True) + elif register["about"] == "error": + await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) + +@tree.command(name="stop", description="パソコンの使用を終了します。通常はこのコマンドを使用する必要はありません。") +async def stop(interaction: discord.Interaction): + stop = dislocker.stop(discord_user_id=interaction.user.id) + if stop["result"] == 0: + await interaction.response.send_message(f":white_check_mark: 使用が終了されました。\n>>> ## PC番号 | {stop['output_dict']['pc_number']}", ephemeral=True) + result_embed = discord.Embed(title=":white_check_mark: 使用停止処理は完了しました。", description=f'PC番号 {stop['output_dict']['pc_number']} 番の使用停止処理が完了しました。', color=0x56FF01) + dislocker.log(title=f"[INFO] PC番号{stop['output_dict']['pc_number']} の使用が終了されました。", message=f"名前 | {stop['output_dict']['name']}", flag=0) + log_embed = discord.Embed(title=f":information_source: PC番号 {stop['output_dict']['pc_number']} の使用は終了されました。", description=f"<@{interaction.user.id}> によるリクエスト", color=0x2286C9) + await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(embed=log_embed) + + elif stop["result"] == 1: + if stop["about"] == "unused": + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'あなたはPCを使用されていないようです...', color=0xC91111) + + elif stop["about"] == "user_data_not_found": + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'Dislockerのユーザーとして登録されていないようです。\n登録を行ってから、またお試しください。', color=0xC91111) + + elif stop["about"] == "error": + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。', color=0xC91111) + result_embed.add_field(name=f"{stop['output_dict']['error_class_name']}", value=f"{stop['output_dict']['error_args']}") + + await interaction.response.send_message(embed=result_embed, ephemeral=True) + +#管理者側のスラッシュコマンド +@tree.command(name="userreg", description="ユーザーを登録します。") +@discord.app_commands.default_permissions(administrator=True) +async def userreg(interaction: discord.Interaction, discord_user_id: str, discord_user_name: str, name: str): + if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: + user_register = dislocker.user_register(discord_user_id=discord_user_id, discord_user_name=discord_user_name, name=name) + if user_register["result"] == 0: + result_embed = discord.Embed(title=":white_check_mark: ユーザー登録が完了しました。", description=f'続いて、PCの使用登録を行いましょう!', color=0x56FF01) + dislocker.log(title=f"[INFO] ユーザーを登録しました。", message=f"名前 | {name}, Discordユーザー名 | {discord_user_name}, DiscordユーザーID | {discord_user_id}", flag=0) + + elif user_register["result"] == 1: + if user_register["about"] == "already_exists": + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'既に登録されているユーザーです。', color=0xC91111) + + elif user_register["about"] == "error": + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。', color=0xC91111) + result_embed.add_field(name=f"{user_register['output_dict']['error_class_name']}", value=f"{user_register['output_dict']['error_args']}") + + await interaction.response.send_message(embed=result_embed, ephemeral=True) + +@tree.command(name="pcreg", description="PCをDislockerに登録するためのワンタイムパスワードを発行します。") +@discord.app_commands.default_permissions(administrator=True) +async def pcreg(interaction: discord.Interaction, how_much: int = 1): + if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: + max_count = how_much + + pc_onetime_password_gen = dislocker.pc_onetime_gen(max_count=max_count) + + if pc_onetime_password_gen["result"] == 0: + pc_onetime_password = str(pc_onetime_password_gen["output_dict"]["onetime_password"]) + pc_onetime_password_max_count = str(pc_onetime_password_gen["output_dict"]["max_count"]) + pc_onetime_password_current_count = str(pc_onetime_password_gen["output_dict"]["current_count"]) + pc_onetime_password_remaining_times = str(int(pc_onetime_password_max_count) - int(pc_onetime_password_current_count)) + + result_embed = discord.Embed(title=":dizzy_face: PCの登録", description=f"PC登録時のワンタイムパスワードを発行します。", color=0x2286C9) + result_embed.add_field(name="パスワード", value=pc_onetime_password) + result_embed.add_field(name="最大使用回数", value=pc_onetime_password_max_count) + result_embed.add_field(name="残り使用回数", value=pc_onetime_password_remaining_times) + + elif pc_onetime_password_gen["result"] == 1: + if pc_onetime_password_gen["about"] == "already_exists": + pc_onetime_password = str(pc_onetime_password_gen["output_dict"]["onetime_password"]) + pc_onetime_password_max_count = str(pc_onetime_password_gen["output_dict"]["max_count"]) + pc_onetime_password_current_count = str(pc_onetime_password_gen["output_dict"]["current_count"]) + pc_onetime_password_remaining_times = str(int(pc_onetime_password_max_count) - int(pc_onetime_password_current_count)) + + result_embed = discord.Embed(title=":dizzy_face: PCの登録", description=f"ワンタイムパスワードはもう発行されており、有効です。", color=0x2286C9) + result_embed.add_field(name="パスワード", value=pc_onetime_password) + result_embed.add_field(name="最大使用回数", value=pc_onetime_password_max_count) + result_embed.add_field(name="残り使用回数", value=pc_onetime_password_remaining_times) + + else: + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。', color=0xC91111) + result_embed.add_field(name=f"{pc_onetime_password_gen['output_dict']['error_class_name']}", value=f"{pc_onetime_password_gen['output_dict']['error_args']}") + + await interaction.response.send_message(embed=result_embed, ephemeral=True) + +@tree.command(name="devicereg", description="デバイスをDislockerに登録するためのワンタイムパスワードを発行します。") +@discord.app_commands.default_permissions(administrator=True) +async def devicereg(interaction: discord.Interaction, how_much: int): + if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: + max_count = how_much + + device_onetime_password_gen = dislocker.device_onetime_gen(max_count=max_count) + + if device_onetime_password_gen["result"] == 0: + device_onetime_password = str(device_onetime_password_gen["output_dict"]["onetime_password"]) + device_onetime_password_max_count = str(device_onetime_password_gen["output_dict"]["max_count"]) + device_onetime_password_current_count = str(device_onetime_password_gen["output_dict"]["current_count"]) + device_onetime_password_remaining_times = str(int(device_onetime_password_max_count) - int(device_onetime_password_current_count)) + + result_embed = discord.Embed(title=":dizzy_face: デバイスの登録", description=f"デバイス登録時のワンタイムパスワードを発行します。", color=0x2286C9) + result_embed.add_field(name="パスワード", value=device_onetime_password) + result_embed.add_field(name="最大使用回数", value=device_onetime_password_max_count) + result_embed.add_field(name="残り使用回数", value=device_onetime_password_remaining_times) + + elif device_onetime_password_gen["result"] == 1: + if device_onetime_password_gen["about"] == "already_exists": + device_onetime_password = str(device_onetime_password_gen["output_dict"]["onetime_password"]) + device_onetime_password_max_count = str(device_onetime_password_gen["output_dict"]["max_count"]) + device_onetime_password_current_count = str(device_onetime_password_gen["output_dict"]["current_count"]) + device_onetime_password_remaining_times = str(int(device_onetime_password_max_count) - int(device_onetime_password_current_count)) + result_embed = discord.Embed(title=":dizzy_face: デバイスの登録", description=f"ワンタイムパスワードはもう発行されており、有効です。", color=0x2286C9) + result_embed.add_field(name="パスワード", value=device_onetime_password) + result_embed.add_field(name="最大使用回数", value=device_onetime_password_max_count) + result_embed.add_field(name="残り使用回数", value=device_onetime_password_remaining_times) + + else: + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。', color=0xC91111) + result_embed.add_field(name=f"{device_onetime_password_gen['output_dict']['error_class_name']}", value=f"{device_onetime_password_gen['output_dict']['error_args']}") + + await interaction.response.send_message(embed=result_embed, ephemeral=True) + + +@tree.command(name="fstop", description="PCの使用登録を強制的に終了します。") +@discord.app_commands.default_permissions(administrator=True) +async def fstop(interaction: discord.Interaction, pc_number: int, about: str): + if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: + force_stop = dislocker.force_stop(pc_number=pc_number, bot_about=about) + if force_stop["result"] == 0: + result_embed = discord.Embed(title=":white_check_mark: 処理が完了しました。", description=f'PC番号 {str(pc_number)} 番の使用登録は抹消されました。', color=0x56FF01) + + dislocker.log(title=f"[INFO] PC {pc_number} の使用を強制終了しました。", message=f"理由 | {about}", flag=0) + elif force_stop["result"] == 1: + if force_stop["about"] == "not_used": + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'指定されたPCは使用されていないようです...', color=0xC91111) + + elif force_stop["about"] == "bot_about_not_found": + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'強制停止する理由を入力してください。', color=0xC91111) + + elif force_stop["about"] == "error": + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。', color=0xC91111) + result_embed.add_field(name=f"{force_stop['output_dict']['error_class_name']}", value=f"{force_stop['output_dict']['error_args']}") + + await interaction.response.send_message(embed=result_embed, ephemeral=True) + +@tree.command(name="report", description="PCの使用履歴をエクスポートします。") +@discord.app_commands.default_permissions(administrator=True) +async def report(interaction: discord.Interaction): + if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: + report_export = dislocker.report_export() + if report_export["result"] == 0: + await interaction.response.send_message(f":white_check_mark: 使用履歴のレポートです。", file=discord.File(report_export["file_path"]), ephemeral=True) + dislocker.log(title=f"[INFO] PCの使用履歴をエクスポートしました。", message=f"ファイルパス | {report_export['file_path']}", flag=0) + elif report_export["result"] == 1: + if report_export["about"] == "error": + await interaction.response.send_message(":x: 内部エラーが発生しました。\nサーバーでエラーが発生しています。管理者に問い合わせてください。", ephemeral=True) + +@tree.command(name="init", description="操作チャンネルにボタン一式を送信します。") +@discord.app_commands.default_permissions(administrator=True) +async def button_init(interaction: discord.Interaction, text_channel: discord.TextChannel): + if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: + pc_list = dislocker.get_pc_list() + + user_register_button_view = discord.ui.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 client.get_channel(text_channel.id).send(f'# :index_pointing_at_the_viewer: ユーザー登録はお済ですか?', view=user_register_button_view) + + stop_button_view = discord.ui.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 client.get_channel(text_channel.id).send(f'# :index_pointing_at_the_viewer: 使用を停止しますか?', view=stop_button_view) + + pc_button_view = discord.ui.View(timeout=None) + for i in pc_list["output_dict"].keys(): + current_pc_list = pc_list['output_dict'][i] + if current_pc_list['alt_name'] == None: + pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(current_pc_list['pc_number'])} 番", custom_id=f"pcregister_{str(current_pc_list['pc_number'])}") + else: + pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(current_pc_list['pc_number'])} 番 | ({current_pc_list['alt_name']})", custom_id=f"pcregister_{str(current_pc_list['pc_number'])}") + pc_button_view.add_item(pc_register_button) + + await client.get_channel(text_channel.id).send(f'# :index_pointing_at_the_viewer: 使いたいPCの番号を選んでください!', view=pc_button_view) + dislocker.log(title=f"[INFO] サーバーで初回処理を実行しました。", flag=0) + + result_embed = discord.Embed(title=":white_check_mark: 初回処理が完了しました。", description=f'指定したテキストチャンネルをご確認ください。', color=0x56FF01) + + await interaction.response.send_message(embed=result_embed, ephemeral=True) + +@tree.command(name="masterpass", description="PCのマスターパスワードを表示します。") +@discord.app_commands.default_permissions(administrator=True) +async def masterpass(interaction: discord.Interaction, pc_number: int): + if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: + pc_master_password_get = dislocker.show_pc_master_password(pc_number=pc_number) + + if pc_master_password_get["result"] == 0: + pc_master_password = pc_master_password_get["output_dict"]["pc_master_password"] + + result_embed = discord.Embed(title=":information_source: マスターパスワード", description=f"PC番号 {str(pc_number)} 番のマスターパスワードを表示します。", color=0x2286C9) + result_embed.add_field(name=f"マスターパスワード", value=f"{str(pc_master_password)}") + + else: + result_embed = discord.Embed(title=":x: 取得に失敗しました。", description=f'サーバーでエラーが発生しています。', color=0xC91111) + result_embed.add_field(name=f"{pc_master_password_get['output_dict']['error_class_name']}", value=f"{pc_master_password_get['output_dict']['error_args']}") + + await interaction.response.send_message(embed=result_embed, ephemeral=True) + +@tree.command(name="pcinfo", description="PCの情報を表示します。") +@discord.app_commands.default_permissions(administrator=True) +async def pcinfo(interaction: discord.Interaction): + if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: + pc_list = dislocker.get_pc_list() + + if pc_list["result"] == 0: + result_embed = discord.Embed(title=":information_source: 現在のPCリスト", description="PCリストです。", color=0x2286C9) + + for i in pc_list['output_dict'].keys(): + current_pc_list = pc_list['output_dict'][i] + if current_pc_list['alt_name'] == None: + pc_name_title = f'{current_pc_list['pc_number']} 番' + else: + pc_name_title = f'{current_pc_list['pc_number']} 番 ({current_pc_list['alt_name']})' + + if current_pc_list['using_member_id'] == None: + pc_using_value = f'未使用' + else: + discord_user_id = dislocker.get_discord_user_id(current_pc_list['using_member_id']) + pc_using_value = f'<@{discord_user_id}> が使用中' + + result_embed.add_field(name=f'{pc_name_title}', value=f'{pc_using_value}') + + else: + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。', color=0xC91111) + result_embed.add_field(name=f"{pc_list['output_dict']['error_class_name']}", value=f"{pc_list['output_dict']['error_args']}") + + await interaction.response.send_message(embed=result_embed, ephemeral=True) + +@tree.command(name="pcnickname", description="PCにニックネームを設定します。") +@discord.app_commands.default_permissions(administrator=True) +async def pcnickname(interaction: discord.Interaction, pc_number: int, nickname: str): + if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: + pc_nickname_set = dislocker.set_pc_nickname(pc_number=pc_number, alt_name=nickname) + if pc_nickname_set["result"] == 0: + result_embed = discord.Embed(title=":white_check_mark: 操作が完了しました。", description=f'PC番号 {str(pc_number)} のニックネームは {str(nickname)} に設定されました。', color=0x56FF01) + + else: + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。ニックネームは変更されません。', color=0xC91111) + result_embed.add_field(name=f"{pc_nickname_set['output_dict']['error_class_name']}", value=f"{pc_nickname_set['output_dict']['error_args']}") + + await interaction.response.send_message(embed=result_embed, ephemeral=True) + + 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']) + client.run(dislocker.server_config["bot"]["token"]) else: - pass \ No newline at end of file + pass From ace3cab44bcd93b59166e606f5802b90c1de9aef Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Tue, 1 Oct 2024 11:31:11 +0900 Subject: [PATCH 138/175] =?UTF-8?q?=E3=83=AD=E3=82=B0=E3=83=81=E3=83=A3?= =?UTF-8?q?=E3=83=B3=E3=83=8D=E3=83=AB=E3=81=AB=E9=80=81=E4=BF=A1=E3=81=99?= =?UTF-8?q?=E3=82=8B=E5=86=85=E5=AE=B9=E3=82=92embed=E3=81=AB=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 71 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/dislocker.py b/dislocker.py index 8b01d1d..b3d64f5 100644 --- a/dislocker.py +++ b/dislocker.py @@ -927,7 +927,7 @@ class ReasonModal(discord.ui.Modal): if register["about"] == "ok": await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\n## 使用目的 | {self.reason_input_form.value}", ephemeral=True) - await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':white_check_mark: {register["output_dict"]["name"]} さんがPC {pc_number} の使用を開始しました。\n>>> ## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\n## 使用目的 | {self.reason_input_form.value}') + await send_log(mode="use", pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=self.reason_input_form.value, discord_user_id=interaction.user.id) dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, 使用目的 | {self.reason_input_form.value}", flag=0) elif register["about"] == "pc_already_in_use_by_you": pc_usage_history = register["pc_usage_history"] @@ -1057,6 +1057,46 @@ intents.message_content = True client = discord.Client(intents=intents) tree = discord.app_commands.CommandTree(client) +async def send_log(**kwargs): + try: + pc_number = str(kwargs.get("pc_number")) + discord_user_id = int(kwargs.get("discord_user_id")) + mode = str(kwargs.get("mode")) + + if mode == "use": + keyboard_number = int(kwargs.get("keyboard_number")) + mouse_number = int(kwargs.get("mouse_number")) + detail = str(kwargs.get("detail")) + + if keyboard_number == 0: + keyboard_number_show = "自前" + else: + keyboard_number_show = str(keyboard_number) + + if mouse_number == 0: + mouse_number_show = "自前" + else: + mouse_number_show = str(mouse_number) + + + log_embed = discord.Embed(title=f"PC {pc_number} 番 | 使用開始通知", description=f"<@{discord_user_id}> さんはPCの使用を開始しました。", color=0x1343EB) + log_embed.add_field(name="PC番号", value=pc_number) + log_embed.add_field(name="キーボード番号", value=keyboard_number_show) + log_embed.add_field(name="マウス番号", value=mouse_number_show) + log_embed.add_field(name="使用目的", value=detail) + + elif mode == "stop": + log_embed = discord.Embed(title=f"PC {pc_number} 番 | 使用終了通知", description=f"<@{discord_user_id}> さんはPCの使用を終了しました。", color=0xE512EB) + + await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(embed=log_embed) + + return {"result": 0, "about": "ok"} + + except Exception as error: + dislocker.log(title=f"[ERROR] ログ送信中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} + + @client.event async def on_ready(): dislocker.log(title=f"[SUCCESS] DiscordのBotが起動しました。", message=f"{client.user.name} としてログインしています。", flag=1) @@ -1255,7 +1295,7 @@ async def on_button(interaction: discord.Interaction): register = dislocker.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=reason) if register["about"] == "ok": await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## キーボード番号 | {str(keyboard_number_show)}\n## マウス番号 | {str(mouse_number_show)}\n## 使用目的 | {reason}", ephemeral=True) - await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':white_check_mark: {register["output_dict"]["name"]} さんがPC {pc_number} の使用を開始しました。\n>>> ## PC番号 | {pc_number}\n## 使用目的 | {reason}') + await send_log(mode="use", pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=reason, discord_user_id=interaction.user.id) dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, 使用目的 | {reason}", flag=0) elif register["about"] == "pc_already_in_use_by_you": pc_usage_history = register["pc_usage_history"] @@ -1312,7 +1352,7 @@ async def on_button(interaction: discord.Interaction): await interaction.response.send_message("# :dizzy_face: ユーザーとして登録されていないようです。\n最初にサーバーで登録を行ってください。", ephemeral=True) elif pc_stop["about"] == "ok": await interaction.response.send_message(f":white_check_mark: PC番号 {pc_stop["output_dict"]["pc_number"]} の使用が終了されました。", ephemeral=True) - await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':negative_squared_cross_mark: {pc_stop["output_dict"]["name"]} さんがPC {pc_stop["output_dict"]["pc_number"]} の使用を終了しました。') + await send_log(mode="stop", pc_number=pc_stop['output_dict']['pc_number'], discord_user_id=interaction.user.id) else: await interaction.response.send_message("# :skull_crossbones: 停止できませんでした。\n内部エラーが発生しています。", ephemeral=True) @@ -1333,7 +1373,7 @@ async def use(interaction: discord.Interaction, pc_number: int, keyboard_number: if register["result"] == 0: await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## 使用目的 | {detail}", ephemeral=True) dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, 使用目的 | {detail}", flag=0) - await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(f':white_check_mark: {register["output_dict"]["name"]} さんがPC {pc_number} の使用を開始しました。\n>>> ## PC番号 | {pc_number}\n## 使用目的 | {detail}') + await send_log(mode="use", pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=detail, discord_user_id=interaction.user.id) elif register["result"] == 1: if register["about"] == "pc_already_in_use_by_other": await interaction.response.send_message(":x: 他の方がそのPCを使用中です。", ephemeral=True) @@ -1350,24 +1390,23 @@ async def use(interaction: discord.Interaction, pc_number: int, keyboard_number: @tree.command(name="stop", description="パソコンの使用を終了します。通常はこのコマンドを使用する必要はありません。") async def stop(interaction: discord.Interaction): - stop = dislocker.stop(discord_user_id=interaction.user.id) - if stop["result"] == 0: - await interaction.response.send_message(f":white_check_mark: 使用が終了されました。\n>>> ## PC番号 | {stop['output_dict']['pc_number']}", ephemeral=True) - result_embed = discord.Embed(title=":white_check_mark: 使用停止処理は完了しました。", description=f'PC番号 {stop['output_dict']['pc_number']} 番の使用停止処理が完了しました。', color=0x56FF01) - dislocker.log(title=f"[INFO] PC番号{stop['output_dict']['pc_number']} の使用が終了されました。", message=f"名前 | {stop['output_dict']['name']}", flag=0) - log_embed = discord.Embed(title=f":information_source: PC番号 {stop['output_dict']['pc_number']} の使用は終了されました。", description=f"<@{interaction.user.id}> によるリクエスト", color=0x2286C9) - await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(embed=log_embed) + pc_stop = dislocker.stop(discord_user_id=interaction.user.id) + if pc_stop["result"] == 0: + await interaction.response.send_message(f":white_check_mark: 使用が終了されました。\n>>> ## PC番号 | {pc_stop['output_dict']['pc_number']}", ephemeral=True) + result_embed = discord.Embed(title=":white_check_mark: 使用停止処理は完了しました。", description=f'PC番号 {pc_stop['output_dict']['pc_number']} 番の使用停止処理が完了しました。', color=0x56FF01) + dislocker.log(title=f"[INFO] PC番号{pc_stop['output_dict']['pc_number']} の使用が終了されました。", message=f"名前 | {pc_stop['output_dict']['name']}", flag=0) + await send_log(mode="stop", pc_number=pc_stop['output_dict']['pc_number'], discord_user_id=interaction.user.id) - elif stop["result"] == 1: - if stop["about"] == "unused": + elif pc_stop["result"] == 1: + if pc_stop["about"] == "unused": result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'あなたはPCを使用されていないようです...', color=0xC91111) - elif stop["about"] == "user_data_not_found": + elif pc_stop["about"] == "user_data_not_found": result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'Dislockerのユーザーとして登録されていないようです。\n登録を行ってから、またお試しください。', color=0xC91111) - elif stop["about"] == "error": + elif pc_stop["about"] == "error": result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。', color=0xC91111) - result_embed.add_field(name=f"{stop['output_dict']['error_class_name']}", value=f"{stop['output_dict']['error_args']}") + result_embed.add_field(name=f"{pc_stop['output_dict']['error_class_name']}", value=f"{pc_stop['output_dict']['error_args']}") await interaction.response.send_message(embed=result_embed, ephemeral=True) From d932730ed7ed7a74b7f547da307fd023c3dacc7b Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Tue, 1 Oct 2024 11:36:27 +0900 Subject: [PATCH 139/175] =?UTF-8?q?=E8=87=AA=E5=89=8D=E3=81=AE=E3=83=87?= =?UTF-8?q?=E3=83=90=E3=82=A4=E3=82=B9=E3=81=AF0=E7=95=AA=E3=81=A8?= =?UTF-8?q?=E3=81=97=E3=81=A6=E5=87=A6=E7=90=86=E3=81=99=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/dislocker.py b/dislocker.py index b3d64f5..9c7418a 100644 --- a/dislocker.py +++ b/dislocker.py @@ -355,12 +355,12 @@ class DL(): else: pass - if kwargs["keyboard_number"] == "own": + if kwargs["keyboard_number"] == "0": pass else: user_info["keyboard_number"] = int(kwargs["keyboard_number"]) - if kwargs["mouse_number"] == "own": + if kwargs["mouse_number"] == "0": pass else: user_info["mouse_number"] = int(kwargs["mouse_number"]) @@ -913,12 +913,12 @@ class ReasonModal(discord.ui.Modal): keyboard_number = custom_id_split[2] mouse_number = custom_id_split[3] - if keyboard_number == "own": + if keyboard_number == "0": keyboard_number_show = "自前" else: keyboard_number_show = keyboard_number - if mouse_number == "own": + if mouse_number == "0": mouse_number_show = "自前" else: mouse_number_show = mouse_number @@ -1223,7 +1223,7 @@ async def on_button(interaction: discord.Interaction): keyboard_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(current_keyboard_list['keyboard_number'])} 番 | ({current_keyboard_list['alt_name']})", custom_id=f"keyboardregister_{str(pc_number)}_{str(current_keyboard_list['keyboard_number'])}") keyboard_register_view.add_item(keyboard_register_button) - keyboard_not_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="キーボードは自前", custom_id=f"keyboardregister_{str(pc_number)}_own") + keyboard_not_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="キーボードは自前", custom_id=f"keyboardregister_{str(pc_number)}_0") keyboard_register_view.add_item(keyboard_not_register_button) await interaction.response.send_message(f"# :keyboard: キーボードのデバイス番号を選んでください!\n>>> # PC番号 | {str(pc_number)}", view=keyboard_register_view, ephemeral=True) @@ -1233,7 +1233,7 @@ async def on_button(interaction: discord.Interaction): pc_number = custom_id_split[1] keyboard_number = custom_id_split[2] mouse_list = dislocker.get_mouse_list() - if keyboard_number == "own": + if keyboard_number == "0": keyboard_number_show = "自前" else: keyboard_number_show = keyboard_number @@ -1246,7 +1246,7 @@ async def on_button(interaction: discord.Interaction): mouse_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(current_mouse_list['mouse_number'])} 番 | ({current_mouse_list['alt_name']})", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_{str(current_mouse_list['mouse_number'])}") mouse_register_view.add_item(mouse_register_button) - mouse_not_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="マウスは自前", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_own") + mouse_not_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label="マウスは自前", custom_id=f"mouseregister_{str(pc_number)}_{str(keyboard_number)}_0") mouse_register_view.add_item(mouse_not_register_button) await interaction.response.send_message(f"# :mouse_three_button: マウスのデバイス番号を選んでください!\n>>> # PC番号 | {str(pc_number)}\n# キーボード番号 | {str(keyboard_number_show)}", view=mouse_register_view, ephemeral=True) @@ -1256,12 +1256,12 @@ async def on_button(interaction: discord.Interaction): keyboard_number = custom_id_split[2] mouse_number = custom_id_split[3] - if keyboard_number == "own": + if keyboard_number == "0": keyboard_number_show = "自前" else: keyboard_number_show = keyboard_number - if mouse_number == "own": + if mouse_number == "0": mouse_number_show = "自前" else: mouse_number_show = mouse_number @@ -1280,12 +1280,12 @@ async def on_button(interaction: discord.Interaction): keyboard_number = custom_id_split[2] mouse_number = custom_id_split[3] - if keyboard_number == "own": + if keyboard_number == "0": keyboard_number_show = "自前" else: keyboard_number_show = keyboard_number - if mouse_number == "own": + if mouse_number == "0": mouse_number_show = "自前" else: mouse_number_show = mouse_number @@ -1330,12 +1330,12 @@ async def on_button(interaction: discord.Interaction): keyboard_number = custom_id_split[2] mouse_number = custom_id_split[3] - if keyboard_number == "own": + if keyboard_number == "0": keyboard_number_show = "自前" else: keyboard_number_show = keyboard_number - if mouse_number == "own": + if mouse_number == "0": mouse_number_show = "自前" else: mouse_number_show = mouse_number From bef78a85d6cf1b60e6c8612497b310a11b7559bc Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Tue, 1 Oct 2024 11:42:16 +0900 Subject: [PATCH 140/175] =?UTF-8?q?=E3=82=BF=E3=82=A4=E3=83=88=E3=83=AB?= =?UTF-8?q?=E3=81=AB=E7=B5=B5=E6=96=87=E5=AD=97=E3=82=92=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dislocker.py b/dislocker.py index 9c7418a..282571a 100644 --- a/dislocker.py +++ b/dislocker.py @@ -1079,14 +1079,14 @@ async def send_log(**kwargs): mouse_number_show = str(mouse_number) - log_embed = discord.Embed(title=f"PC {pc_number} 番 | 使用開始通知", description=f"<@{discord_user_id}> さんはPCの使用を開始しました。", color=0x1343EB) + log_embed = discord.Embed(title=f":video_game: PC {pc_number} 番 | 使用開始通知", description=f"<@{discord_user_id}> さんはPCの使用を開始しました。", color=0x1343EB) log_embed.add_field(name="PC番号", value=pc_number) log_embed.add_field(name="キーボード番号", value=keyboard_number_show) log_embed.add_field(name="マウス番号", value=mouse_number_show) log_embed.add_field(name="使用目的", value=detail) elif mode == "stop": - log_embed = discord.Embed(title=f"PC {pc_number} 番 | 使用終了通知", description=f"<@{discord_user_id}> さんはPCの使用を終了しました。", color=0xE512EB) + log_embed = discord.Embed(title=f":stop_button: PC {pc_number} 番 | 使用終了通知", description=f"<@{discord_user_id}> さんはPCの使用を終了しました。", color=0xE512EB) await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(embed=log_embed) From dba23585342ad893d2d0713968cb5e30d065c688 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Tue, 1 Oct 2024 11:51:29 +0900 Subject: [PATCH 141/175] =?UTF-8?q?=E3=83=91=E3=82=B9=E3=83=AF=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=81=AE=E3=82=BF=E3=82=A4=E3=83=A0=E3=82=A2=E3=82=A6?= =?UTF-8?q?=E3=83=88=E3=82=92=E9=80=9A=E7=9F=A5=E3=81=99=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB,=20=E7=84=A1=E9=A7=84=E3=81=AA=E3=82=B3?= =?UTF-8?q?=E3=83=BC=E3=83=89=E3=81=AE=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 36 ++++++++---------------------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/dislocker.py b/dislocker.py index 282571a..b15c720 100644 --- a/dislocker.py +++ b/dislocker.py @@ -988,8 +988,8 @@ class Monitor(): dislocker.log(title=f"[SUCCESS] 定期のPCの使用停止処理は完了しました。", flag=0) else: if pc_list: - if len(pc_list) == 1: - member_id = pc_list[0][1] + for i in pc_list: + member_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", (member_id,)) pc_usage = cursor.fetchall() start_time = pc_usage[0][5] @@ -998,36 +998,13 @@ class Monitor(): if time_difference.seconds >= timedelta(seconds=self.allowable_time).seconds: cursor.execute("SELECT * FROM club_member WHERE member_id = %s", (member_id,)) user_info = cursor.fetchall() - stop = dislocker.stop(discord_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]) + stop = dislocker.stop(discord_user_id=user_info[0][3], bot_about="タイムアウトでBotによる強制停止。") + discord_user_id = dislocker.get_discord_user_id(member_id=member_id)["discord_user_id"] + asyncio.run(send_log(mode="timeout", pc_number=i[0], discord_user_id=discord_user_id)) dislocker.log(title=f"[INFO] パスワードのタイムアウト時間に達したため、強制停止されました。", flag=0) result = {"result": "STOP", "details": str(pc_usage)} else: result = {"result": "BUT SAFE", "details": str(pc_usage)} - - - elif len(pc_list) >= 2: - for i in pc_list: - member_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", (member_id,)) - pc_usage = cursor.fetchall() - start_time = pc_usage[0][5] - time_difference = current_datetime - start_time - dislocker.log(title=f"[INFO] 現在確認されているパスワード未使用のユーザー", message=f"レコード | {str(pc_usage)}, 経過時間(Sec) | {time_difference.seconds}/{timedelta(seconds=self.allowable_time).seconds}", flag=0) - if time_difference.seconds >= timedelta(seconds=self.allowable_time).seconds: - cursor.execute("SELECT * FROM club_member WHERE member_id = %s", (member_id,)) - user_info = cursor.fetchall() - stop = dislocker.stop(discord_user_id=user_info[0][3], bot_about="タイムアウトでBotによる強制停止。") - - #bot.timeout_notify(pc_number=i[0], discord_display_name=user_info[0][1]) - dislocker.log(title=f"[INFO] パスワードのタイムアウト時間に達したため、強制停止されました。", flag=0) - result = {"result": "STOP", "details": str(pc_usage)} - else: - result = {"result": "BUT SAFE", "details": str(pc_usage)} - - else: - result = {"result": "NONE"} else: result = {"result": "NONE"} @@ -1087,6 +1064,9 @@ async def send_log(**kwargs): elif mode == "stop": log_embed = discord.Embed(title=f":stop_button: PC {pc_number} 番 | 使用終了通知", description=f"<@{discord_user_id}> さんはPCの使用を終了しました。", color=0xE512EB) + + elif mode == "timeout": + log_embed = discord.Embed(title=f":alarm_clock: PC {pc_number} 番 | タイムアウト通知", description=f"<@{discord_user_id}> さんが指定時間内にPCを使用しなかったため、停止されました。", color=0xE512EB) await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(embed=log_embed) From a5b7ba9c187b9c250e082224d3c3e10758191ac5 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Tue, 1 Oct 2024 12:04:24 +0900 Subject: [PATCH 142/175] =?UTF-8?q?=E3=83=AD=E3=82=B0=E3=81=AE=E7=A8=AE?= =?UTF-8?q?=E9=A1=9E=E3=82=92=E5=A2=97=E3=82=84=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/dislocker.py b/dislocker.py index b15c720..8339cd2 100644 --- a/dislocker.py +++ b/dislocker.py @@ -1000,7 +1000,7 @@ class Monitor(): user_info = cursor.fetchall() stop = dislocker.stop(discord_user_id=user_info[0][3], bot_about="タイムアウトでBotによる強制停止。") discord_user_id = dislocker.get_discord_user_id(member_id=member_id)["discord_user_id"] - asyncio.run(send_log(mode="timeout", pc_number=i[0], discord_user_id=discord_user_id)) + asyncio.run_coroutine_threadsafe(send_log(mode="timeout", pc_number=i[0], discord_user_id=discord_user_id)) dislocker.log(title=f"[INFO] パスワードのタイムアウト時間に達したため、強制停止されました。", flag=0) result = {"result": "STOP", "details": str(pc_usage)} else: @@ -1067,6 +1067,19 @@ async def send_log(**kwargs): elif mode == "timeout": log_embed = discord.Embed(title=f":alarm_clock: PC {pc_number} 番 | タイムアウト通知", description=f"<@{discord_user_id}> さんが指定時間内にPCを使用しなかったため、停止されました。", color=0xE512EB) + + elif mode == "userreg": + log_embed = discord.Embed(title=f":bust_in_silhouette: ユーザー登録通知", description=f"<@{discord_user_id}> さんがユーザーとして登録されました。", color=0x1343EB) + + elif mode == "fstop": + reason = str(kwargs.get("reason")) + log_embed = discord.Embed(title=f":stop_button: PC {pc_number} 番 | 強制停止通知", description=f"<@{discord_user_id}> さんによってPCの使用は停止されました。", color=0xE512EB) + log_embed.add_field(name="理由", value=reason) + + elif mode == "pcnickname": + alt_name = str(kwargs.get("alt_name")) + log_embed = discord.Embed(title=f":pencil: PC {pc_number} 番 | PCのニックネーム変更通知", description=f"<@{discord_user_id}> さんによってPCのニックネームが変更されました。", color=0x1343EB) + log_embed.add_field(name="ニックネーム", value=alt_name) await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(embed=log_embed) @@ -1340,6 +1353,7 @@ async def on_button(interaction: discord.Interaction): user_register = dislocker.user_register(name=interaction.user.display_name, discord_user_name=interaction.user.name, discord_user_id=interaction.user.id) if user_register["about"] == "ok": await interaction.response.send_message(f"# :white_check_mark: ユーザー情報が登録されました。\n>>> ユーザー名:{interaction.user.display_name}", ephemeral=True) + await send_log(mode="userreg", discord_user_id=interaction.user.id) elif user_register["about"] == "already_exists": await interaction.response.send_message("# :no_entry: 登録できませんでした。\nもう登録されている可能性があります。", ephemeral=True) else: @@ -1399,6 +1413,7 @@ async def userreg(interaction: discord.Interaction, discord_user_id: str, discor if user_register["result"] == 0: result_embed = discord.Embed(title=":white_check_mark: ユーザー登録が完了しました。", description=f'続いて、PCの使用登録を行いましょう!', color=0x56FF01) dislocker.log(title=f"[INFO] ユーザーを登録しました。", message=f"名前 | {name}, Discordユーザー名 | {discord_user_name}, DiscordユーザーID | {discord_user_id}", flag=0) + await send_log(mode="userreg", discord_user_id=discord_user_id) elif user_register["result"] == 1: if user_register["about"] == "already_exists": @@ -1486,13 +1501,13 @@ async def devicereg(interaction: discord.Interaction, how_much: int): @tree.command(name="fstop", description="PCの使用登録を強制的に終了します。") @discord.app_commands.default_permissions(administrator=True) -async def fstop(interaction: discord.Interaction, pc_number: int, about: str): +async def fstop(interaction: discord.Interaction, pc_number: int, reason: str): if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: - force_stop = dislocker.force_stop(pc_number=pc_number, bot_about=about) + force_stop = dislocker.force_stop(pc_number=pc_number, bot_about=reason) if force_stop["result"] == 0: result_embed = discord.Embed(title=":white_check_mark: 処理が完了しました。", description=f'PC番号 {str(pc_number)} 番の使用登録は抹消されました。', color=0x56FF01) - - dislocker.log(title=f"[INFO] PC {pc_number} の使用を強制終了しました。", message=f"理由 | {about}", flag=0) + dislocker.log(title=f"[INFO] PC {pc_number} の使用を強制終了しました。", message=f"理由 | {reason}", flag=0) + await send_log(mode="fstop", pc_number=pc_number, discord_user_id=interaction.user.id, detail=reason) elif force_stop["result"] == 1: if force_stop["about"] == "not_used": result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'指定されたPCは使用されていないようです...', color=0xC91111) @@ -1607,6 +1622,7 @@ async def pcnickname(interaction: discord.Interaction, pc_number: int, nickname: pc_nickname_set = dislocker.set_pc_nickname(pc_number=pc_number, alt_name=nickname) if pc_nickname_set["result"] == 0: result_embed = discord.Embed(title=":white_check_mark: 操作が完了しました。", description=f'PC番号 {str(pc_number)} のニックネームは {str(nickname)} に設定されました。', color=0x56FF01) + await send_log(mode="pcnickname", pc_number=pc_number, discord_user_id=interaction.user.id, detail=nickname) else: result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。ニックネームは変更されません。', color=0xC91111) From a2c7fd6f052f262d7cd90038acd2801e1953649d Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Tue, 1 Oct 2024 12:07:42 +0900 Subject: [PATCH 143/175] =?UTF-8?q?PC=E3=81=AE=E3=83=8B=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=83=8D=E3=83=BC=E3=83=A0=E8=A8=AD=E5=AE=9A=E6=99=82=E3=81=AB?= =?UTF-8?q?=E3=83=AD=E3=82=B0=E3=81=AB=E3=83=8B=E3=83=83=E3=82=AF=E3=83=8D?= =?UTF-8?q?=E3=83=BC=E3=83=A0=E3=81=8C=E5=87=BA=E3=81=AA=E3=81=84=E5=95=8F?= =?UTF-8?q?=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker.py b/dislocker.py index 8339cd2..2316f13 100644 --- a/dislocker.py +++ b/dislocker.py @@ -1622,7 +1622,7 @@ async def pcnickname(interaction: discord.Interaction, pc_number: int, nickname: pc_nickname_set = dislocker.set_pc_nickname(pc_number=pc_number, alt_name=nickname) if pc_nickname_set["result"] == 0: result_embed = discord.Embed(title=":white_check_mark: 操作が完了しました。", description=f'PC番号 {str(pc_number)} のニックネームは {str(nickname)} に設定されました。', color=0x56FF01) - await send_log(mode="pcnickname", pc_number=pc_number, discord_user_id=interaction.user.id, detail=nickname) + await send_log(mode="pcnickname", pc_number=pc_number, discord_user_id=interaction.user.id, alt_name=nickname) else: result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。ニックネームは変更されません。', color=0xC91111) From 5bff1f6a16e13eccb9276f2c65163a2f69f96d55 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Tue, 1 Oct 2024 12:10:11 +0900 Subject: [PATCH 144/175] =?UTF-8?q?=E3=81=A1=E3=82=87=E3=81=A3=E3=81=A8?= =?UTF-8?q?=E8=AA=AC=E6=98=8E=E3=81=AE=E8=A3=9C=E8=B6=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker.py b/dislocker.py index 2316f13..2c098de 100644 --- a/dislocker.py +++ b/dislocker.py @@ -1078,7 +1078,7 @@ async def send_log(**kwargs): elif mode == "pcnickname": alt_name = str(kwargs.get("alt_name")) - log_embed = discord.Embed(title=f":pencil: PC {pc_number} 番 | PCのニックネーム変更通知", description=f"<@{discord_user_id}> さんによってPCのニックネームが変更されました。", color=0x1343EB) + log_embed = discord.Embed(title=f":pencil: PC {pc_number} 番 | PCのニックネーム変更通知", description=f"<@{discord_user_id}> さんによってPCのニックネームが変更されました。\nボタンに変更を適用する場合は、再度 /init コマンドでボタンを送信して下さい。\n古いボタンを削除することをお忘れなく!", color=0x1343EB) log_embed.add_field(name="ニックネーム", value=alt_name) await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(embed=log_embed) From e3d0e2ae24e6801723a5ed5a23553bc50a2ca8e5 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Tue, 1 Oct 2024 12:14:08 +0900 Subject: [PATCH 145/175] =?UTF-8?q?fstop=E3=81=AE=E3=81=A8=E3=81=8D?= =?UTF-8?q?=E3=81=AB=E3=83=AD=E3=82=B0=E3=81=AB=E7=90=86=E7=94=B1=E3=81=8C?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84=E3=83=90?= =?UTF-8?q?=E3=82=B0=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker.py b/dislocker.py index 2c098de..eecff32 100644 --- a/dislocker.py +++ b/dislocker.py @@ -1507,7 +1507,7 @@ async def fstop(interaction: discord.Interaction, pc_number: int, reason: str): if force_stop["result"] == 0: result_embed = discord.Embed(title=":white_check_mark: 処理が完了しました。", description=f'PC番号 {str(pc_number)} 番の使用登録は抹消されました。', color=0x56FF01) dislocker.log(title=f"[INFO] PC {pc_number} の使用を強制終了しました。", message=f"理由 | {reason}", flag=0) - await send_log(mode="fstop", pc_number=pc_number, discord_user_id=interaction.user.id, detail=reason) + await send_log(mode="fstop", pc_number=pc_number, discord_user_id=interaction.user.id, reason=reason) elif force_stop["result"] == 1: if force_stop["about"] == "not_used": result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'指定されたPCは使用されていないようです...', color=0xC91111) From af62f218c2714af996ad262fe96d1d345a7b1113 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Tue, 1 Oct 2024 12:15:43 +0900 Subject: [PATCH 146/175] =?UTF-8?q?about=E3=81=8B=E3=82=89reason=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E3=81=88=E3=81=9F=E3=81=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/dislocker.py b/dislocker.py index eecff32..21eabf2 100644 --- a/dislocker.py +++ b/dislocker.py @@ -927,7 +927,7 @@ class ReasonModal(discord.ui.Modal): if register["about"] == "ok": await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## キーボード番号 | {keyboard_number_show}\n## マウス番号 | {mouse_number_show}\n## 使用目的 | {self.reason_input_form.value}", ephemeral=True) - await send_log(mode="use", pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=self.reason_input_form.value, discord_user_id=interaction.user.id) + await send_log(mode="use", pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, reason=self.reason_input_form.value, discord_user_id=interaction.user.id) dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, 使用目的 | {self.reason_input_form.value}", flag=0) elif register["about"] == "pc_already_in_use_by_you": pc_usage_history = register["pc_usage_history"] @@ -1043,7 +1043,7 @@ async def send_log(**kwargs): if mode == "use": keyboard_number = int(kwargs.get("keyboard_number")) mouse_number = int(kwargs.get("mouse_number")) - detail = str(kwargs.get("detail")) + reason = str(kwargs.get("reason")) if keyboard_number == 0: keyboard_number_show = "自前" @@ -1060,7 +1060,7 @@ async def send_log(**kwargs): log_embed.add_field(name="PC番号", value=pc_number) log_embed.add_field(name="キーボード番号", value=keyboard_number_show) log_embed.add_field(name="マウス番号", value=mouse_number_show) - log_embed.add_field(name="使用目的", value=detail) + log_embed.add_field(name="使用目的", value=reason) elif mode == "stop": log_embed = discord.Embed(title=f":stop_button: PC {pc_number} 番 | 使用終了通知", description=f"<@{discord_user_id}> さんはPCの使用を終了しました。", color=0xE512EB) @@ -1288,7 +1288,7 @@ async def on_button(interaction: discord.Interaction): register = dislocker.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=reason) if register["about"] == "ok": await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## キーボード番号 | {str(keyboard_number_show)}\n## マウス番号 | {str(mouse_number_show)}\n## 使用目的 | {reason}", ephemeral=True) - await send_log(mode="use", pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=reason, discord_user_id=interaction.user.id) + await send_log(mode="use", pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, reason=reason, discord_user_id=interaction.user.id) dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, 使用目的 | {reason}", flag=0) elif register["about"] == "pc_already_in_use_by_you": pc_usage_history = register["pc_usage_history"] @@ -1362,12 +1362,12 @@ async def on_button(interaction: discord.Interaction): #使用者側のスラッシュコマンド @tree.command(name="use", description="パソコンの使用登録をします。通常はこのコマンドを使用する必要はありません。") -async def use(interaction: discord.Interaction, pc_number: int, keyboard_number: int, mouse_number: int, detail: str): - register = dislocker.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=detail) +async def use(interaction: discord.Interaction, pc_number: int, keyboard_number: int, mouse_number: int, reason: str): + register = dislocker.register(discord_user_id=interaction.user.id, name=interaction.user.name, display_name=interaction.user.display_name, pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=reason) if register["result"] == 0: - await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## 使用目的 | {detail}", ephemeral=True) - dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, 使用目的 | {detail}", flag=0) - await send_log(mode="use", pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, detail=detail, discord_user_id=interaction.user.id) + await interaction.response.send_message(f":white_check_mark: 使用が開始されました。\n>>> # パスワード | {register["output_dict"]["password"]}\n## PC番号 | {pc_number}\n## 使用目的 | {reason}", ephemeral=True) + dislocker.log(title=f"[INFO] PC番号{pc_number} の使用が開始されました。", message=f"名前 | {register["output_dict"]["name"]}, 使用目的 | {reason}", flag=0) + await send_log(mode="use", pc_number=pc_number, keyboard_number=keyboard_number, mouse_number=mouse_number, reason=reason, discord_user_id=interaction.user.id) elif register["result"] == 1: if register["about"] == "pc_already_in_use_by_other": await interaction.response.send_message(":x: 他の方がそのPCを使用中です。", ephemeral=True) From 5de0d66829ca6f454e8eb731bd7d634a07462c08 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Tue, 1 Oct 2024 12:23:06 +0900 Subject: [PATCH 147/175] =?UTF-8?q?=E3=82=BF=E3=82=A4=E3=83=A0=E3=82=A2?= =?UTF-8?q?=E3=82=A6=E3=83=88=E6=99=82=E3=81=AE=E3=83=AD=E3=82=B0=E9=80=81?= =?UTF-8?q?=E4=BF=A1=E3=82=92=E7=84=A1=E5=8A=B9=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dislocker.py b/dislocker.py index 21eabf2..ec31dba 100644 --- a/dislocker.py +++ b/dislocker.py @@ -999,8 +999,8 @@ class Monitor(): cursor.execute("SELECT * FROM club_member WHERE member_id = %s", (member_id,)) user_info = cursor.fetchall() stop = dislocker.stop(discord_user_id=user_info[0][3], bot_about="タイムアウトでBotによる強制停止。") - discord_user_id = dislocker.get_discord_user_id(member_id=member_id)["discord_user_id"] - asyncio.run_coroutine_threadsafe(send_log(mode="timeout", pc_number=i[0], discord_user_id=discord_user_id)) + #discord_user_id = dislocker.get_discord_user_id(member_id=member_id)["discord_user_id"] + #asyncio.run_coroutine_threadsafe(send_log(mode="timeout", pc_number=i[0], discord_user_id=discord_user_id)) dislocker.log(title=f"[INFO] パスワードのタイムアウト時間に達したため、強制停止されました。", flag=0) result = {"result": "STOP", "details": str(pc_usage)} else: From d88a40a360c53654456650e5529f8b048950dcfe Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Tue, 1 Oct 2024 15:32:33 +0900 Subject: [PATCH 148/175] =?UTF-8?q?=E3=82=AB=E3=83=90=E3=83=BC=E7=94=BB?= =?UTF-8?q?=E5=83=8F=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/dislocker_client.py b/dislocker_client.py index 40d42f0..793a4ce 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -17,6 +17,7 @@ import uuid import time import win32com.client import pythoncom +from PIL import Image app_name = "Dislocker" dislocker_dir = os.path.dirname(os.path.abspath(sys.argv[0])) @@ -242,7 +243,7 @@ class Lock(customtkinter.CTkToplevel): self.title(f'{app_name} | PC番号 {client_config["pc_number"]} | ロックされています') self.iconbitmap(default=resource_path + '\\icon\\dislocker.ico') - self.window_width = 600 + self.window_width = 760 self.window_height = 320 self.screen_width = self.winfo_screenwidth() self.screen_height = self.winfo_screenheight() @@ -263,13 +264,20 @@ class Lock(customtkinter.CTkToplevel): 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.cover_img = customtkinter.CTkImage(light_image=Image.open(resource_path + '\\dislocker_light.png'), dark_image=Image.open(resource_path + '\\dislocker_dark.png'), size=(160, 320)) + + self.cover_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') + self.cover_frame.grid(row=0, column=0, padx=10, pady=10, sticky="nsew") + self.cover_img_label = customtkinter.CTkLabel(self.cover_frame, image=self.cover_img, text="") + self.cover_img_label.grid(row=0, column=0, padx=0, pady=0) self.msg_title_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') - self.msg_title_frame.grid(row=0, column=0, padx=10, pady=10, sticky="nsew") + self.msg_title_frame.grid(row=1, column=0, padx=10, pady=10, sticky="nsew") - self.icon_title_1 = customtkinter.CTkLabel(self.msg_title_frame, text='😎', font=self.emoji_font, justify="left") - self.icon_title_1.grid(row=0, column=0, padx=10, sticky="w") + #self.icon_title_1 = customtkinter.CTkLabel(self.msg_title_frame, text='😎', font=self.emoji_font, justify="left") + #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") From 4969680f09a4e77a8ab070e26316a90956f1fb32 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Tue, 1 Oct 2024 15:36:47 +0900 Subject: [PATCH 149/175] =?UTF-8?q?cover=E3=83=87=E3=82=A3=E3=83=AC?= =?UTF-8?q?=E3=82=AF=E3=83=88=E3=83=AA=E3=81=AE=E9=99=A4=E5=A4=96=E3=82=92?= =?UTF-8?q?=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 080152b..86e7a3a 100644 --- a/.gitignore +++ b/.gitignore @@ -50,7 +50,6 @@ coverage.xml *.py,cover .hypothesis/ .pytest_cache/ -cover/ # Translations *.mo From 07742da1f5e45375317e151f2372c67a7558263e Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Tue, 1 Oct 2024 15:36:57 +0900 Subject: [PATCH 150/175] =?UTF-8?q?=E3=81=8C=E3=83=90=E3=83=BC=E7=94=BB?= =?UTF-8?q?=E5=83=8F=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/cover/dislocker_dark.png | Bin 0 -> 48223 bytes resource/cover/dislocker_light.png | Bin 0 -> 48922 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 resource/cover/dislocker_dark.png create mode 100644 resource/cover/dislocker_light.png diff --git a/resource/cover/dislocker_dark.png b/resource/cover/dislocker_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..4c2b76916e23d41ddc7882be3e4d451fbcc68984 GIT binary patch literal 48223 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 literal 0 HcmV?d00001 diff --git a/resource/cover/dislocker_light.png b/resource/cover/dislocker_light.png new file mode 100644 index 0000000000000000000000000000000000000000..b16de21c9acfaf36da5fae48aadeb3fe12e6ad97 GIT binary patch literal 48922 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 Date: Tue, 1 Oct 2024 15:45:49 +0900 Subject: [PATCH 151/175] =?UTF-8?q?=E3=83=AC=E3=82=A4=E3=82=A2=E3=82=A6?= =?UTF-8?q?=E3=83=88=E4=BF=AE=E6=AD=A3=E3=80=81=E3=82=AB=E3=83=90=E3=83=BC?= =?UTF-8?q?=E7=94=BB=E5=83=8F=E3=81=AE=E3=83=91=E3=82=B9=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dislocker_client.py b/dislocker_client.py index 793a4ce..779301e 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -265,16 +265,16 @@ class Lock(customtkinter.CTkToplevel): self.textbox_font = customtkinter.CTkFont(family="meiryo", size=14) self.button_font = customtkinter.CTkFont(family="meiryo", size=14) - self.cover_img = customtkinter.CTkImage(light_image=Image.open(resource_path + '\\dislocker_light.png'), dark_image=Image.open(resource_path + '\\dislocker_dark.png'), size=(160, 320)) + self.cover_img = customtkinter.CTkImage(light_image=Image.open(resource_path + '\\cover\\dislocker_light.png'), dark_image=Image.open(resource_path + '\\cover\\dislocker_dark.png'), size=(160, 320)) self.cover_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') - self.cover_frame.grid(row=0, column=0, padx=10, pady=10, sticky="nsew") + self.cover_frame.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") self.cover_img_label = customtkinter.CTkLabel(self.cover_frame, image=self.cover_img, text="") self.cover_img_label.grid(row=0, column=0, padx=0, pady=0) self.msg_title_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') - self.msg_title_frame.grid(row=1, column=0, padx=10, pady=10, sticky="nsew") + self.msg_title_frame.grid(row=0, column=1, padx=10, pady=10, sticky="nsew") #self.icon_title_1 = customtkinter.CTkLabel(self.msg_title_frame, text='😎', font=self.emoji_font, justify="left") #self.icon_title_1.grid(row=0, column=0, padx=10, sticky="w") From d1cf551fd380d0da79f662e3779bb2960b3ad0ae Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Tue, 1 Oct 2024 15:49:48 +0900 Subject: [PATCH 152/175] =?UTF-8?q?grid=E3=81=AEsticky=E3=82=92=E3=81=AA?= =?UTF-8?q?=E3=81=8F=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker_client.py b/dislocker_client.py index 779301e..7568f33 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -268,7 +268,7 @@ class Lock(customtkinter.CTkToplevel): self.cover_img = customtkinter.CTkImage(light_image=Image.open(resource_path + '\\cover\\dislocker_light.png'), dark_image=Image.open(resource_path + '\\cover\\dislocker_dark.png'), size=(160, 320)) self.cover_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') - self.cover_frame.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") + self.cover_frame.grid(row=0, column=0, padx=0, pady=0) self.cover_img_label = customtkinter.CTkLabel(self.cover_frame, image=self.cover_img, text="") self.cover_img_label.grid(row=0, column=0, padx=0, pady=0) From b6bbefbcbe96f1052dfce290f9e2bd600e4cf70b Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Tue, 1 Oct 2024 16:18:50 +0900 Subject: [PATCH 153/175] =?UTF-8?q?=E3=83=AC=E3=82=A4=E3=82=A2=E3=82=A6?= =?UTF-8?q?=E3=83=88=E3=82=92=E4=BF=AE=E6=AD=A3=E3=80=81=E6=96=87=E8=A8=80?= =?UTF-8?q?=E3=81=AE=E7=B7=A8=E9=9B=86=E3=80=81guitest=E3=83=A2=E3=83=BC?= =?UTF-8?q?=E3=83=89=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 48 ++++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/dislocker_client.py b/dislocker_client.py index 7568f33..07b30c7 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -178,13 +178,13 @@ class App(customtkinter.CTk): self.attributes('-fullscreen', True) self.attributes('-topmost', True) - if client_config["hard_lock"] == True: - exit_explorer = subprocess.run('taskkill /f /im explorer.exe', startupinfo=sp_startupinfo, stdout=subprocess.PIPE, text=True) - if exit_explorer.returncode == 0: - pass - else: - signout_session = subprocess.run('shutdown /l /f /t 3', startupinfo=sp_startupinfo, stdout=subprocess.PIPE, text=True) - error_msgbox = tkinter.messagebox.showerror(title=f"{app_name} | 初回処理のエラー", message=f"初回処理の実行にエラーが発生しました。\n自動的にサインアウトされます。") + if client_config["hard_lock"] == True: + exit_explorer = subprocess.run('taskkill /f /im explorer.exe', startupinfo=sp_startupinfo, stdout=subprocess.PIPE, text=True) + if exit_explorer.returncode == 0: + pass + else: + signout_session = subprocess.run('shutdown /l /f /t 3', startupinfo=sp_startupinfo, stdout=subprocess.PIPE, text=True) + error_msgbox = tkinter.messagebox.showerror(title=f"{app_name} | 初回処理のエラー", message=f"初回処理の実行にエラーが発生しました。\n自動的にサインアウトされます。") self.title(f"{app_name} | ロック中") self.iconbitmap(default=resource_path + '\\icon\\dislocker.ico') @@ -267,11 +267,14 @@ class Lock(customtkinter.CTkToplevel): self.cover_img = customtkinter.CTkImage(light_image=Image.open(resource_path + '\\cover\\dislocker_light.png'), dark_image=Image.open(resource_path + '\\cover\\dislocker_dark.png'), size=(160, 320)) + self.grid_columnconfigure(0, weight=1) + self.grid_columnconfigure(1, weight=6) + self.cover_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') - self.cover_frame.grid(row=0, column=0, padx=0, pady=0) + self.cover_frame.grid(row=0, column=0, padx=0, pady=0, sticky="nsew", rowspan=4) self.cover_img_label = customtkinter.CTkLabel(self.cover_frame, image=self.cover_img, text="") - self.cover_img_label.grid(row=0, column=0, padx=0, pady=0) + self.cover_img_label.grid(row=0, column=0, padx=0, pady=0, sticky="w") self.msg_title_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') self.msg_title_frame.grid(row=0, column=1, padx=10, pady=10, sticky="nsew") @@ -279,24 +282,27 @@ class Lock(customtkinter.CTkToplevel): #self.icon_title_1 = customtkinter.CTkLabel(self.msg_title_frame, text='😎', font=self.emoji_font, justify="left") #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_1 = customtkinter.CTkLabel(self.msg_title_frame, text=f'ちょっと待って!! ', font=self.title_font, justify="left") + self.msg_title_1.grid(row=0, column=0, padx=10, sticky="we") + + self.pc_number_title_1 = customtkinter.CTkLabel(self.msg_title_frame, text=f'PC番号 | {client_config["pc_number"]}', font=self.title_font, justify="right") + self.pc_number_title_1.grid(row=0, column=1, padx=10, sticky="e") 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_title_2.grid(row=1, column=0, padx=10, columnspan=2, sticky="w") self.msg_subtitle_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') - self.msg_subtitle_frame.grid(row=1, column=0, padx=10, pady=10, sticky="nsew") + self.msg_subtitle_frame.grid(row=1, column=1, padx=10, pady=0, sticky="nsew") self.msg_subtitle_frame.grid_columnconfigure(0, weight=1) - 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, sticky="ew") + self.msg_subtitle_1 = customtkinter.CTkLabel(self.msg_subtitle_frame, text='サインインするには、Discordを開き、Dislockerから送信された\nパスワードを入力してください。', font=self.general_font, justify="left") + self.msg_subtitle_1.grid(row=0, column=0, padx=10, sticky="w") - self.msg_subtitle_2 = customtkinter.CTkLabel(self.msg_subtitle_frame, text='※ パスワードの有効期限は発行から5分間です。', font=self.general_small_font, justify="left") + self.msg_subtitle_2 = customtkinter.CTkLabel(self.msg_subtitle_frame, text='※ パスワードの有効期限は発行から5分間です。期限が切れた場合は、もう一度発行してください。', 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.grid(row=2, column=0, padx=10, pady=10, sticky="nsew") + self.input_frame.grid(row=2, column=1, padx=10, pady=0, sticky="nsew") self.input_frame.columnconfigure(0, weight=1) self.password_entry = customtkinter.CTkEntry(self.input_frame, placeholder_text='パスワード', show='*', font=self.textbox_font) @@ -304,7 +310,7 @@ class Lock(customtkinter.CTkToplevel): self.password_entry.bind("", self.auth_start_ev) self.button_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color='transparent') - self.button_frame.grid(row=3, column=0, padx=10, pady=10, sticky="nsew") + self.button_frame.grid(row=3, column=1, 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) @@ -630,6 +636,12 @@ if __name__ == '__main__': else: print("エラーが発生しました。") + elif args[1] == "guitest": + init_result = init() + client_config["testing"] = True + app = App() + app.mainloop() + else: print("引数エラー。") else: From df9100d66f7b8b20eb97c541b07992f212566c5b Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Tue, 1 Oct 2024 23:52:03 +0900 Subject: [PATCH 154/175] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E5=81=9C=E6=AD=A2?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=82=92=E5=84=AA=E5=85=88=E3=81=97=E3=81=A6?= =?UTF-8?q?=E8=A1=8C=E3=81=86=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client_shutdown.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/dislocker_client_shutdown.py b/dislocker_client_shutdown.py index b7e5419..91428ac 100644 --- a/dislocker_client_shutdown.py +++ b/dislocker_client_shutdown.py @@ -106,17 +106,6 @@ class App(tkinter.Tk): def handler_close(self): print("停止処理を実行。") - if client_config["eraser"] == True: - appdata_local = os.path.expandvars("%LOCALAPPDATA%") - appdata_roaming = os.path.expandvars("%APPDATA%") - epic_del = self.delete_appdata(process_name="EpicGamesLauncher.exe", dir_path=f"{appdata_local}\\EpicGamesLauncher\\Saved") - chrome_del = self.delete_appdata(process_name="chrome.exe", dir_path=f"{appdata_local}\\Google\\Chrome\\User Data") - discord_del = self.delete_appdata(process_name="discord.exe", dir_path=f"{appdata_roaming}\\discord") - steam_del = self.delete_appdata(process_name="steam.exe", dir_path=f"{appdata_local}\\Steam") - ea_del = self.delete_appdata(process_name="EADesktop.exe", dir_path=f"{appdata_local}\\Electronic Arts") - riot_del = self.delete_appdata(process_name="RiotClientServices.exe", dir_path=f"{appdata_local}\\Riot Games\\Riot Client") - else: - print("削除処理をスキップ。") stop_url = client_config["auth_host_url"] + "/stop" stop_json = { "pc_number": int(client_config["pc_number"]), @@ -133,8 +122,24 @@ class App(tkinter.Tk): print("内部エラーにより停止処理に失敗しました。") except: print("ネットワークエラーにより停止処理に失敗しました。") - finally: - self.destroy() + + print("データ削除を実行。") + try: + if client_config["eraser"] == True: + appdata_local = os.path.expandvars("%LOCALAPPDATA%") + appdata_roaming = os.path.expandvars("%APPDATA%") + epic_del = self.delete_appdata(process_name="EpicGamesLauncher.exe", dir_path=f"{appdata_local}\\EpicGamesLauncher\\Saved") + chrome_del = self.delete_appdata(process_name="chrome.exe", dir_path=f"{appdata_local}\\Google\\Chrome\\User Data") + discord_del = self.delete_appdata(process_name="discord.exe", dir_path=f"{appdata_roaming}\\discord") + steam_del = self.delete_appdata(process_name="steam.exe", dir_path=f"{appdata_local}\\Steam") + ea_del = self.delete_appdata(process_name="EADesktop.exe", dir_path=f"{appdata_local}\\Electronic Arts") + riot_del = self.delete_appdata(process_name="RiotClientServices.exe", dir_path=f"{appdata_local}\\Riot Games\\Riot Client") + else: + print("削除処理をスキップ。") + except: + print("データ削除に失敗しました。") + + self.destroy() def icon(self): self.iconify() From d4fb94c0a28a6fb0afadaca0a8e69b2a640b9f30 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 3 Oct 2024 16:29:19 +0900 Subject: [PATCH 155/175] =?UTF-8?q?pc=E6=83=85=E5=A0=B1=E3=81=8C=E5=8F=96?= =?UTF-8?q?=E5=BE=97=E3=81=A7=E3=81=8D=E3=81=AA=E3=81=84=E3=83=90=E3=82=B0?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker.py b/dislocker.py index ec31dba..eb17d8a 100644 --- a/dislocker.py +++ b/dislocker.py @@ -1604,7 +1604,7 @@ async def pcinfo(interaction: discord.Interaction): if current_pc_list['using_member_id'] == None: pc_using_value = f'未使用' else: - discord_user_id = dislocker.get_discord_user_id(current_pc_list['using_member_id']) + discord_user_id = dislocker.get_discord_user_id(member_id=current_pc_list['using_member_id']) pc_using_value = f'<@{discord_user_id}> が使用中' result_embed.add_field(name=f'{pc_name_title}', value=f'{pc_using_value}') From 98574811f34f0f4fddc4f1e522471646ed04ca68 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 3 Oct 2024 16:54:53 +0900 Subject: [PATCH 156/175] =?UTF-8?q?PC=E6=83=85=E5=A0=B1=E3=81=AE=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC=E8=A1=A8=E7=A4=BA?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker.py b/dislocker.py index eb17d8a..b2ca053 100644 --- a/dislocker.py +++ b/dislocker.py @@ -1604,7 +1604,7 @@ async def pcinfo(interaction: discord.Interaction): if current_pc_list['using_member_id'] == None: pc_using_value = f'未使用' else: - discord_user_id = dislocker.get_discord_user_id(member_id=current_pc_list['using_member_id']) + discord_user_id = dislocker.get_discord_user_id(member_id=current_pc_list['using_member_id'])[discord_user_id] pc_using_value = f'<@{discord_user_id}> が使用中' result_embed.add_field(name=f'{pc_name_title}', value=f'{pc_using_value}') From aeb67bf093f0f961975aa46f0d41e2320ac281e9 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 3 Oct 2024 16:58:34 +0900 Subject: [PATCH 157/175] =?UTF-8?q?PC=E6=83=85=E5=A0=B1=E3=81=8C=E5=8F=96?= =?UTF-8?q?=E5=BE=97=E3=81=A7=E3=81=8D=E3=81=AA=E3=81=84=E3=83=90=E3=82=B0?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker.py b/dislocker.py index b2ca053..b932b5f 100644 --- a/dislocker.py +++ b/dislocker.py @@ -1604,7 +1604,7 @@ async def pcinfo(interaction: discord.Interaction): if current_pc_list['using_member_id'] == None: pc_using_value = f'未使用' else: - discord_user_id = dislocker.get_discord_user_id(member_id=current_pc_list['using_member_id'])[discord_user_id] + discord_user_id = dislocker.get_discord_user_id(member_id=current_pc_list['using_member_id'])['discord_user_id'] pc_using_value = f'<@{discord_user_id}> が使用中' result_embed.add_field(name=f'{pc_name_title}', value=f'{pc_using_value}') From 6e7b59de65c8c6051cccb8b28acede465e9dd7e8 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 3 Oct 2024 20:50:18 +0900 Subject: [PATCH 158/175] =?UTF-8?q?=E5=BF=85=E8=A6=81=E3=81=AA=E3=83=A9?= =?UTF-8?q?=E3=82=A4=E3=83=96=E3=83=A9=E3=83=AA=E3=82=92=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 8 ++------ requirements_client.txt | 3 ++- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/requirements.txt b/requirements.txt index b5e4974..9d4be8d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,4 @@ discord.py -flask -flask-login psycopg2-binary -winotify -customtkinter -keyboard -requests \ No newline at end of file +openpyxl +flask \ No newline at end of file diff --git a/requirements_client.txt b/requirements_client.txt index 8c989de..c08283b 100644 --- a/requirements_client.txt +++ b/requirements_client.txt @@ -1,4 +1,5 @@ customtkinter winotify keyboard -requests \ No newline at end of file +requests +pywin32 \ No newline at end of file From 2d963de60f6c0326e47291e443e3534f218c0858 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Thu, 3 Oct 2024 21:33:43 +0900 Subject: [PATCH 159/175] =?UTF-8?q?PostgreSQL=E3=81=AE=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E3=83=A7=E3=83=B3=E3=82=9216=E3=81=AB=E5=9B=BA?= =?UTF-8?q?=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- compose.yml | 2 +- compose_db.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compose.yml b/compose.yml index f498f9c..c814bfc 100644 --- a/compose.yml +++ b/compose.yml @@ -28,7 +28,7 @@ services: - dislocker_network db: - image: postgres:alpine3.20 + image: postgres:16-alpine3.20 restart: always environment: - TZ=Asia/Tokyo diff --git a/compose_db.yml b/compose_db.yml index 7cff067..f2a5251 100644 --- a/compose_db.yml +++ b/compose_db.yml @@ -1,6 +1,6 @@ services: db: - image: postgres:alpine3.20 + image: postgres:16-alpine3.20 restart: always environment: - TZ=Asia/Tokyo From 43519b104d79665e9f1b4328e929248c4eff2793 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 4 Oct 2024 16:32:26 +0900 Subject: [PATCH 160/175] =?UTF-8?q?=E3=83=87=E3=83=90=E3=82=A4=E3=82=B9?= =?UTF-8?q?=E3=83=AA=E3=82=B9=E3=83=88=E3=81=AE=E9=80=81=E4=BF=A1=E3=82=92?= =?UTF-8?q?=E7=84=A1=E5=8A=B9=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dislocker_client.py b/dislocker_client.py index 07b30c7..c2fee77 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -388,7 +388,8 @@ class Lock(customtkinter.CTkToplevel): def auth(self): self.button_disable() password = str(self.password_entry.get()) - devices = self.get_input_devices() + #devices = self.get_input_devices() + devices = [] if len(password) == 10: print("マスターパスワードで認証を試行します。") From e65f6024d3ed10a52c7a667dc419db6c5ed3ed06 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Fri, 4 Oct 2024 16:38:57 +0900 Subject: [PATCH 161/175] =?UTF-8?q?=E3=83=87=E3=83=90=E3=82=A4=E3=82=B9?= =?UTF-8?q?=E3=83=AA=E3=82=B9=E3=83=88=E3=81=8C=E7=A9=BA=E3=81=AE=E5=A0=B4?= =?UTF-8?q?=E5=90=88=E3=81=AE=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_auth.py | 49 +++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/dislocker_auth.py b/dislocker_auth.py index 0c08fcd..0f1472e 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -66,34 +66,41 @@ class Auth(): pc_uuid = str(kwargs["pc_uuid"]) pc_token = str(kwargs["pc_token"]) if "device_list" in kwargs: - device_list = kwargs["device_list"] + if kwargs["device_list"] == []: + device_list = None + else: + device_list = kwargs["device_list"] else: - pass - keyboard_number = "own" - mouse_number = "own" + device_list = None + + keyboard_number = 0 + mouse_number = 0 if "password_hash" in kwargs: password_hash = str(kwargs["password_hash"]) cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s AND password_hash = %s AND pc_uuid = %s AND pc_token = %s", (pc_number, password_hash, pc_uuid, pc_token)) pc_info = cursor.fetchall() if pc_info: - for device in device_list: - cursor.execute("SELECT * FROM keyboard_list WHERE device_instance_path = %s", (device["device_instance_path"],)) - keyboard_record = cursor.fetchall() - if keyboard_record: - keyboard_number = int(keyboard_record[0][0]) - break - else: - pass - - for device in device_list: - cursor.execute("SELECT * FROM mouse_list WHERE device_instance_path = %s", (device["device_instance_path"],)) - mouse_record = cursor.fetchall() - if mouse_record: - mouse_number = int(mouse_record[0][0]) - break - else: - pass + if device_list == None: + pass + else: + for device in device_list: + cursor.execute("SELECT * FROM keyboard_list WHERE device_instance_path = %s", (device["device_instance_path"],)) + keyboard_record = cursor.fetchall() + if keyboard_record: + keyboard_number = int(keyboard_record[0][0]) + break + else: + pass + + for device in device_list: + cursor.execute("SELECT * FROM mouse_list WHERE device_instance_path = %s", (device["device_instance_path"],)) + mouse_record = cursor.fetchall() + if mouse_record: + mouse_number = int(mouse_record[0][0]) + break + else: + pass return {"result": 0, "about": "ok", "output_dict": {"keyboard_number": keyboard_number, "mouse_number": mouse_number}} else: From d643e6d1badbb1d332ea5b6d8c7a481d39268fbc Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 5 Oct 2024 09:35:14 +0900 Subject: [PATCH 162/175] =?UTF-8?q?#20=20PC=E3=81=AE=E7=99=BB=E9=8C=B2?= =?UTF-8?q?=E3=82=92=E8=A7=A3=E9=99=A4=E3=81=99=E3=82=8B=E3=82=B9=E3=83=A9?= =?UTF-8?q?=E3=83=83=E3=82=B7=E3=83=A5=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89?= =?UTF-8?q?=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/dislocker.py b/dislocker.py index b932b5f..b45ef14 100644 --- a/dislocker.py +++ b/dislocker.py @@ -898,6 +898,31 @@ class DL(): finally: if cursor: cursor.close() + + def pc_unregister(self, **kwargs): + try: + cursor = self.db.cursor() + + if 'pc_number' in kwargs: + pc_number = int(kwargs["pc_number"]) + cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s", (pc_number,)) + pc_list = cursor.fetchall() + if pc_list: + cursor.execute("DELETE FROM pc_list WHERE pc_number = %s", (pc_number,)) + self.db.commit() + return {"result": 0, "about": "ok"} + else: + return {"result": 1, "about": "not_exists"} + else: + return {"result": 1, "about": "syntax_error"} + + except Exception as error: + self.log(title=f"[ERROR] PCの登録解除中にエラーが発生しました。 {str(error.__class__.__name__)}", message=str(error.args), flag=1) + return {"result": 1, "about": "error", "output_dict": {"error_class_name": str(error.__class__.__name__), "error_args": str(error.args)}} + + finally: + if cursor: + cursor.close() class ReasonModal(discord.ui.Modal): @@ -1081,6 +1106,14 @@ async def send_log(**kwargs): log_embed = discord.Embed(title=f":pencil: PC {pc_number} 番 | PCのニックネーム変更通知", description=f"<@{discord_user_id}> さんによってPCのニックネームが変更されました。\nボタンに変更を適用する場合は、再度 /init コマンドでボタンを送信して下さい。\n古いボタンを削除することをお忘れなく!", color=0x1343EB) log_embed.add_field(name="ニックネーム", value=alt_name) + elif mode == "pcunreg": + alt_name = str(kwargs.get("alt_name")) + if alt_name == None: + log_embed = discord.Embed(title=f":x: PC {pc_number} 番 | PCの登録解除通知", description=f"<@{discord_user_id}> さんによってPCの登録が解除されました。", color=0xE512EB) + else: + log_embed = discord.Embed(title=f":x: PC {pc_number} 番 | PCの登録解除通知", description=f"<@{discord_user_id}> さんによってPCの登録が解除されました。", color=0xE512EB) + log_embed.add_field(name="ニックネーム", value=alt_name) + await client.get_channel(dislocker.server_config["bot"]["log_channel_id"]).send(embed=log_embed) return {"result": 0, "about": "ok"} @@ -1630,6 +1663,30 @@ async def pcnickname(interaction: discord.Interaction, pc_number: int, nickname: await interaction.response.send_message(embed=result_embed, ephemeral=True) +@tree.command(name="pcunreg", description="PCの登録を解除します。") +@discord.app_commands.default_permissions(administrator=True) +async def pcunreg(interaction: discord.Interaction, pc_number:int): + if interaction.guild_id in dislocker.server_config["bot"]["server_id"] or interaction.user.id in dislocker.server_config["bot"]["admin_user_id"]: + pc_dict = dislocker.get_pc_list()['output_dict'].get(pc_number) + if pc_dict == None: + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'指定されたPC番号には登録されていません。', color=0xC91111) + await interaction.response.send_message(embed=result_embed, ephemeral=True) + else: + nickname = pc_dict['alt_name'] + pc_unregister = dislocker.pc_unregister(pc_number=pc_number) + if pc_unregister["result"] == 0: + if nickname == None: + result_embed = discord.Embed(title=":white_check_mark: 操作が完了しました。", description=f'PC {str(pc_number)} 番 の登録は解除されました。', color=0x56FF01) + else: + result_embed = discord.Embed(title=":white_check_mark: 操作が完了しました。", description=f'PC {str(pc_number)} 番 | {str(nickname)}の登録は解除されました。', color=0x56FF01) + + await send_log(mode="pcunreg", pc_number=pc_number, discord_user_id=interaction.user.id, alt_name=nickname) + + else: + result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。登録は解除されません。', color=0xC91111) + result_embed.add_field(name=f"{pc_unregister['output_dict']['error_class_name']}", value=f"{pc_unregister['output_dict']['error_args']}") + + await interaction.response.send_message(embed=result_embed, ephemeral=True) if dislocker.init_result == "ok": print("Botを起動します...") From ee4061d10b27ba6b9b709a04f931f43942941e8e Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 5 Oct 2024 09:35:49 +0900 Subject: [PATCH 163/175] =?UTF-8?q?=E5=AD=98=E5=9C=A8=E3=81=97=E3=81=AA?= =?UTF-8?q?=E3=81=84PC=E7=95=AA=E5=8F=B7=E3=81=AE=E5=A0=B4=E5=90=88?= =?UTF-8?q?=E3=81=AFINSERT=E3=81=A7=E3=83=AC=E3=82=B3=E3=83=BC=E3=83=89?= =?UTF-8?q?=E3=82=92=E4=BD=9C=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_auth.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/dislocker_auth.py b/dislocker_auth.py index 0f1472e..04a47e3 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -307,18 +307,29 @@ class Auth(): cursor.execute("SELECT pc_uuid FROM pc_list WHERE pc_number = %s", (pc_number,)) pc_record = cursor.fetchall() pc_record_uuid = pc_record[0][0] - if pc_record_uuid == None: + cursor.execute("SELECT pc_number FROM pc_list WHERE pc_number = %s", (pc_number,)) + pc_record = cursor.fetchall() + pc_record_number = pc_record[0][0] + if pc_record_number == None: pc_token = self.token_generate(36) master_password = self.master_password_generate(16) master_password_hash = self.hash_genarate(master_password) - cursor.execute("UPDATE pc_list SET pc_uuid = %s, pc_token = %s, master_password = %s WHERE pc_number = %s", (pc_uuid, pc_token, master_password, pc_number)) + cursor.execute("INSERT INTO pc_list (pc_number, pc_uuid, pc_token, master_password) VALUES (%s, %s, %s, %s)", (pc_number, pc_uuid, pc_token, master_password)) self.db.commit() return {"result": 0, "about": "ok", "output_dict": {"pc_token": pc_token, "master_password": master_password, "master_password_hash": master_password_hash}} else: - return {"result": 1, "about": "exist"} + if pc_record_uuid == None: + pc_token = self.token_generate(36) + master_password = self.master_password_generate(16) + master_password_hash = self.hash_genarate(master_password) + cursor.execute("UPDATE pc_list SET pc_uuid = %s, pc_token = %s, master_password = %s WHERE pc_number = %s", (pc_uuid, pc_token, master_password, pc_number)) + self.db.commit() + return {"result": 0, "about": "ok", "output_dict": {"pc_token": pc_token, "master_password": master_password, "master_password_hash": master_password_hash}} + else: + return {"result": 1, "about": "exist"} except Exception as error: - print("停止処理中にエラーが発生しました。\nエラー内容") + print("PCの登録処理中にエラーが発生しました。\nエラー内容") print(str(error.__class__.__name__)) print(str(error.args)) print(str(error)) From 461bcf6fba38ac7efbc8aefaa220fef88a65ca3d Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 5 Oct 2024 09:48:00 +0900 Subject: [PATCH 164/175] =?UTF-8?q?=E3=83=AA=E3=83=AC=E3=83=BC=E3=82=B7?= =?UTF-8?q?=E3=83=A7=E3=83=B3=E3=81=AE=E9=96=A2=E4=BF=82=E3=81=A7DELETE?= =?UTF-8?q?=E3=81=AFUPDATE=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker.py b/dislocker.py index b45ef14..71a0aac 100644 --- a/dislocker.py +++ b/dislocker.py @@ -908,7 +908,7 @@ class DL(): cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s", (pc_number,)) pc_list = cursor.fetchall() if pc_list: - cursor.execute("DELETE FROM pc_list WHERE pc_number = %s", (pc_number,)) + cursor.execute("UPDATE pc_list SET using_member_id = NULL, password_hash = NULL, pc_uuid = NULL, pc_token = NULL, master_password = NULL, detail = NULL, alt_name = NULL WHERE pc_number = %s", (pc_number,)) self.db.commit() return {"result": 0, "about": "ok"} else: From bc35550693d4f7ac26f8c9e07ae9dbc9ee64f010 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 5 Oct 2024 09:55:12 +0900 Subject: [PATCH 165/175] =?UTF-8?q?=E7=99=BB=E9=8C=B2=E3=81=95=E3=82=8C?= =?UTF-8?q?=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84PC=E3=81=AE=E3=83=9C?= =?UTF-8?q?=E3=82=BF=E3=83=B3=E3=81=AF=E9=80=81=E4=BF=A1=E3=81=97=E3=81=AA?= =?UTF-8?q?=E3=81=84=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/dislocker.py b/dislocker.py index 71a0aac..c2ca9c7 100644 --- a/dislocker.py +++ b/dislocker.py @@ -1587,10 +1587,14 @@ async def button_init(interaction: discord.Interaction, text_channel: discord.Te pc_button_view = discord.ui.View(timeout=None) for i in pc_list["output_dict"].keys(): current_pc_list = pc_list['output_dict'][i] - if current_pc_list['alt_name'] == None: - pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(current_pc_list['pc_number'])} 番", custom_id=f"pcregister_{str(current_pc_list['pc_number'])}") + if current_pc_list['pc_token'] == None: + pass else: - pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(current_pc_list['pc_number'])} 番 | ({current_pc_list['alt_name']})", custom_id=f"pcregister_{str(current_pc_list['pc_number'])}") + if current_pc_list['alt_name'] == None: + pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(current_pc_list['pc_number'])} 番", custom_id=f"pcregister_{str(current_pc_list['pc_number'])}") + else: + pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(current_pc_list['pc_number'])} 番 | ({current_pc_list['alt_name']})", custom_id=f"pcregister_{str(current_pc_list['pc_number'])}") + pc_button_view.add_item(pc_register_button) await client.get_channel(text_channel.id).send(f'# :index_pointing_at_the_viewer: 使いたいPCの番号を選んでください!', view=pc_button_view) From 4547e05700ad03b25a978740ae14a39977a1ffe5 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 5 Oct 2024 09:58:13 +0900 Subject: [PATCH 166/175] =?UTF-8?q?pc=5Ftoken=E3=82=92=E5=8F=96=E5=BE=97?= =?UTF-8?q?=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dislocker.py b/dislocker.py index c2ca9c7..e76d02e 100644 --- a/dislocker.py +++ b/dislocker.py @@ -800,13 +800,13 @@ class DL(): cursor.execute("SELECT * FROM pc_list WHERE pc_number = %s", (pc_number,)) pc_list = cursor.fetchall() - return {"result": 0, "about": "ok", "output_dict": {pc_number: {"pc_number": pc_list[0][0], "using_member_id": pc_list[0][1], "master_password": [0][5], "detail": pc_list[0][6], "alt_name": pc_list[0][7]}}} + return {"result": 0, "about": "ok", "output_dict": {pc_number: {"pc_number": pc_list[0][0], "using_member_id": pc_list[0][1], "pc_token": i[4], "master_password": [0][5], "detail": pc_list[0][6], "alt_name": pc_list[0][7]}}} else: cursor.execute("SELECT * FROM pc_list ORDER BY pc_number") pc_list = cursor.fetchall() pc_list_base = {} for i in pc_list: - pc_list_base[i[0]] = {"pc_number": i[0], "using_member_id": i[1], "master_password": i[5], "detail": i[6], "alt_name": i[7]} + pc_list_base[i[0]] = {"pc_number": i[0], "using_member_id": i[1], "pc_token": i[4], "master_password": i[5], "detail": i[6], "alt_name": i[7]} return {"result": 0, "about": "ok", "output_dict": pc_list_base} From 08a1178209239755e69b54f3c11036f6f78dde8a Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 5 Oct 2024 10:00:14 +0900 Subject: [PATCH 167/175] =?UTF-8?q?init=E3=81=A7=E3=83=9C=E3=82=BF?= =?UTF-8?q?=E3=83=B3=E3=81=8C=E9=80=81=E4=BF=A1=E3=81=A7=E3=81=8D=E3=81=AA?= =?UTF-8?q?=E3=81=84=E3=83=90=E3=82=B0=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker.py b/dislocker.py index e76d02e..d4c3c42 100644 --- a/dislocker.py +++ b/dislocker.py @@ -1595,7 +1595,7 @@ async def button_init(interaction: discord.Interaction, text_channel: discord.Te else: pc_register_button = discord.ui.Button(style=discord.ButtonStyle.primary, label=f"{str(current_pc_list['pc_number'])} 番 | ({current_pc_list['alt_name']})", custom_id=f"pcregister_{str(current_pc_list['pc_number'])}") - pc_button_view.add_item(pc_register_button) + pc_button_view.add_item(pc_register_button) await client.get_channel(text_channel.id).send(f'# :index_pointing_at_the_viewer: 使いたいPCの番号を選んでください!', view=pc_button_view) dislocker.log(title=f"[INFO] サーバーで初回処理を実行しました。", flag=0) From a0056ef2c4494df9de2967fe3eaa17987187d72e Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 5 Oct 2024 10:09:06 +0900 Subject: [PATCH 168/175] =?UTF-8?q?=E7=99=BB=E9=8C=B2=E6=99=82=E3=81=AE?= =?UTF-8?q?=E3=82=B9=E3=83=86=E3=83=BC=E3=82=BF=E3=82=B9=E3=82=B3=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_auth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dislocker_auth.py b/dislocker_auth.py index 04a47e3..e329fef 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -371,9 +371,9 @@ def register(): return jsonify({'message': 'ok', 'pc_token': pc_token, 'master_password': master_password, 'master_password_hash': master_password_hash}), 200 elif register_result["result"] == 1: if register_result["about"] == "exist": - return jsonify({'message': 'exist'}), 401 + return jsonify({'message': 'exist'}), 400 else: - return jsonify({'message': 'damedesu'}), 401 + return jsonify({'message': 'damedesu'}), 500 else: return jsonify({'message': 'damedesu'}), 401 From 9e01485e0a2f0e0507a4f6b009f218106006d1df Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 5 Oct 2024 10:14:54 +0900 Subject: [PATCH 169/175] =?UTF-8?q?=E6=96=B0=E8=A6=8FPC=E3=81=8C=E7=99=BB?= =?UTF-8?q?=E9=8C=B2=E3=81=A7=E3=81=8D=E3=81=AA=E3=81=84=E3=83=90=E3=82=B0?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_auth.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dislocker_auth.py b/dislocker_auth.py index e329fef..6befeae 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -305,12 +305,12 @@ class Auth(): pc_number = int(kwargs["pc_number"]) pc_uuid = str(kwargs["pc_uuid"]) cursor.execute("SELECT pc_uuid FROM pc_list WHERE pc_number = %s", (pc_number,)) - pc_record = cursor.fetchall() - pc_record_uuid = pc_record[0][0] + pc_record_uuid_source = cursor.fetchall() + pc_record_uuid = pc_record_uuid_source[0][0] cursor.execute("SELECT pc_number FROM pc_list WHERE pc_number = %s", (pc_number,)) - pc_record = cursor.fetchall() - pc_record_number = pc_record[0][0] - if pc_record_number == None: + pc_record_number_source = cursor.fetchall() + + if pc_record_number_source == None: pc_token = self.token_generate(36) master_password = self.master_password_generate(16) master_password_hash = self.hash_genarate(master_password) From ecfc39695aa15964f2650f4560da45e3590b556e Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 5 Oct 2024 10:18:29 +0900 Subject: [PATCH 170/175] =?UTF-8?q?=E6=96=B0=E8=A6=8FPC=E3=81=8C=E7=99=BB?= =?UTF-8?q?=E9=8C=B2=E3=81=A7=E3=81=8D=E3=81=AA=E3=81=84=E3=83=90=E3=82=B0?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E3=81=9D=E3=81=AE=EF=BC=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_auth.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dislocker_auth.py b/dislocker_auth.py index 6befeae..c819015 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -304,9 +304,6 @@ class Auth(): cursor = self.db.cursor() pc_number = int(kwargs["pc_number"]) pc_uuid = str(kwargs["pc_uuid"]) - cursor.execute("SELECT pc_uuid FROM pc_list WHERE pc_number = %s", (pc_number,)) - pc_record_uuid_source = cursor.fetchall() - pc_record_uuid = pc_record_uuid_source[0][0] cursor.execute("SELECT pc_number FROM pc_list WHERE pc_number = %s", (pc_number,)) pc_record_number_source = cursor.fetchall() @@ -318,6 +315,9 @@ class Auth(): self.db.commit() return {"result": 0, "about": "ok", "output_dict": {"pc_token": pc_token, "master_password": master_password, "master_password_hash": master_password_hash}} else: + cursor.execute("SELECT pc_uuid FROM pc_list WHERE pc_number = %s", (pc_number,)) + pc_record_uuid_source = cursor.fetchall() + pc_record_uuid = pc_record_uuid_source[0][0] if pc_record_uuid == None: pc_token = self.token_generate(36) master_password = self.master_password_generate(16) From a8bf3ab4b7d0db3d0cab2633e14c7b1641f9eea6 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 5 Oct 2024 10:25:37 +0900 Subject: [PATCH 171/175] =?UTF-8?q?=E6=96=B0=E8=A6=8F=E7=99=BB=E9=8C=B2?= =?UTF-8?q?=E3=81=A7=E3=81=8D=E3=81=AA=E3=81=84=E3=83=90=E3=82=B0=E3=81=9D?= =?UTF-8?q?=E3=81=AE=EF=BC=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dislocker_auth.py b/dislocker_auth.py index c819015..3ae6611 100644 --- a/dislocker_auth.py +++ b/dislocker_auth.py @@ -307,7 +307,7 @@ class Auth(): cursor.execute("SELECT pc_number FROM pc_list WHERE pc_number = %s", (pc_number,)) pc_record_number_source = cursor.fetchall() - if pc_record_number_source == None: + if pc_record_number_source == []: pc_token = self.token_generate(36) master_password = self.master_password_generate(16) master_password_hash = self.hash_genarate(master_password) From 683be4281757e17ed5ae12aef28812b984dfd972 Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 5 Oct 2024 10:30:23 +0900 Subject: [PATCH 172/175] =?UTF-8?q?pcinfo=E3=81=A7=E7=99=BB=E9=8C=B2?= =?UTF-8?q?=E3=81=95=E3=82=8C=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84PC?= =?UTF-8?q?=E3=82=92=E9=9D=9E=E8=A1=A8=E7=A4=BA=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/dislocker.py b/dislocker.py index d4c3c42..7d5e140 100644 --- a/dislocker.py +++ b/dislocker.py @@ -1633,18 +1633,21 @@ async def pcinfo(interaction: discord.Interaction): for i in pc_list['output_dict'].keys(): current_pc_list = pc_list['output_dict'][i] - if current_pc_list['alt_name'] == None: - pc_name_title = f'{current_pc_list['pc_number']} 番' + if current_pc_list['pc_token'] == None: + pass else: - pc_name_title = f'{current_pc_list['pc_number']} 番 ({current_pc_list['alt_name']})' - - if current_pc_list['using_member_id'] == None: - pc_using_value = f'未使用' - else: - discord_user_id = dislocker.get_discord_user_id(member_id=current_pc_list['using_member_id'])['discord_user_id'] - pc_using_value = f'<@{discord_user_id}> が使用中' + if current_pc_list['alt_name'] == None: + pc_name_title = f'{current_pc_list['pc_number']} 番' + else: + pc_name_title = f'{current_pc_list['pc_number']} 番 ({current_pc_list['alt_name']})' + + if current_pc_list['using_member_id'] == None: + pc_using_value = f'未使用' + else: + discord_user_id = dislocker.get_discord_user_id(member_id=current_pc_list['using_member_id'])['discord_user_id'] + pc_using_value = f'<@{discord_user_id}> が使用中' - result_embed.add_field(name=f'{pc_name_title}', value=f'{pc_using_value}') + result_embed.add_field(name=f'{pc_name_title}', value=f'{pc_using_value}') else: result_embed = discord.Embed(title=":x: 操作に失敗しました。", description=f'サーバーでエラーが発生しています。', color=0xC91111) From c8d6517c3c10d11a09e698ea539fb8f29c5dfb4d Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 5 Oct 2024 10:37:35 +0900 Subject: [PATCH 173/175] =?UTF-8?q?=E7=99=BB=E9=8C=B2=E6=99=82=E3=81=AE?= =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/dislocker_client.py b/dislocker_client.py index c2fee77..0356935 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -158,9 +158,18 @@ def init(**kwargs): with open(client_config_path, "w") as w: json.dump(client_config, w, indent=4) return 2 - else: + + elif responce.status_code == 400: + msgbox = tkinter.messagebox.showerror(title=f"{app_name} | 登録時にエラー", message=f"登録時にエラーが発生しました。\n指定されたPC番号には既に登録されています。PCを置き換えたい場合は、Botから登録を解除してください。") + return 2 + + elif responce.status_code == 401: msgbox = tkinter.messagebox.showerror(title=f"{app_name} | 登録時にエラー", message=f"登録時にエラーが発生しました。\nワンタイムパスワードが間違っている可能性があります。") return 2 + + elif responce.status_code == 500: + msgbox = tkinter.messagebox.showerror(title=f"{app_name} | 登録時にエラー", message=f"登録時にエラーが発生しました。\nサーバーで内部エラーが発生しています。") + return 2 else: return 0 @@ -429,7 +438,7 @@ class Lock(customtkinter.CTkToplevel): elif responce.status_code == 500: print("内部エラーにより認証に失敗しました。") self.withdraw() - msgbox = tkinter.messagebox.showinfo(title=f"{app_name} | 内部エラー", message=f"内部エラーにより認証に失敗しました。") + msgbox = tkinter.messagebox.showinfo(title=f"{app_name} | 内部エラー", message=f"サーバーの内部エラーにより認証に失敗しました。") self.msg_subtitle_1.configure(text='内部エラーにより認証に失敗しました。 ') self.button_enable() self.deiconify() From af18cae3ff278e2683ab71a94d80f345fc13a77d Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 5 Oct 2024 10:45:01 +0900 Subject: [PATCH 174/175] =?UTF-8?q?=E5=89=8A=E9=99=A4=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=83=87=E3=83=BC=E3=82=BF=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=A7?= =?UTF-8?q?=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 6 ++++++ dislocker_client_shutdown.py | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/dislocker_client.py b/dislocker_client.py index 0356935..5ec1ccb 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -42,6 +42,12 @@ if not os.path.isfile(client_config_path): "master_password_hash": "", "testing": False, "eraser": True, + "erase_data": { + "example": { + "process_name": "example.exe", + "dir_path": "C:\\Users\\example\\AppData\\Local\\example" + } + }, "pc_uuid": "", "pc_token": "", "hard_lock": False diff --git a/dislocker_client_shutdown.py b/dislocker_client_shutdown.py index 91428ac..11428e2 100644 --- a/dislocker_client_shutdown.py +++ b/dislocker_client_shutdown.py @@ -134,6 +134,14 @@ class App(tkinter.Tk): steam_del = self.delete_appdata(process_name="steam.exe", dir_path=f"{appdata_local}\\Steam") ea_del = self.delete_appdata(process_name="EADesktop.exe", dir_path=f"{appdata_local}\\Electronic Arts") riot_del = self.delete_appdata(process_name="RiotClientServices.exe", dir_path=f"{appdata_local}\\Riot Games\\Riot Client") + for i in client_config['erase_data'].keys(): + if i == 'example': + pass + else: + erase_process_name = client_config['erase_data'][i]['process_name'] + erase_dir_path = client_config['erase_data'][i]['dir_path'] + erase_del = self.delete_appdata(process_name=erase_process_name, dir_path=erase_dir_path) + else: print("削除処理をスキップ。") except: From 5604c7a2bb78186ad87f69c5224d9649e46d96ac Mon Sep 17 00:00:00 2001 From: suti7yk5032 Date: Sat, 5 Oct 2024 11:03:42 +0900 Subject: [PATCH 175/175] =?UTF-8?q?=E5=89=8A=E9=99=A4=E3=83=87=E3=83=BC?= =?UTF-8?q?=E3=82=BF=E3=81=AE=E8=BF=BD=E5=8A=A0=E3=82=92=E7=84=A1=E5=8A=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dislocker_client.py | 2 +- dislocker_client_shutdown.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/dislocker_client.py b/dislocker_client.py index 5ec1ccb..eb81ecf 100644 --- a/dislocker_client.py +++ b/dislocker_client.py @@ -45,7 +45,7 @@ if not os.path.isfile(client_config_path): "erase_data": { "example": { "process_name": "example.exe", - "dir_path": "C:\\Users\\example\\AppData\\Local\\example" + "dir_path": "example" } }, "pc_uuid": "", diff --git a/dislocker_client_shutdown.py b/dislocker_client_shutdown.py index 11428e2..51d841e 100644 --- a/dislocker_client_shutdown.py +++ b/dislocker_client_shutdown.py @@ -134,6 +134,7 @@ class App(tkinter.Tk): steam_del = self.delete_appdata(process_name="steam.exe", dir_path=f"{appdata_local}\\Steam") ea_del = self.delete_appdata(process_name="EADesktop.exe", dir_path=f"{appdata_local}\\Electronic Arts") riot_del = self.delete_appdata(process_name="RiotClientServices.exe", dir_path=f"{appdata_local}\\Riot Games\\Riot Client") + """ for i in client_config['erase_data'].keys(): if i == 'example': pass @@ -141,6 +142,7 @@ class App(tkinter.Tk): erase_process_name = client_config['erase_data'][i]['process_name'] erase_dir_path = client_config['erase_data'][i]['dir_path'] erase_del = self.delete_appdata(process_name=erase_process_name, dir_path=erase_dir_path) + """ else: print("削除処理をスキップ。")