~nickwinston123/armagetronad/arma_chatbot_config

8 by hackermans
1
import curses
2
import threading
3
import time
4
import os
5
import textwrap
6
import configparser
7
import logging
8
import sys
9
10
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
11
12
config = configparser.ConfigParser()
13
ini_path = os.path.join(os.path.dirname(__file__), 'arma_terminal_real.ini')
14
try:
15
    config.read(ini_path)
16
except Exception as e:
17
    logging.error(f"Error reading configuration: {e}")
18
    sys.exit(1)
19
20
try:
21
    CONSOLE_LOG       = config.get('Paths',    'console_log')
22
    COMMANDS_FILE     = config.get('Paths',    'commands_file')
23
    COMMAND_PREFIX    = config.get('Settings', 'command_prefix')
24
    MAX_LOG_LINES     = config.getint('Settings', 'max_log_lines', fallback=1000)
25
except Exception as e:
26
    logging.error(f"Error retrieving config values: {e}")
27
    sys.exit(1)
28
29
def tail_log(lines, lock, stop_event):
30
    with open(CONSOLE_LOG, 'r', encoding='utf-8', errors='ignore') as f:
31
        f.seek(0, os.SEEK_END)
32
        while not stop_event.is_set():
33
            chunk = f.readline()
34
            if not chunk:
35
                time.sleep(0.1)
36
                continue
37
            text = chunk.strip()
38
            if text:
39
                with lock:
40
                    lines.append(text)
41
            with lock:
42
                if len(lines) > MAX_LOG_LINES:
43
                    lines[:] = lines[-MAX_LOG_LINES:]
44
45
def draw_scrollbar(win, top_line, total_lines, height):
46
    if total_lines <= height:
47
        return
48
    scroll_height = height - 2
49
    bar_height = max(1, int(scroll_height * (height / total_lines)))
50
    top_pos = int(scroll_height * (top_line / total_lines))
51
    for i in range(scroll_height):
52
        char = '█' if top_pos <= i < top_pos + bar_height else '│'
53
        try:
54
            win.addch(i + 1, win.getmaxyx()[1] - 2, char)
55
        except curses.error:
56
            pass
57
58
def draw_screen(stdscr, lines, lock):
59
    curses.curs_set(1)
60
    stdscr.nodelay(True)
61
62
    height, width = stdscr.getmaxyx()
63
    log_h = height - 3
64
    log_win = curses.newwin(log_h, width, 0, 0)
65
    input_win = curses.newwin(3, width, log_h, 0)
66
67
    input_str = ""
68
    cursor_x = 0
69
    scroll_offset = 0
70
    history = []
71
    history_index = 0
72
73
    dragging = False
74
    drag_start_y = None
75
    drag_start_offset = None
76
77
    curses.mousemask(curses.ALL_MOUSE_EVENTS | curses.REPORT_MOUSE_POSITION)
78
79
    while True:
80
        with lock:
81
            raw_lines = lines[-MAX_LOG_LINES:]
82
        wrapped = []
83
        for line in raw_lines:
84
            wrapped.extend(textwrap.wrap(line, width - 3) or [''])
85
86
        visible_lines = log_h - 2
87
        max_offset = max(len(wrapped) - visible_lines, 0)
88
        scroll_offset = min(scroll_offset, max_offset)
89
90
        start_idx = max(0, len(wrapped) - visible_lines - scroll_offset)
91
        display = wrapped[start_idx:start_idx + visible_lines]
92
93
        log_win.erase()
94
        log_win.box()
95
        for idx, disp in enumerate(display):
96
            log_win.addnstr(idx + 1, 1, disp, width - 3)
97
        draw_scrollbar(log_win, start_idx, len(wrapped), log_h)
98
        log_win.refresh()
99
100
        input_win.erase()
101
        input_win.box()
102
        prompt = "> " + input_str
103
        input_win.addnstr(1, 1, prompt, width - 2)
104
        input_win.move(1, 2 + cursor_x)
105
        input_win.refresh()
106
107
        try:
108
            ch = stdscr.get_wch()
109
        except curses.error:
110
            time.sleep(0.05)
111
            continue
112
113
        if ch == curses.KEY_RESIZE:
114
            height, width = stdscr.getmaxyx()
115
            log_h = height - 3
