init: workspace opencode sync

This commit is contained in:
2026-08-15 00:28:08 +07:00
commit 2451170708
122 changed files with 10549 additions and 0 deletions
+159
View File
@@ -0,0 +1,159 @@
import socket, hashlib, re, time
class RouterOSApi:
def __init__(self, host, port=8728):
self.host = host; self.port = port; self.sock = None
def connect(self, username, password):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(10)
self.sock.connect((self.host, self.port))
self._write_sentence('/login', f'=name={username}', f'=password={password}')
resp = self._read_response()
if any(isinstance(r, tuple) and r[0] == '!trap' for r in resp):
chal = None
for item in resp:
if isinstance(item, dict) and 'ret' in item: chal = item['ret']
if chal and len(chal) == 32:
response = '00' + hashlib.md5(b'\x00' + password.encode() + bytes.fromhex(chal)).hexdigest()
self._write_sentence('/login', f'=name={username}', f'=response={response}')
resp = self._read_response()
return self
def _encode_length(self, length):
if length < 0x80: return bytes([length])
elif length < 0x4000: length |= 0x8000; return bytes([(length>>8)&0xFF, length&0xFF])
elif length < 0x200000: length |= 0xC00000; return bytes([(length>>16)&0xFF, (length>>8)&0xFF, length&0xFF])
elif length < 0x10000000: length |= 0xE0000000; return bytes([(length>>24)&0xFF, (length>>16)&0xFF, (length>>8)&0xFF, length&0xFF])
else: return bytes([0xF0, (length>>24)&0xFF, (length>>16)&0xFF, (length>>8)&0xFF, length&0xFF])
def _write_word(self, word): self.sock.sendall(self._encode_length(len(word)) + word.encode('utf-8'))
def _write_sentence(self, cmd, *words):
self._write_word(cmd)
for w in words: self._write_word(w)
self.sock.sendall(b'\x00')
def _read_byte(self): return self.sock.recv(1)
def _read_word(self):
first = self._read_byte()
if first == b'\x00': return None
lb = first[0]
if lb & 0x80:
if (lb & 0xC0) == 0x80: length = ((lb & 0x3F)<<8) + self._read_byte()[0]
elif (lb & 0xE0) == 0xC0: length = ((lb & 0x1F)<<16) + (self._read_byte()[0]<<8) + self._read_byte()[0]
elif (lb & 0xF0) == 0xE0: length = ((lb & 0x0F)<<24) + (self._read_byte()[0]<<16) + (self._read_byte()[0]<<8) + self._read_byte()[0]
else: length = (self._read_byte()[0]<<24) + (self._read_byte()[0]<<16) + (self._read_byte()[0]<<8) + self._read_byte()[0]
else: length = lb
data = b''
while len(data) < length:
chunk = self.sock.recv(length - len(data))
if not chunk: break
data += chunk
return data.decode('utf-8', errors='replace')
def _read_sentence(self):
words = []
while True:
w = self._read_word()
if w is None: break
words.append(w)
return words
def _read_response(self):
responses = []
while True:
sentence = self._read_sentence()
if not sentence: break
reply = sentence[0]; rest = sentence[1:]
if reply == '!done': break
elif reply == '!re':
d = {}
for w in rest:
if '=' in w:
parts = w.split('=', 2)
d[parts[1]] = parts[2] if len(parts) >= 3 else ''
else: d[w] = ''
responses.append(d)
elif reply == '!trap': responses.append(('!trap', rest))
elif reply == '!fatal': raise Exception(f"Fatal: {rest}")
else: responses.append((reply, rest))
return responses
def cmd(self, command, **attrs):
words = [command]
for k, v in attrs.items():
if k.startswith('?'): words.append(f'{k}={v}')
else: words.append(f'={k}={v}')
self._write_sentence(*words)
return self._read_response()
def close(self):
if self.sock: self.sock.close(); self.sock = None
def fix_script(s):
"""Safely fix :pic typo without creating :pickk corruption"""
# Step 0: undo previous over-fix :pickk -> :pick
s = s.replace(':pickk', ':pick')
# Step 1: protect existing :pick
s = s.replace(':pick', '\x00PICKGUARD\x00')
# Step 2: fix typo :pic -> :pick
s = s.replace(':pic', ':pick')
# Step 3: restore protected :pick
s = s.replace('\x00PICKGUARD\x00', ':pick')
return s
api = RouterOSApi('192.168.100.2')
api.connect('mi4', 'm14')
schedulers = ["Bulanan2", "Bulanan4", "vc-2K3J", "vc-5K10J", "Family", "DevB2"]
profiles = ["Bulanan2", "Bulanan4", "vc-2K3J", "vc-5K10J", "Family", "home", "DevB2"]
print("=== Fixing scheduler on-event ===")
for s in schedulers:
resp = api.cmd('/system/scheduler/print', **{'?name': s})
if not resp:
print(f"{s}: not found"); continue
oe = resp[0].get('on-event', '')
oid = resp[0].get('.id', '')
print(f"{s}: before fix: pic={oe.count(':pic')} pick={oe.count(':pick')} pickk={oe.count(':pickk')}")
fixed = fix_script(oe)
if fixed == oe:
print(f" No changes needed"); continue
print(f" after fix: pic={fixed.count(':pic')} pick={fixed.count(':pick')}")
api.cmd('/system/scheduler/set', **{'.id': oid, 'on-event': fixed})
# Verify
verify = api.cmd('/system/scheduler/print', **{'?name': s})
if verify:
oe2 = verify[0]['on-event']
print(f" verify: pic={oe2.count(':pic')} pick={oe2.count(':pick')}")
if ':pic' not in oe2 and ':pickk' not in oe2:
print(f" FIXED!")
else:
print(f" BROKEN: pic={':pic' in oe2} pickk={':pickk' in oe2}")
print("\n=== Fixing profile on-login ===")
for p in profiles:
resp = api.cmd('/ip/hotspot/user/profile/print', **{'?name': p})
if not resp:
print(f"{p}: not found"); continue
ol = resp[0].get('on-login', '')
oid = resp[0].get('.id', '')
print(f"{p}: before: pic={ol.count(':pic')} pick={ol.count(':pick')}")
fixed = fix_script(ol)
if fixed == ol:
print(f" No changes needed"); continue
print(f" after: pic={fixed.count(':pic')} pick={fixed.count(':pick')}")
api.cmd('/ip/hotspot/user/profile/set', **{'.id': oid, 'on-login': fixed})
verify = api.cmd('/ip/hotspot/user/profile/print', **{'?name': p})
if verify:
ol2 = verify[0]['on-login']
print(f" verify: pic={ol2.count(':pic')} pick={ol2.count(':pick')}")
if ':pic' not in ol2:
print(f" FIXED!")
api.close()
print("\nALL DONE")