-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclient.py
More file actions
250 lines (209 loc) · 8.57 KB
/
client.py
File metadata and controls
250 lines (209 loc) · 8.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
import threading
import socket
import sys
from datetime import datetime
import time
import os
import re
# Import select untuk Linux/Mac
try:
import select
except ImportError:
pass
# --- KONFIGURASI WARNA ---
try:
from colorama import init, Fore, Style
init(autoreset=True)
H_MERAH = Fore.RED
H_HIJAU = Fore.GREEN
H_KUNING = Fore.YELLOW
H_BIRU = Fore.CYAN
RESET = Style.RESET_ALL
except ImportError:
H_MERAH = H_HIJAU = H_KUNING = H_BIRU = RESET = ""
# --- DETEKSI OS UNTUK GETCH (INPUT MANUAL) ---
if os.name == 'nt': # Windows
import msvcrt
def get_char():
return msvcrt.getwch()
def input_available():
return msvcrt.kbhit()
else: # Linux / Mac
import tty, termios
def get_char():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
def input_available():
# Pastikan select diimport di atas
return select.select([sys.stdin], [], [], 0)[0] != []
# Global variable untuk status koneksi
is_connected = False
input_buffer = [] # Penampungan teks yang sedang diketik
print_lock = threading.Lock() # Kunci agar receive & write tidak tabrakan saat print
KATA_KASAR = ["bodoh", "jelek", "goblok", "anjing", "anjir", "malas", "bego"]
list_kata_sensor = ", ".join(KATA_KASAR).upper()
def refresh_line():
"""Fungsi untuk menggambar ulang baris input setelah tertimpa pesan"""
current_input = "".join(input_buffer)
# \r = kembali ke awal baris
# \033[K = hapus sisa baris ke kanan
sys.stdout.write(f"\r\033[K{H_HIJAU}Ketik pesan: {RESET}{current_input}")
sys.stdout.flush()
def sensor_text(text):
"""Sensor menggunakan REGEX agar menangkap semua variasi huruf besar/kecil"""
for kata in KATA_KASAR:
# Ignore case (tidak peduli huruf besar/kecil)
pattern = re.compile(re.escape(kata), re.IGNORECASE)
text = pattern.sub('*' * len(kata), text)
return text
def receive(sock, nickname):
"""Menerima pesan tanpa merusak input user"""
global is_connected
while True:
try:
message = sock.recv(1024).decode('utf-8')
if message == 'NICK':
sock.send(f"{H_HIJAU}{nickname}".encode('utf-8'))
else:
with print_lock:
# 1. Hapus baris input user saat ini (bersihkan layar bawah)
sys.stdout.write("\r\033[K")
# 2. Tampilkan pesan baru dari server
sys.stdout.write(f"{message}\n")
if "Server dimatikan" not in message:
# 3. Tampilkan KEMBALI apa yang sedang diketik user (Repainting)
refresh_line()
sys.stdout.flush()
except:
# Jika error (koneksi putus), jangan exit program, tapi matikan loop ini
is_connected = False
with print_lock:
sys.stdout.write("\r\033[K")
print(f"{H_MERAH}[!] Koneksi terputus dari server.{RESET} Tekan ENTER untuk konfigurasi ulang\n")
try:
sock.close()
except:
pass
break
def write(sock, nickname):
"""Mengirim pesan"""
global is_connected, input_buffer
# Tampilkan prompt awal
with print_lock:
refresh_line()
while is_connected:
try:
char = get_char() # Menunggu 1 tombol ditekan
if not is_connected:
break
# --- JIKA TOMBOL ENTER DITEKAN ---
if char == '\r' or char == '\n':
with print_lock:
text = "".join(input_buffer)
input_buffer.clear()
# Bersihkan baris input di layar
sys.stdout.write("\r\033[K")
# Cek command exit
if text.strip().upper() == 'EXIT':
print(f"{H_KUNING}Keluar dari chat.{RESET}")
is_connected = False
sock.close()
return
# Kirim pesan (jika tidak kosong)
if text.strip() != "":
text = sensor_text(text)
waktu = datetime.now().strftime('%d/%m/%Y %H:%M')
msg = f"[{waktu}] {H_BIRU}{nickname}{RESET}: {text}"
sock.send((msg).encode('utf-8'))
# Munculkan prompt baru (kosong)
refresh_line()
# --- JIKA TOMBOL BACKSPACE DITEKAN ---
# Windows: \b atau \x08, Linux: 127
elif char == '\b' or ord(char) == 8 or ord(char) == 127:
with print_lock:
if len(input_buffer) > 0:
input_buffer.pop() # Hapus huruf terakhir di memori
refresh_line() # Gambar ulang
# --- JIKA TOMBOL Ctrl+C ---
elif ord(char) == 3:
is_connected = False
sock.close()
sys.exit() # Keluar total dari aplikasi
# --- TOMBOL KARAKTER BIASA (Huruf/Angka) ---
else:
# Filter karakter aneh (non-printable)
if char.isprintable():
with print_lock:
input_buffer.append(char)
# Tampilkan hurufnya langsung (biar responsif)
sys.stdout.write(char)
sys.stdout.flush()
except (KeyboardInterrupt, EOFError):
sys.stdout.write(f"\r\033[K")
sock.close()
break
def start_client():
global is_connected, input_buffer
first_run = True
while True: # Loop utama aplikasi (agar bisa restart)
is_connected = False # Pastikan reset status
if not first_run:
print(f"\n{H_KUNING}Mengulang konfigurasi...{RESET}")
time.sleep(1) # Delay sedikit sebelum restart
input_buffer.clear()
first_run = False
print(f"\n{H_BIRU}=== KONFIGURASI KONEKSI BARU ==={RESET}")
print(f"(Ketik {H_MERAH}'EXIT'{RESET} atau tekan {H_MERAH}Ctrl+C{RESET} untuk keluar aplikasi)\n")
try:
# 1. Input Nickname
while True:
nickname = input(f"{H_KUNING}Masukkan Nama: {RESET}")
if nickname.strip().upper() == 'EXIT':
print("Dadah!")
sys.exit()
if nickname.strip() != '':
break
# 2. Input IP Server
SERVER_IP_INPUT = input(f"{H_KUNING}Masukan IP Server: {RESET}")
if SERVER_IP_INPUT.strip().upper() == 'EXIT':
sys.exit()
if SERVER_IP_INPUT == '':
SERVER_IP = '127.0.0.1'
else:
SERVER_IP = SERVER_IP_INPUT
PORT = 12345
# 3. Buat Socket Baru
client_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print(f"Menghubungkan ke {SERVER_IP} : {PORT}...")
client_sock.connect((SERVER_IP, PORT))
print(f"{H_KUNING}[+] Berhasil terhubung ke Server!{RESET}")
is_connected = True
# 4. Jalankan Thread Receive
receive_thread = threading.Thread(target=receive, args=(client_sock, nickname))
receive_thread.daemon = True
receive_thread.start()
# 5. Jalankan Write (Main Thread)
# Write akan blocking di sini sampai user exit atau koneksi putus
write(client_sock, nickname)
# Jika write selesai (karena putus/error), loop while True akan mengulang dari awal
except KeyboardInterrupt:
print(f"\n{H_KUNING}Koneksi terputus. Dadah!{RESET}")
sys.exit()
except ConnectionRefusedError:
print(f"{H_MERAH}[!] Gagal terhubung ke {SERVER_IP} : {PORT}. Server mungkin mati.{RESET}")
time.sleep(1)
# Loop akan mengulang ke atas
except Exception as e:
print(f"{H_MERAH}[!] Terjadi Error: {e}{RESET}")
try:
client_sock.close()
except:
pass
if __name__ == "__main__":
start_client()