카테고리 없음
ㅈㅈㅈ
1231.
2025. 2. 21. 12:40
import tkinter as tk
from tkinter import ttk, scrolledtext
import requests
from Cryptodome.Cipher import AES
from Cryptodome.Util.Padding import pad, unpad
import base64
import threading
import time
import random
import string
class ConsoleStyle:
BG_COLOR = 'black'
FG_COLOR = '#00ff00' # 연한 녹색 (터미널 스타일)
FONT = ('Consolas', 10) # 고정폭 폰트
CURSOR_COLOR = '#00ff00'
SELECT_BG = '#003300' # 선택 영역 배경색
SELECT_FG = '#00ff00' # 선택 영역 텍스트색
class ChatClientGUI:
def __init__(self, root):
self.root = root
self.root.title("Secure Terminal Chat")
self.setup_gui()
self.setup_client()
self.apply_console_style()
self.setup_shortcuts() # 단축키 설정 추가
def setup_shortcuts(self):
# Ctrl+O 단축키 바인딩
self.root.bind('<Control-o>', lambda e: self.clear_chat())
def clear_chat(self):
# 채팅 영역의 모든 내용을 삭제
self.chat_area.delete(1.0, tk.END)
self.show_system_message("대화 내용이 삭제되었습니다.")
def apply_console_style(self):
# 윈도우 스타일
self.root.configure(bg=ConsoleStyle.BG_COLOR)
# 스타일 설정
style = ttk.Style()
style.configure('Console.TFrame', background=ConsoleStyle.BG_COLOR)
style.configure('Console.TLabel',
background=ConsoleStyle.BG_COLOR,
foreground=ConsoleStyle.FG_COLOR,
font=ConsoleStyle.FONT)
style.configure('Console.TButton',
background=ConsoleStyle.BG_COLOR,
foreground=ConsoleStyle.FG_COLOR,
font=ConsoleStyle.FONT)
style.configure('Console.TEntry',
background=ConsoleStyle.BG_COLOR,
foreground=ConsoleStyle.FG_COLOR,
font=ConsoleStyle.FONT,
fieldbackground=ConsoleStyle.BG_COLOR)
# 채팅 영역 스타일
self.chat_area.configure(
bg=ConsoleStyle.BG_COLOR,
fg=ConsoleStyle.FG_COLOR,
font=ConsoleStyle.FONT,
insertbackground=ConsoleStyle.CURSOR_COLOR,
selectbackground=ConsoleStyle.SELECT_BG,
selectforeground=ConsoleStyle.SELECT_FG
)
# 입력 영역 스타일
self.message_entry.configure(
style='Console.TEntry',
font=ConsoleStyle.FONT
)
def setup_client(self):
self.client_id = self.generate_client_id()
self.running = True
self.server_url = None
# 서버 연결 프레임
self.connect_frame = ttk.Frame(self.root, padding="10", style='Console.TFrame')
self.connect_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
ttk.Label(self.connect_frame, text="서버 IP:", style='Console.TLabel').grid(row=0, column=0, padx=5)
self.server_ip = ttk.Entry(self.connect_frame, width=20, style='Console.TEntry')
self.server_ip.insert(0, "localhost")
self.server_ip.grid(row=0, column=1, padx=5)
self.connect_btn = ttk.Button(self.connect_frame, text="연결",
command=self.connect_to_server,
style='Console.TButton')
self.connect_btn.grid(row=0, column=2, padx=5)
# 메시지 영역은 처음에는 숨김
self.message_frame.grid_remove()
def setup_gui(self):
# 메시지 프레임
self.message_frame = ttk.Frame(self.root, padding="10", style='Console.TFrame')
self.message_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 채팅 영역
self.chat_area = scrolledtext.ScrolledText(
self.message_frame,
wrap=tk.WORD,
width=80, # 더 넓게
height=24 # 더 높게
)
self.chat_area.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S))
# 메시지 입력
self.message_entry = ttk.Entry(
self.message_frame,
style='Console.TEntry',
width=70 # 더 넓게
)
self.message_entry.grid(row=1, column=0, sticky=(tk.W, tk.E), padx=5, pady=5)
self.message_entry.bind('<Return>', lambda e: self.send_message())
# 전송 버튼
self.send_button = ttk.Button(
self.message_frame,
text="전송",
command=self.send_message,
style='Console.TButton'
)
self.send_button.grid(row=1, column=1, sticky=(tk.E), pady=5)
# Grid 설정
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(1, weight=1)
self.message_frame.columnconfigure(0, weight=1)
def generate_client_id(self):
return ''.join(random.choices(string.ascii_letters + string.digits, k=8))
def connect_to_server(self):
server_ip = self.server_ip.get()
self.server_url = f'http://{server_ip}:5000'
try:
response = requests.post(f"{self.server_url}/register",
json={"client_id": self.client_id})
if response.status_code == 200:
self.show_system_message("서버에 연결되었습니다.")
self.connect_frame.grid_remove()
self.message_frame.grid()
self.receive_thread = threading.Thread(target=self.receive_messages)
self.receive_thread.daemon = True
self.receive_thread.start()
else:
self.show_system_message("서버 연결 실패")
except Exception as e:
self.show_system_message(f"연결 오류: {str(e)}")
def encrypt_message(self, message):
message_bytes = message.encode('utf-8')
padded_message = pad(message_bytes, AES.block_size)
cipher = AES.new(SECRET_KEY, AES.MODE_CBC, IV=IV)
encrypted_message = cipher.encrypt(padded_message)
return base64.b64encode(encrypted_message).decode('utf-8')
def decrypt_message(self, encrypted_message):
encrypted_bytes = base64.b64decode(encrypted_message)
cipher = AES.new(SECRET_KEY, AES.MODE_CBC, IV=IV)
decrypted_padded = cipher.decrypt(encrypted_bytes)
return unpad(decrypted_padded, AES.block_size).decode('utf-8')
def send_message(self):
if not self.server_url:
return
message = self.message_entry.get().strip()
if message:
try:
encrypted_message = self.encrypt_message(message)
response = requests.post(
f"{self.server_url}/send",
json={
"sender_id": self.client_id,
"message": encrypted_message
}
)
if response.status_code == 200:
self.message_entry.delete(0, tk.END)
self.show_message("me", message)
except Exception as e:
self.show_system_message(f"전송 오류: {str(e)}")
def receive_messages(self):
while self.running:
if not self.server_url:
time.sleep(1)
continue
try:
response = requests.post(
f"{self.server_url}/receive",
json={"client_id": self.client_id}
)
if response.status_code == 200:
messages = response.json()['messages']
for encrypted_message in messages:
try:
decrypted_message = self.decrypt_message(encrypted_message)
self.root.after(0, self.show_message, "other", decrypted_message)
except Exception as e:
print(f"메시지 복호화 오류: {str(e)}")
except Exception as e:
print(f"수신 오류: {str(e)}")
time.sleep(1)
def show_message(self, sender, message):
timestamp = time.strftime("%H:%M:%S")
if sender == "me":
formatted_message = f"[{timestamp}] >>> {message}\n"
else:
# 상대방 메시지는 ID 없이 표시
formatted_message = f"[{timestamp}] <<< {message}\n"
self.chat_area.insert(tk.END, formatted_message)
self.chat_area.see(tk.END)
def show_system_message(self, message):
timestamp = time.strftime("%H:%M:%S")
formatted_message = f"[{timestamp}] === {message} ===\n"
self.chat_area.insert(tk.END, formatted_message)
self.chat_area.see(tk.END)
def on_closing(self):
self.running = False
if self.server_url:
try:
requests.post(f"{self.server_url}/unregister",
json={"client_id": self.client_id})
except:
pass
self.root.destroy()
def main():
root = tk.Tk()
root.configure(bg='black')
# 윈도우 크기 설정
root.geometry("800x600") # 더 큰 창 크기
app = ChatClientGUI(root)
root.protocol("WM_DELETE_WINDOW", app.on_closing)
root.mainloop()
if __name__ == '__main__':
SECRET_KEY = b'01234567890123456789012345678901'
IV = b'0123456789012345'
main()