116
            stdscr.erase(); stdscr.refresh()
117
            log_win.resize(log_h, width); log_win.mvwin(0, 0)
118
            input_win.resize(3, width); input_win.mvwin(log_h, 0)
119
            continue
120
121
        if ch == curses.KEY_UP:
122
            if history:
123
                history_index = max(history_index - 1, 0)
124
                input_str = history[history_index]
125
                cursor_x = len(input_str)
126
            continue
127
        if ch == curses.KEY_DOWN:
128
            if history:
129
                history_index = min(history_index + 1, len(history))
130
                input_str = history[history_index] if history_index < len(history) else ""
131
                cursor_x = len(input_str)
132
            continue
133
134
        if ch == curses.KEY_LEFT:
135
            cursor_x = max(0, cursor_x - 1)
136
            continue
137
        if ch == curses.KEY_RIGHT:
138
            cursor_x = min(len(input_str), cursor_x + 1)
139
            continue
140
141
        if ch == curses.KEY_PPAGE:
142
            scroll_offset = min(scroll_offset + 3, max_offset)
143
            continue
144
        if ch == curses.KEY_NPAGE:
145
            scroll_offset = max(scroll_offset - 3, 0)
146
            continue
147
148
        if ch == curses.KEY_END:
149
            scroll_offset = 0
150
            continue
151
152
        if ch == curses.KEY_MOUSE:
153
            try:
154
                _, mx, my, _, bstate = curses.getmouse()
155
                if bstate & curses.BUTTON1_PRESSED:
156
                    if 1 <= my < log_h - 1 and mx == width - 2:
157
                        dragging = True
158
                        drag_start_y = my
159
                        drag_start_offset = scroll_offset
160
                elif bstate & curses.BUTTON1_RELEASED:
161
                    dragging = False
162
                elif dragging and drag_start_y is not None:
163
                    dy = my - drag_start_y
164
                    scroll_offset = min(max(drag_start_offset - dy, 0), max_offset)
165
                elif bstate & curses.BUTTON4_PRESSED:
166
                    scroll_offset = min(scroll_offset + 1, max_offset)
167
                elif bstate & curses.BUTTON5_PRESSED:
168
                    scroll_offset = max(scroll_offset - 1, 0)
169
            except Exception:
170
                pass
171
            continue
172
173
        if isinstance(ch, str) and ch.isprintable():
174
            input_str = input_str[:cursor_x] + ch + input_str[cursor_x:]
175
            cursor_x += 1
176
        elif ch in (curses.KEY_BACKSPACE, '\b', '\x7f'):
177
            if cursor_x > 0:
178
                input_str = input_str[:cursor_x - 1] + input_str[cursor_x:]
179
                cursor_x -= 1
180
        elif ch == curses.KEY_DC:
181
            if cursor_x < len(input_str):
182
                input_str = input_str[:cursor_x] + input_str[cursor_x + 1:]
183
        elif ch == '\n':
184
            cmd = input_str.strip()
185
            input_str = ""
186
            cursor_x = 0
187
            if cmd:
188
                history.append(cmd)
189
            history_index = len(history)
190
            if cmd.lower() in ("exit", "quit"):
191
                return
192
            full_cmd = f"{COMMAND_PREFIX} {cmd}" if COMMAND_PREFIX else cmd
193
            try:
194
                with open(COMMANDS_FILE, 'a', encoding='utf-8') as f:
195
                    f.write(full_cmd + "\n")
196
                    f.flush()
197
                    os.fsync(f.fileno())
198
            except Exception as e:
199
                with lock:
200
                    lines.append(f"→ ERROR writing command: {e}")
201
            else:
202
                with lock:
203
                    lines.append(f"→ SENT: {full_cmd}")
204
        elif ch in (curses.KEY_EXIT, '\x1b'):
205
            return
206
207
def main(stdscr):
208
    lines = []
209
    lock = threading.Lock()
210
    stop_event = threading.Event()
211
212
    t = threading.Thread(target=tail_log, args=(lines, lock, stop_event), daemon=True)
213
    t.start()
214
215
    try:
216
        draw_screen(stdscr, lines, lock)
217
    finally:
218
        stop_event.set()
219
        t.join(0.1)
220
221
if __name__ == "__main__":
222
    import curses
223
    curses.wrapper(main)