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
+5
View File
@@ -0,0 +1,5 @@
*.log
__pycache__/
*.pyc.DS_Store
node_modules/.env
+160
View File
@@ -0,0 +1,160 @@
# Telegram Reset Button untuk Notifikasi Expired (MSR Hotspot)
> Ringkasan perubahan arsitektur notifikasi expired hotspot & tombol "🔄 Reset" via Telegram.
> Tanggal: 1 Agu 2026 · RouterOS v6.49.6 · Mikhmon (Docker)
---
## 1. Masalah Awal
1. Notifikasi "User X expired" sebelumnya dikirim oleh `notify-expire.php` di server Mikhmon, yang menampilkan inline keyboard **🔄 Reset** dengan `callback_data=reset_<user>`.
2. Router (MikroTik) tidak bisa menjangkau path lama (`192.168.100.6`, sudah mati). Scheduler di router mengirim notifikasi ke URL Telegram lama yang tidak lagi berfungsi.
3. Akibatnya pesan expired terkirim **tanpa tombol Reset**, dan callback reset tidak berfungsi.
## 2. Solusi Baru (Arsitektur)
```
[RouterOS Scheduler] --/tool/fetch sendMessage--> [Telegram Bot] --Kirim ke Grup (thread "Notif Expire")
^
[User klik 🔄 Reset] --> callback_query --> [Bot] --> poller getUpdates
|
v
[Host rl] cron tiap menit --> msr-poller.sh --> docker start mikhmon
|
v
telegram-cb.php (di container) --> RouterOS API reset user
|
v
Telegram: "✅ {user} has been reset"
```
### Komponen
| Komponen | Lokasi | Peran |
|---|---|---|
| Scheduler RouterOS | Router 192.168.100.2 | Kirim pesan expired + `reply_markup` tombol Reset langsung ke Telegram |
| Telegram Bot | `5115036608:AAGey...` | Terima/teruskan pesan & callback |
| Cron host | `/usr/local/bin/msr-poller.sh` di `rl` | Setiap menit: `docker start mikhmon` + curl `telegram-cb.php` |
| `telegram-cb.php` | `/var/www/html/` di container `mikhmon` | getUpdates → reset user di RouterOS → jawab callback + kirim konfirmasi |
| `lib/routeros_api.class.php` | `/var/www/html/lib/` | Koneksi RouterOS API dari PHP |
## 3. Perubahan yang Dilakukan
### 3.1 Scheduler RouterOS (Family, Bulanan2, Bulanan4)
`on-event` scheduler sekarang melakukan `/tool/fetch` ke Telegram `sendMessage` dengan:
- `chat_id=-1002554941429`, `message_thread_id=42` (thread "Notif Expire")
- `text=" . $name . "+expired`
- `reply_markup` = JSON inline keyboard **URL-encoded**:
```
%7B%22inline_keyboard%22%3A%5B%5B%7B%22text%22%3A%22%F0%9F%94%84%20Reset%22%2C%22callback_data%22%3A%22reset_" . $name . "%22%7D%5D%5D%7D
```
yang setara dengan:
```json
{"inline_keyboard":[[{"text":"🔴 Reset","callback_data":"reset_<name>"}]]}
```
Contoh cuplikan on-event Family:
```
tool fetch url=("https://api.telegram.org/bot5115036608:AAGey.../sendMessage?chat_id=-1002554941429&message_thread_id=42&text=" . $name . "+expired&reply_markup=" . "%7B%22inline_keyboard%22%3A...%22reset_" . $name . "%22%7D%5D%5D%7D") mode=https http-method=get keep-result=no
```
**Penting:** deduplikasi (`:if ($curlim = "")`) tetap dipertahankan agar setiap user hanya dikirim sekali.
### 3.2 Container Mikhmon — Install php81-curl
Container PHP 8.1 (Alpine) awalnya tanpa ekstensi cURL → `telegram-cb.php` error HTTP 500.
```sh
docker exec -u root mikhmon apk add --no-cache php81-curl
```
### 3.3 Perbaikan include path `telegram-cb.php`
Dari `dirname(__DIR__) . '/lib/routeros_api.class.php'` → `__DIR__ . '/lib/routeros_api.class.php'` (2 tempat), karena file berada langsung di `/var/www/html/`.
### 3.4 Poller Host (atasi sablier)
Container `mikhmon` dikelola **sablier** (traefik plugin) yang mematikannya setelah **5 menit idle** — cron di dalam container tidak bisa diandalkan. Solusi: cron di **host** yang men-starter container lalu memanggil callback.
`/usr/local/bin/msr-poller.sh`:
```bash
#!/bin/bash
CB_KEY="msr-2026-reset"
URL="http://172.18.0.15/telegram-cb.php?key=${CB_KEY}"
docker start mikhmon >/dev/null 2>&1
sleep 1
curl -s -m 30 "$URL" >/dev/null 2>&1
```
Crontab host (`crontab -l`):
```
* * * * * /usr/local/bin/msr-poller.sh >/dev/null 2>&1
```
Catatan:
- `docker start` idempotent (kalau sudah jalan, no-op) → otomatis menjaga container hidup.
- Polling maksimal latency ~1 menit setelah klik tombol.
### 3.5 Callback Handler `telegram-cb.php`
- Auth via `?key=msr-2026-reset` (403 kalau salah).
- `getUpdates` (hanya `callback_query`).
- Untuk `data` berawalan `reset_`:
1. Cari user di `/ip/hotspot/user/print?name=<user>`
2. `set limit-uptime=0, comment=''` + `reset-counters`
3. Hapus scheduler bernama `<user>` (jika ada)
4. `answerCallbackQuery` (`✅ {user} reset!`)
5. `sendMessage` ke chat/thread: `✅ *{user}* has been reset\nReady to use again`
- User tidak ditemukan → `answerCallbackQuery` alert `User {user} not found` (tanpa pesan).
- Meng-konfirmasi offset via `getUpdates?offset=<last_id+1>` setelah diproses.
## 4. Verifikasi
| Item | Hasil |
|---|---|
| Pesan expired + tombol Reset muncul di grup | ✅ Bubble punya `reply-markup-button` "Reset" |
| Klik tombol → callback `reset_<user>` sampai ke bot | ✅ `data: "reset_TMR-1"` via getUpdates |
| Poller cron memproses callback | ✅ `processed: 1` |
| User di-reset di router | ✅ `limit-uptime` 10s → None (unlimited), counters reset |
| Scheduler user dihapus | ✅ |
| Konfirmasi bot di grup | ✅ `msr_net: TMR-1 has been reset` (09:11) |
Test user `TMR-1` dibuat khusus lalu dihapus setelah verifikasi.
## 5. Operasional & Troubleshooting
### Cek status
- Poller: `ssh rl "cat /usr/local/bin/msr-poller.sh; crontab -l | grep poller"`
- Container: `ssh rl "docker ps --filter name=mikhmon --format '{{.Status}}'"`
- Callback pending: `curl 'http://172.18.0.15/telegram-cb.php?key=msr-2026-reset'` → harus `processed: 0` / `no updates`
- Webhook tidak terpasang (poller getUpdates aman): `getWebhookInfo` → `url: ""`, `allowed_updates:["callback_query"]`
### Gejala umum
- **HTTP 500 dari telegram-cb.php** → cek cURL: `docker exec mikhmon php -r 'var_dump(function_exists("curl_init"));'`
- **Container mati** → sablier mematikannya setelah 5m idle; poller harusnya men-starter. Cek log poller/cron.
- **Callback tidak terproses** → JANGAN baca `getUpdates` manual (maju offset & poller kehilangan callback). Biarkan poller cron yang memproses.
- **Tombol tidak muncul** → `reply_markup` tidak ter-encode dengan benar; cek `verify-markup.py` & on-event scheduler.
## 6. Kredensial & Referensi
- Router: `192.168.100.2`, API `remote.vpnmurahjogja.my.id:33206`, login `mi4`/`m14`
- Bot: `5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk`
- Chat/Grup: `-1002554941429`, thread `42` ("Notif Expire")
- Container: `mikhmon` (animegasan/mikhmon:v3-latest) di host `rl` (`root@100.100.31.46`, ssh alias `rl`, key `~/.ssh/pl`)
- Callback secret: `msr-2026-reset`
- Container IP: `172.18.0.15`
## 7. Skrip Bantu (folder `C:\Users\Admin\dev\mikhmon-fix\`)
| Skrip | Fungsi |
|---|---|
| `patch-notif2.py` | Patch on-event scheduler → tambah `reply_markup` Reset button |
| `verify-markup.py` | Verifikasi on-event berisi `reply_markup` & `reset_` |
| `verify-notif.py` | Verifikasi isi scheduler (api.telegram.org, tidak ada URL lama) |
| `dump-sched.py` | Dump penuh on-event scheduler |
| `telegram-cb.php` | Callback handler (sumber; di-deploy ke container) |
| `poller.sh` | Poller host (sumber; di-deploy ke `/usr/local/bin/msr-poller.sh`) |
| `mk-user.py` / `rm-user.py` | Buat / hapus user test |
| `send-expired-tmr.py` | Kirim pesan expired + tombol untuk user tertentu |
| `check-reset.py` | Cek limit-uptime & scheduler user setelah reset |
## 8. Catatan
- Bot menampilkan tombol sebagai "Reset" — teks `🔄` (U+1F504, `%F0%9F%94%84`) di-render Telegram sebagai ikon 🔄.
- `keep-result=no` di scheduler fetch (tidak menyimpan file respons di router). Saat debug bisa ubah `keep-result=yes` untuk lihat file `sendMessage?...`.
- Jika tombol ditekan dua kali, user kedua dikirim ulang notif "expired" (karena sudah reset, limit-uptime unlimited → tidak ada scheduler baru sampai limit diubah lagi).
+72
View File
@@ -0,0 +1,72 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(30)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
r = api.cmd('/file/print', **{'?type':'file'})
for x in r:
if isinstance(x,dict) and 'sendMessage' in x.get('name',''):
api.cmd('/file/print', **{'file': x.get('name')})
api.cmd('/file/remove', **{'=.id': x.get('.id')})
api.close()
+7
View File
@@ -0,0 +1,7 @@
<?php
$p1 = '/var/www/lib/routeros_api.class.php';
$p2 = '/var/www/html/lib/routeros_api.class.php';
echo "p1 exists: " . (file_exists($p1) ? 'YES' : 'NO') . "\n";
echo "p2 exists: " . (file_exists($p2) ? 'YES' : 'NO') . "\n";
$r = @file_get_contents('https://api.telegram.org/bot5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk/getMe');
echo "telegram getMe: " . substr($r ?: 'FAIL', 0, 120) . "\n";
+5
View File
@@ -0,0 +1,5 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', '1');
$_GET['key'] = 'msr-2026-reset';
include '/var/www/html/telegram-cb.php';
+6
View File
@@ -0,0 +1,6 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
$_GET['key'] = 'msr-2026-reset';
include '/var/www/html/telegram-cb.php';
+10
View File
@@ -0,0 +1,10 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', '1');
ini_set('log_errors', '1');
ini_set('error_log', '/tmp/cb-err.log');
@unlink('/tmp/cb-err.log');
$_GET['key'] = 'msr-2026-reset';
include '/var/www/html/telegram-cb.php';
echo "\n===LOG===\n";
echo @file_get_contents('/tmp/cb-err.log') ?: '(no log)';
+86
View File
@@ -0,0 +1,86 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(30)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
print('=== DNS settings ===')
for d in api.cmd('/ip/dns/print'):
for k in ['servers', 'dynamic-servers', 'static', 'use-doh-server', 'doh-timeout']:
v = d.get(k,'')
if v: print(' %s: %s' % (k, v))
print()
print('=== DNS static entries for msr / dimanaaja ===')
for s in api.cmd('/ip/dns/static/print'):
n = s.get('name','')
if 'dimanaaja' in n or 'msr' in n or 'mikhmon' in n:
print(' %s -> %s' % (n, s.get('address','')))
print()
print('=== Try DNS resolve with 8.8.8.8 as server ===')
r = api.cmd('/ip/dns/query', **{'server': '8.8.8.8', 'name': 'msr.s.dimanaaja.biz.id', 'type': 'A'})
print(' %s' % str(r)[:300])
api.close()
+90
View File
@@ -0,0 +1,90 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(20)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
print('=== Router IP Addresses ===')
for a in api.cmd('/ip/address/print'):
print(' %-20s %-12s net=%s' % (a.get('address',''), a.get('interface',''), a.get('network','')))
print()
print('=== Routes to 100.100.31.46 / tailscale ===')
for r in api.cmd('/ip/route/print'):
dst = r.get('dst-address','')
gw = r.get('gateway','')
if dst and ('100.100' in dst or '0.0.0.0' == dst.split('/')[0]):
print(' dst=%-20s gateway=%-15s dist=%s' % (dst, gw, r.get('distance','')))
print()
print('=== Route check: to 100.100.31.46 ===')
r = api.cmd('/ip/route/check', address='100.100.31.46')
print(' %s' % str(r)[:300])
print()
print('=== Route check: to 192.168.100.6 ===')
r2 = api.cmd('/ip/route/check', address='192.168.100.6')
print(' %s' % str(r2)[:300])
api.close()
+81
View File
@@ -0,0 +1,81 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(25)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
print('=== ALL ROUTES ===')
for r in api.cmd('/ip/route/print'):
dst = r.get('dst-address','')
gw = r.get('gateway','')
iface = r.get('gateway-status','')
print(' dst=%-18s gw=%-18s dist=%s type=%s' % (dst, gw, r.get('distance',''), r.get('routing-table','')))
print()
print('=== ARP / neighbours on ether1 (192.168.100.x) ===')
for n in api.cmd('/ip/arp/print'):
if '100.' in n.get('address',''):
print(' %-18s iface=%-10s %s' % (n.get('address',''), n.get('interface',''), n.get('mac-address','')))
api.close()
+111
View File
@@ -0,0 +1,111 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(15)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
# Check ALL profiles for notification capability
print('=== All Profiles with notify ===')
for p in api.cmd('/ip/hotspot/user/profile/print'):
name = p.get('name','')
ol = p.get('on-login','')
has_notify = 'notify' in ol or 'telegram' in ol or 'bot' in ol or 'fetch' in ol
has_expmode = 'rem' in ol or 'ntf' in ol
print(' %s | has_notify=%s | has_expmode=%s' % (name, has_notify, has_expmode))
# Check all schedulers for notify calls
print()
print('=== Schedulers that call notify ===')
for si in api.cmd('/system/scheduler/print'):
oe = si.get('on-event','')
n = si.get('name','')
if 'notify-expire' in oe or 'telegram' in oe or 'fetch' in oe:
print(' %s: calls notify-expire' % n)
elif 'set limit-uptime' in oe:
print(' %s: silent (limit-uptime only)' % n)
elif 'remove' in oe:
print(' %s: remove only' % n)
else:
print(' %s: other' % n)
# Count how many times per day each notify-scheduler would fire
print()
print('=== Notification frequency ===')
for si in api.cmd('/system/scheduler/print'):
n = si.get('name','')
intv = si.get('interval','')
oe = si.get('on-event','')
if 'notify-expire' in oe:
# Parse interval
parts = intv.split(':')
if len(parts) == 3:
hrs = int(parts[0])
mins = int(parts[1])
secs = int(parts[2])
total_secs = hrs*3600 + mins*60 + secs
if total_secs > 0:
per_day = 86400 // total_secs
print(' %s: every %s = ~%dx/day per EXPIRED user (NO dedup!)' % (n, intv, per_day))
api.close()
+113
View File
@@ -0,0 +1,113 @@
import socket, hashlib
class R:
def __init__(self,h,p=8728): self.h=h; self.p=p; self.s=None
def c(self,u,p):
self.s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); self.s.settimeout(10)
self.s.connect((self.h,self.p))
self._ws('/login','=name='+u,'=password='+p)
r=self._rr()
if any(isinstance(x,tuple) and x[0]=='!trap' for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
h=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
self._ws('/login','=name='+u,'=response=00'+h)
self._rr()
return self
def _el(self,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
elif l<0x200000: l|=0xC00000; return bytes([(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
elif l<0x10000000: l|=0xE0000000; return bytes([(l>>24)&0xFF,(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
else: return bytes([0xF0,(l>>24)&0xFF,(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(self,w): self.s.sendall(self._el(len(w))+w.encode('utf-8'))
def _ws(self,c,*ws): self._ww(c); [self._ww(w) for w in ws]; self.s.sendall(b'\x00')
def _rb(self): return self.s.recv(1)
def _rw(self):
f=self._rb()
if f==b'\x00': return None
lb=f[0]
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+self._rb()[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(self._rb()[0]<<8)+self._rb()[0]
elif (lb&0xF0)==0xE0: l=((lb&0x0F)<<24)+(self._rb()[0]<<16)+(self._rb()[0]<<8)+self._rb()[0]
else: l=(self._rb()[0]<<24)+(self._rb()[0]<<16)+(self._rb()[0]<<8)+self._rb()[0]
else: l=lb
d=b''
while len(d)<l:
c=self.s.recv(l-len(d))
if not c: break
d+=c
return d.decode('utf-8',errors='replace')
def _rss(self):
ws=[]
while True:
w=self._rw()
if w is None: break
ws.append(w)
return ws
def _rr(self):
rs=[]
while True:
s=self._rss()
if not s: break
r=s[0]; t=s[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(self,c,**a):
ws=[c]
for k,v in a.items():
if k.startswith('?'): ws.append(f'{k}={v}')
else: ws.append(f'={k}={v}')
self._ws(*ws)
return self._rr()
def close(self):
if self.s: self.s.close(); self.s=None
api=R('192.168.100.2').c('mi4','m14')
print("=== DHCP Lease 192.168.53.177 ===")
for l in api.cmd('/ip/dhcp-server/lease/print', **{'?address': '192.168.53.177'}):
print(f" MAC: {l.get('mac-address')}")
print(f" Hostname: {l.get('host-name')}")
print(f" Status: {l.get('status')}")
print(f" Server: {l.get('server')}")
print(f" Comment: {l.get('comment')}")
lid = l.get('.id')
# Remove it
print(f"\n Removing lease {lid}...")
api.cmd('/ip/dhcp-server/lease/remove', **{'.id': lid})
print(" Done.")
print("\n=== IP Binding untuk MAC CA:1A:02:C2:8C:1E ===")
for b in api.cmd('/ip/hotspot/ip-binding/print'):
mac = b.get('mac-address', '')
if 'CA:1A:02:C2:8C:1E' in mac or 'CA:1A' in mac[:7]:
print(f" Server: {b.get('server')} MAC: {mac} IP: {b.get('address')} Type: {b.get('type')}")
print("\n=== IP Binding ALL ===")
for b in api.cmd('/ip/hotspot/ip-binding/print'):
print(f" MAC: {b.get('mac-address')} IP: {b.get('address')} Type: {b.get('type')} Server: {b.get('server')}")
print("\n=== MAC CA:1A:02:C2:8C:1E di Hotspot User ===")
for u in api.cmd('/ip/hotspot/user/print'):
mac = u.get('mac-address', '')
comment = u.get('comment', '')
name = u.get('name', '')
limit_uptime = u.get('limit-uptime', '')
profile = u.get('profile', '')
if mac and 'CA:1A' in mac:
print(f" User: {name} Profile: {profile} MAC: {mac} Comment: {comment} limit-uptime: {limit_uptime}")
api.close()
+12
View File
@@ -0,0 +1,12 @@
import json, requests
r = requests.get('https://api.telegram.org/bot5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk/getUpdates?timeout=5')
d = r.json()
print('ok:', d.get('ok'))
print('count:', len(d.get('result', [])))
for u in d.get('result', []):
types = [k for k in u if k != 'update_id']
print(f' update {u["update_id"]}: {types}')
if 'callback_query' in u:
cb = u['callback_query']
print(f' data: {cb.get("data","")}')
print(f' from: {cb.get("from",{}).get("first_name","")}')
+12
View File
@@ -0,0 +1,12 @@
import requests, time
r = requests.get('https://api.telegram.org/bot5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk/getUpdates', params={'timeout': 10, 'allowed_updates': ['callback_query']}, timeout=15)
d = r.json()
print('ok:', d.get('ok'))
print('count:', len(d.get('result', [])))
for u in d.get('result', []):
types = [k for k in u if k != 'update_id']
print(' update %s: %s' % (u['update_id'], types))
if 'callback_query' in u:
cb = u['callback_query']
print(' data: %s' % cb.get('data',''))
print(' from: %s' % cb.get('from',{}).get('first_name',''))
+10
View File
@@ -0,0 +1,10 @@
import socket, hashlib, subprocess
s=socket.socket(); s.settimeout(5)
try:
s.connect(('192.168.100.2',8728))
print('API OK')
except Exception as e:
print('API FAIL: '+str(e))
s.close()
r=subprocess.run(['ping','-c','1','192.168.100.2'],capture_output=True,timeout=5)
print('PING: '+('OK' if r.returncode==0 else 'FAIL'))
+29
View File
@@ -0,0 +1,29 @@
import paramiko
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('192.168.100.2', username='mi4', password='m14', look_for_keys=False, allow_agent=False)
schedulers = ["Bulanan2", "Bulanan4", "vc-2K3J", "vc-5K10J", "Family", "DevB2"]
for s in schedulers:
stdin, stdout, stderr = client.exec_command(f'/system scheduler get {s} disabled')
d = stdout.read().decode().strip()
e = stderr.read().decode().strip()
print(f"{s}: disabled={d}")
if e and "no such item" in e.lower():
print(f" WARN: {e}")
stdin, stdout, stderr = client.exec_command('/log print where topics~"scheduler"')
logs = stdout.read().decode().strip()
print(f"\n--- Scheduler log entries (last 20) ---")
for line in logs.split('\n')[-20:]:
print(line)
stdin, stdout, stderr = client.exec_command('/log print where message~"error" or message~"failure"')
logs = stdout.read().decode().strip()
print(f"\n--- Error log entries (last 20) ---")
for line in logs.split('\n')[-20:]:
print(line)
client.close()
+20
View File
@@ -0,0 +1,20 @@
import paramiko
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('192.168.100.2', username='mi4', password='m14', look_for_keys=False, allow_agent=False)
# List all schedulers with their IDs and disabled status
stdin, stdout, stderr = client.exec_command('/system scheduler print terse')
lines = stdout.read().decode().strip().split('\n')
for line in lines:
if not line.strip():
continue
print(line.strip())
print("\n--- Check disabled property differently ---")
# Try using print with flags
stdin, stdout, stderr = client.exec_command('/system scheduler print brief')
print(stdout.read().decode())
client.close()
+95
View File
@@ -0,0 +1,95 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(15)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
# Check system clock
clk = api.cmd('/system/clock/print')
if clk:
print('Clock: %s' % clk[0])
# Check scheduler last run
logs = api.cmd('/log/print', **{'?topics': 'system', '?message': 'Bulanan4'})
print()
print('=== Recent logs for Bulanan4 ===')
for l in logs[:10]:
print(l.get('time','') + ' ' + l.get('topics','') + ' ' + l.get('message',''))
# Also check for errors
logs2 = api.cmd('/log/print', **{'?topics': 'error'})
print()
print('=== Errors ===')
for l in logs2[:5]:
print(l.get('time','') + ' ' + l.get('topics','') + ' ' + l.get('message',''))
# Check system resource
res = api.cmd('/system/resource/print')
if res:
r = res[0]
print()
print('Uptime: %s' % r.get('uptime',''))
print('CPU: %s%%' % r.get('cpu-load',''))
api.close()
+44
View File
@@ -0,0 +1,44 @@
import base64, json
# Try to decode pWJm
enc = "pWJm"
# Base64
try:
dec = base64.b64decode(enc)
print(f"base64: '{enc}' -> bytes {list(dec)}")
except:
print("not base64")
# Check Mikhmon encryption in config
import os
config_path = r"C:\Users\Admin\dev\Mikhmon Server\mikhmon\include\config.php"
with open(config_path, 'r') as f:
content = f.read()
# Find Msr line
for line in content.split('\n'):
if "'Msr'" in line or "'rm-jogja'" in line:
print(f"\nConfig line: {line.strip()[:200]}")
# Check if there's a Mikhmon config.json or similar
for root, dirs, files in os.walk(r"C:\Users\Admin\dev\Mikhmon Server"):
for f in files:
if f.endswith('.json') or f.endswith('.ini') or f.endswith('.txt'):
path = os.path.join(root, f)
size = os.path.getsize(path)
if size < 100000:
print(f"\n=== {path} ({size} bytes) ===")
try:
with open(path, 'r') as fh:
for line in fh.read().split('\n')[:5]:
print(f" {line.strip()[:150]}")
except:
print(" (binary)")
# Check for any .env or key file
print("\n=== Searching for MSR key files ===")
for root, dirs, files in os.walk(r"C:\Users\Admin\dev\Mikhmon Server"):
for f in files:
if 'bit' in f.lower() or 'father' in f.lower() or 'msrkey' in f.lower() or 'license' in f.lower() or 'key' in f.lower():
print(f" {os.path.join(root, f)}")
+100
View File
@@ -0,0 +1,100 @@
import socket, hashlib
class R:
def __init__(self,h,p=8728): self.h=h; self.p=p; self.s=None
def c(self,u,p):
self.s=socket.socket(); self.s.settimeout(10)
self.s.connect((self.h,self.p))
self._ws('/login','=name='+u,'=password='+p)
r=self._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
h=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
self._ws('/login','=name='+u,'=response=00'+h); self._rr()
return self
def _el(self,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(self,w): self.s.sendall(self._el(len(w))+w.encode())
def _ws(self,c,*ws): self._ww(c); [self._ww(w) for w in ws]; self.s.sendall(b'\x00')
def _rw(self):
f=self.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+self.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(self.s.recv(1)[0]<<8)+self.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(self.s.recv(1)[0]<<16)+(self.s.recv(1)[0]<<8)+self.s.recv(1)[0]
d=b''
while len(d)<l: d+=self.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(self):
ws=[]
while True:
w=self._rw()
if w is None: break
ws.append(w)
return ws
def _rr(self):
rs=[]
while True:
s=self._rss()
if not s: break
r=s[0]; t=s[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(self,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
self._ws(*ws)
return self._rr()
def close(self):
if self.s: self.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
# Check Nadia
users = api.cmd('/ip/hotspot/user/print', **{'?name': 'nadia'})
if users:
u = users[0]
print('NADIA:')
for k,v in u.items():
print(' {}={}'.format(k,v))
else:
print('NADIA: not found')
# Check Bulanan4 scheduler
for si in api.cmd('/system/scheduler/print'):
if si['name'] == 'Bulanan4':
oe = si.get('on-event','')
print()
print('Bulanan4 scheduler:')
print(' disabled={}'.format(si.get('disabled','')))
print(' interval={}'.format(si.get('interval','')))
print(' pic count={}'.format(oe.count(':pic')))
print(' pick count={}'.format(oe.count(':pick')))
break
# Check Bulanan4 users
users4 = api.cmd('/ip/hotspot/user/print', **{'?profile': 'Bulanan4'})
print()
print('Bulanan4 users: {}'.format(len(users4)))
for u in users4:
n = u.get('name','')
c = u.get('comment','')
lim = u.get('limit-uptime','')
print(' {} | comment={} | limit-uptime={}'.format(n,c,lim))
api.close()
+92
View File
@@ -0,0 +1,92 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(15)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
# Check tunnelbroker
print('=== tunnelbroker on-event ===')
for si in api.cmd('/system/scheduler/print'):
if si['name'] == 'tunnelbroker':
print(si.get('on-event','')[:300])
break
# Check DevB2 - does it notify?
print()
print('=== DevB2 on-event (first 300 chars) ===')
for si in api.cmd('/system/scheduler/print'):
if si['name'] == 'DevB2':
oe = si.get('on-event','')
print(oe[:300])
print('...')
print('notify-expire in oe: %s' % ('notify-expire' in oe))
break
# All user profiles summary
print()
print('=== All profiles summary ===')
for p in api.cmd('/ip/hotspot/user/profile/print'):
print(' %s: on-login has notify=%s' % (p['name'], 'expand' in p.get('on-login','') or 'set limit-uptime' in p.get('on-login','')))
api.close()
+113
View File
@@ -0,0 +1,113 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(15)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
# Check Family scheduler (the one with notifications)
print('=== Family scheduler ===')
for si in api.cmd('/system/scheduler/print'):
if si['name'] == 'Family':
oe = si.get('on-event','')
print(' disabled=%s' % si.get('disabled',''))
print(' interval=%s' % si.get('interval',''))
print(' contains :pic(typo)=%s' % (':pic ' in oe))
print(' has notify call=%s' % ('notify-expire' in oe))
print(' has telegram=%s' % ('5115036608' in oe))
print()
print(' FULL on-event:')
print(oe)
print()
break
else:
print(' Family scheduler NOT FOUND')
# Check Family users with notifications
print('=== Family profile users ===')
for u in api.cmd('/ip/hotspot/user/print', **{'?profile': 'Family'}):
n = u.get('name','')
c = u.get('comment','')
lim = u.get('limit-uptime','')
print(' %s | comment=%s | limit-uptime=%s' % (n, c, lim))
# Check all schedulers
print()
print('=== ALL Schedulers ===')
for si in api.cmd('/system/scheduler/print'):
n = si.get('name','')
dis = si.get('disabled','')
intr = si.get('interval','')
print(' %s interval=%s disabled=%s' % (n, intr, dis))
# Check notify-expire.php in container
import subprocess
ssh = subprocess.run(
['ssh', '-o', 'StrictHostKeyChecking=no', 'rl',
'docker exec mikhmon cat /var/www/html/notify-expire.php | head -3'],
capture_output=True, text=True, timeout=10)
print()
print('=== Container notify-expire.php header ===')
print(ssh.stdout[:200] if ssh.stdout else 'FAIL: ' + ssh.stderr[:200])
api.close()
+94
View File
@@ -0,0 +1,94 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(15)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
# Check NTP
print('=== NTP Client ===')
ntp = api.cmd('/system/ntp/client/print')
if ntp:
for k,v in ntp[0].items():
print(' %s=%s' % (k,v))
else:
print(' (no ntp config)')
# Check NTP servers
print()
print('=== NTP Servers ===')
srvs = api.cmd('/system/ntp/client/servers/print')
for s in srvs:
print(' %s' % s)
# Try setting clock
print()
print('=== Setting clock to jul/24/2026 ===')
api.cmd('/system/clock/set', **{'date': 'jul/24/2026', 'time': '12:00:00'})
# Verify
clk = api.cmd('/system/clock/print')
if clk:
print('Clock now: date=%s time=%s' % (clk[0].get('date',''), clk[0].get('time','')))
api.close()
+76
View File
@@ -0,0 +1,76 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(60)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
r = api.cmd('/ip/hotspot/user/print', **{'?name': 'TMR-1'})
for x in r:
if isinstance(x,dict):
print('USER TMR-1:', 'limit-uptime=', x.get('limit-uptime'), 'comment=', x.get('comment'), 'uptime=', x.get('uptime'))
sch = api.cmd('/system/scheduler/print')
tmr_sch = [s.get('name') for s in sch if isinstance(s,dict) and s.get('name')=='TMR-1']
print('scheduler TMR-1 exists:', bool(tmr_sch))
api.close()
+116
View File
@@ -0,0 +1,116 @@
import socket, hashlib, 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
api = RouterOSApi('192.168.100.2')
api.connect('mi4', 'm14')
# Check current time and scheduler status
print("=== System Time ===")
resp = api.cmd('/system/clock/print')
print(f" {resp[0].get('date')} {resp[0].get('time')}")
print("\n=== Scheduler Run Counts ===")
for item in api.cmd('/system/scheduler/print'):
name = item['name']
rc = item.get('run-count', '?')
disabled = item.get('disabled', 'false')
interval = item.get('interval', '?')
next_run = item.get('next-run', '?')
if name in ["Log", "tunnelbroker", "backup-relay"]:
continue
print(f" {name}: run={rc} disabled={disabled} interval={interval} next={next_run}")
# Wait 30s and check again
print("\nWaiting 30 seconds...")
time.sleep(30)
print("=== After 30s ===")
for item in api.cmd('/system/scheduler/print'):
name = item['name']
rc = item.get('run-count', '?')
if name in ["Log", "tunnelbroker", "backup-relay"]:
continue
print(f" {name}: run={rc}")
api.close()
+94
View File
@@ -0,0 +1,94 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(15)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
for si in api.cmd('/system/scheduler/print'):
if si['name'] == 'Bulanan4':
oe = si.get('on-event','')
print('=== Bulanan4 on-event FULL ===')
print(oe)
print('=== END ===')
print()
print('disabled=%s' % si.get('disabled',''))
print('interval=%s' % si.get('interval',''))
print('contains :pic=%s' % (':pic ' in oe))
print('contains :pick=%s' % (':pick ' in oe))
break
# Check all Bulanan4 users with details
print()
print('=== Bulanan4 user details ===')
nowd = 'jul/24/2026'
nowt = 8*60 # 08:00 in minutes for comparison
print('Current ref: date=%s time=%d min' % (nowd, nowt))
for u in api.cmd('/ip/hotspot/user/print', **{'?profile': 'Bulanan4'}):
n = u.get('name','')
c = u.get('comment','')
lim = u.get('limit-uptime','')
dis = u.get('disabled','')
print('%s | comment=%s | limit-uptime=%s | disabled=%s' % (n, c, lim, dis))
api.close()
+111
View File
@@ -0,0 +1,111 @@
import socket, hashlib, time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(10)
s.connect(('192.168.100.2', 8728))
def el(l):
if l < 0x80: return bytes([l])
elif l < 0x4000: l |= 0x8000; return bytes([(l>>8)&0xFF, l&0xFF])
elif l < 0x200000: l |= 0xC00000; return bytes([(l>>16)&0xFF, (l>>8)&0xFF, l&0xFF])
elif l < 0x10000000: l |= 0xE0000000; return bytes([(l>>24)&0xFF, (l>>16)&0xFF, (l>>8)&0xFF, l&0xFF])
else: return bytes([0xF0, (l>>24)&0xFF, (l>>16)&0xFF, (l>>8)&0xFF, l&0xFF])
def ww(w): s.sendall(el(len(w)) + (w.encode() if isinstance(w, str) else w))
def ws(c, *a):
ww(c)
for x in a: ww(x)
s.sendall(b'\x00')
def rb(): return s.recv(1)
def rw():
f = rb()
if f == b'\x00': return None
lb = f[0]
if lb & 0x80:
if (lb & 0xC0) == 0x80: l = ((lb & 0x3F) << 8) + rb()[0]
elif (lb & 0xE0) == 0xC0: l = ((lb & 0x1F) << 16) + (rb()[0] << 8) + rb()[0]
elif (lb & 0xF0) == 0xE0: l = ((lb & 0x0F) << 24) + (rb()[0] << 16) + (rb()[0] << 8) + rb()[0]
else: l = (rb()[0] << 24) + (rb()[0] << 16) + (rb()[0] << 8) + rb()[0]
else: l = lb
d = b''
while len(d) < l:
c = s.recv(l - len(d))
if not c: break
d += c
return d.decode('utf-8', errors='replace')
def rss():
ws = []
while True:
w = rw()
if w is None: break
ws.append(w)
return ws
def rr():
rs = []
while True:
sent = rss()
if not sent: break
r = sent[0]; t = sent[1:]
if r == '!done': break
elif r == '!re':
d = {}
for w in t:
if '=' in w:
p = w.split('=', 2)
d[p[1]] = p[2] if len(p) >= 3 else ''
else: d[w] = ''
rs.append(d)
elif r == '!trap':
rs.append(('!trap', t))
return rs
def cmd(c, **a):
w = [c]
for k, v in a.items():
if k.startswith('?'): w.append(f'{k}={v}')
else: w.append(f'={k}={v}')
ws(*w)
return rr()
# Login
ws('/login', '=name=mi4', '=password=m14')
r = rr()
if any(isinstance(x, tuple) and x[0] == '!trap' for x in r):
for x in r:
if isinstance(x, dict) and 'ret' in x: ch = x['ret']
if ch and len(ch) == 32:
resp = '00' + hashlib.md5(b'\x00' + b'm14' + bytes.fromhex(ch)).hexdigest()
ws('/login', '=name=mi4', f'=response={resp}')
rr()
# Dump on-event Bulanan2
print("=== Bulanan2 on-event ===")
for s_item in cmd('/system/scheduler/print'):
if s_item['name'] == 'Bulanan2':
oe = s_item.get('on-event', '')
lines = oe.split('\n')
print(f"Total lines: {len(lines)}")
for i, l in enumerate(lines):
print(f"{i+1:3d}: {l}")
# Count :pic vs :pick
print(f"\n:pik occurrences: {oe.count(':pic,') + oe.count(':pic')} (should be 0 if fully :pick)")
print(f":pick occurrences: {oe.count(':pick')}")
break
# Dump on-login Bulanan2
print("\n=== Bulanan2 on-login ===")
for p_item in cmd('/ip/hotspot/profile/print'):
if p_item['name'] == 'Bulanan2':
ol = p_item.get('on-login', '')
lines = ol.split('\n')
print(f"Total lines: {len(lines)}")
for i, l in enumerate(lines):
print(f"{i+1:3d}: {l}")
break
s.close()
+89
View File
@@ -0,0 +1,89 @@
import socket, hashlib
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); s.settimeout(10); s.connect(('192.168.100.2',8728))
def el(l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
elif l<0x200000: l|=0xC00000; return bytes([(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
elif l<0x10000000: l|=0xE0000000; return bytes([(l>>24)&0xFF,(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
else: return bytes([0xF0,(l>>24)&0xFF,(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def ww(w): s.sendall(el(len(w))+(w.encode() if isinstance(w,str) else w))
def ws(c,*a): ww(c); [ww(x) for x in a]; s.sendall(b'\x00')
def rb(): return s.recv(1)
def rw():
f=rb()
if f==b'\x00': return None
lb=f[0]
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+rb()[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(rb()[0]<<8)+rb()[0]
elif (lb&0xF0)==0xE0: l=((lb&0x0F)<<24)+(rb()[0]<<16)+(rb()[0]<<8)+rb()[0]
else: l=(rb()[0]<<24)+(rb()[0]<<16)+(rb()[0]<<8)+rb()[0]
else: l=lb
d=b''
while len(d)<l: d+=s.recv(l-len(d))
return d
def rss():
ws=[]
while True:
w=rw()
if w is None: break
ws.append(w)
return ws
def rr():
rs=[]
while True:
sent=rss()
if not sent: break
r=sent[0]; t=sent[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(c,**a):
w=[c]
for k,v in a.items():
if k.startswith('?'): w.append(k+'='+str(v))
else: w.append('='+k+'='+str(v))
ws(*w)
return rr()
ws('/login','=name=mi4','=password=m14')
r=rr()
if any(True for x in r if isinstance(x,tuple) and x[0]=='!trap'):
ch=None
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
resp='00'+hashlib.md5(b'\x00'+b'm14'+bytes.fromhex(ch)).hexdigest()
ws('/login','=name=mi4','=response='+resp)
rr()
print("=== Scheduler on-event Telegram check ===")
for si in cmd('/system/scheduler/print'):
name=si['name']
if name in ['Log','tunnelbroker','backup-relay']: continue
oe=si.get('on-event','')
has_tg='5115036608' in oe or 'telegram' in oe.lower() or 'sendMessage' in oe
print(f" {name}: Telegram={'YES' if has_tg else 'no'}")
if has_tg:
lines=oe.split('\n')
for i,l in enumerate(lines):
if '5115036608' in l or 'telegram' in l.lower() or 'sendMessage' in l:
print(f" TG line: {l.strip()[:120]}")
print("\n=== Script Telegram check ===")
for sc in cmd('/system/script/print'):
name=sc['name']
src=sc.get('source','')
has_tg='5115036608' in src or 'telegram' in src.lower() or 'sendMessage' in src
print(f" {name}: Telegram={'YES' if has_tg else 'no'}")
s.close()
+108
View File
@@ -0,0 +1,108 @@
import socket, hashlib
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:
h = hashlib.md5(b'\x00' + password.encode() + bytes.fromhex(chal)).hexdigest()
self._write_sentence('/login', f'=name={username}', f'=response=00{h}')
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}")
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
api = RouterOSApi('192.168.100.2')
api.connect('mi4', 'm14')
print("=== User test123 ===")
r = api.cmd('/ip/hotspot/user/print', **{'?name': 'test123'})
if r:
u = r[0]
print(f" Comment: {u.get('comment')}")
print(f" limit-uptime: {u.get('limit-uptime')}")
print(f" Profile: {u.get('profile')}")
print(f" Uptime: {u.get('uptime')}")
else:
print(" NOT FOUND")
print("\n=== Scheduler Run Counts ===")
for item in api.cmd('/system/scheduler/print'):
name = item['name']
if name in ['Log', 'tunnelbroker', 'backup-relay']: continue
print(f" {name}: run={item.get('run-count')} next={item.get('next-run')}")
print("\n=== Total Hotspot Users ===")
all_users = list(api.cmd('/ip/hotspot/user/print'))
print(f" {len(all_users)} users")
api.close()
+17
View File
@@ -0,0 +1,17 @@
import requests, json
r = requests.get('https://api.telegram.org/bot5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk/getUpdates?offset=0')
d = r.json()
msgs = [m for m in d.get('result', []) if 'message' in m and m['message'].get('text') == 'Test topic id']
for m in msgs:
msg = m['message']
tid = msg.get('message_thread_id', 'none')
cid = msg['chat']['id']
from_id = msg['from']['id']
print(f'chat={cid} thread_id={tid} from={from_id} text={msg.get("text","")}')
if not msgs:
print('no matching messages found')
for m in d.get('result', []):
if 'message' in m:
mm = m['message']
print(f" text='{mm.get('text','')[:60]}' thread_id={mm.get('message_thread_id','-')}")
+10
View File
@@ -0,0 +1,10 @@
import requests, json
r = requests.get('https://api.telegram.org/bot5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk/getUpdates?offset=-10')
d = r.json()
msgs = [m for m in d.get('result',[]) if 'message' in m]
print(f"Total messages: {len(msgs)}")
for m in msgs[-5:]:
cid = m['message']['chat']['id']
txt = m['message'].get('text','')[:150]
print(f" chat={cid} text={txt}")
+21
View File
@@ -0,0 +1,21 @@
import requests, json
# Get updates without offset
r = requests.get('https://api.telegram.org/bot5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk/getUpdates')
d = r.json()
print(f"ok: {d.get('ok')}, count: {len(d.get('result',[]))}")
msgs = [m for m in d['result'] if 'message' in m]
for m in msgs[-10:]:
mid = m['message']['message_id']
cid = m['message']['chat']['id']
txt = m['message'].get('text', m['message'].get('caption', ''))[:200]
print(f" mid={mid} chat={cid} txt={txt}")
# Also check the test we sent
print("\n--- Check laporkan group test ---")
r2 = requests.get('https://api.telegram.org/bot5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk/getUpdates?offset=0&limit=100')
d2 = r2.json()
msgs2 = [m for m in d2['result'] if 'message' in m and m['message']['chat']['id'] == -1002554941429]
print(f"Messages in laporkan: {len(msgs2)}")
for m in msgs2[-5:]:
print(f" mid={m['message']['message_id']} text={m['message'].get('text','')[:200]}")
+80
View File
@@ -0,0 +1,80 @@
import socket, hashlib
class R:
def __init__(self,h,p=8728): self.h=h; self.p=p; self.s=None
def c(self,u,p):
self.s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); self.s.settimeout(15)
self.s.connect((self.h,self.p))
self._ws('/login','=name='+u,'=password='+p)
r=self._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
h=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
self._ws('/login','=name='+u,'=response=00'+h)
self._rr()
return self
def _el(self,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(self,w): self.s.sendall(self._el(len(w))+w.encode('utf-8'))
def _ws(self,c,*ws): self._ww(c); [self._ww(w) for w in ws]; self.s.sendall(b'\x00')
def _rw(self):
f=self._rb()
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+self._rb()[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(self._rb()[0]<<8)+self._rb()[0]
else: l=((lb&0x0F)<<24)+(self._rb()[0]<<16)+(self._rb()[0]<<8)+self._rb()[0]
d=b''
while len(d)<l: d+=self._rb()
return d.decode('utf-8',errors='replace')
def _rb(self): return self.s.recv(1)
def _rss(self):
ws=[]
while True:
w=self._rw()
if w is None: break
ws.append(w)
return ws
def _rr(self):
rs=[]
while True:
s=self._rss()
if not s: break
r=s[0]; t=s[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(self,c,**a):
ws=[c]
for k,v in a.items(): ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
self._ws(*ws)
return self._rr()
def close(self):
if self.s: self.s.close(); self.s=None
api=R('192.168.100.2').c('mi4','m14')
print("=== All hotspot users ===")
for u in api.cmd('/ip/hotspot/user/print'):
name = u.get('name','')
if 'tg_test' in name or 'test' in name:
print(" %s | %s | profile=%s | uptime=%s | comment=%s" % (u.get('.id',''), name, u.get('profile',''), u.get('limit-uptime',''), u.get('comment','')[:40] if u.get('comment') else '-'))
print("---")
for u in api.cmd('/ip/hotspot/user/print'):
name = u.get('name','')
prof = u.get('profile','')
if prof == 'Family':
print(" %s | %s | uptime=%s | limit-bytes=%s | comment=%s" % (u.get('.id',''), name, u.get('limit-uptime',''), u.get('limit-bytes-total',''), u.get('comment','')[:30] if u.get('comment') else '-'))
api.close()
+110
View File
@@ -0,0 +1,110 @@
import socket, hashlib
class R:
def __init__(self,h,p=8728): self.h=h; self.p=p; self.s=None
def c(self,u,p):
self.s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); self.s.settimeout(10)
self.s.connect((self.h,self.p))
self._ws('/login','=name='+u,'=password='+p)
r=self._rr()
if any(isinstance(x,tuple) and x[0]=='!trap' for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
h=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
self._ws('/login','=name='+u,'=response=00'+h)
self._rr()
return self
def _el(self,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
elif l<0x200000: l|=0xC00000; return bytes([(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
elif l<0x10000000: l|=0xE0000000; return bytes([(l>>24)&0xFF,(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
else: return bytes([0xF0,(l>>24)&0xFF,(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(self,w): self.s.sendall(self._el(len(w))+w.encode('utf-8'))
def _ws(self,c,*ws): self._ww(c); [self._ww(w) for w in ws]; self.s.sendall(b'\x00')
def _rb(self): return self.s.recv(1)
def _rw(self):
f=self._rb()
if f==b'\x00': return None
lb=f[0]
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+self._rb()[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(self._rb()[0]<<8)+self._rb()[0]
elif (lb&0xF0)==0xE0: l=((lb&0x0F)<<24)+(self._rb()[0]<<16)+(self._rb()[0]<<8)+self._rb()[0]
else: l=(self._rb()[0]<<24)+(self._rb()[0]<<16)+(self._rb()[0]<<8)+self._rb()[0]
else: l=lb
d=b''
while len(d)<l:
c=self.s.recv(l-len(d))
if not c: break
d+=c
return d.decode('utf-8',errors='replace')
def _rss(self):
ws=[]
while True:
w=self._rw()
if w is None: break
ws.append(w)
return ws
def _rr(self):
rs=[]
while True:
s=self._rss()
if not s: break
r=s[0]; t=s[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(self,c,**a):
ws=[c]
for k,v in a.items():
if k.startswith('?'): ws.append(f'{k}={v}')
else: ws.append(f'={k}={v}')
self._ws(*ws)
return self._rr()
def close(self):
if self.s: self.s.close(); self.s=None
api=R('192.168.100.2').c('mi4','m14')
print("Router time:")
r=api.cmd('/system/clock/print')[0]
print(f" {r['date']} {r['time']}")
# Create user for A55-Labibah - already expired (comment yesterday)
# MAC-bound so only A55 can login
api.cmd('/ip/hotspot/user/add', **{
'name': 'labibah_tes',
'password': '123',
'profile': 'Bulanan2',
'mac-address': 'CA:1A:02:C2:8C:1E',
'comment': 'jul/06/2026 00:00:00'
})
print("\nCreated user: labibah_tes / 123")
print(" Profile: Bulanan2 (di-expire otomatis)")
print(" MAC: CA:1A:02:C2:8C:1E (hanya A55-Labibah)")
print(" Comment: jul/06/2026 (SUDAH EXPIRED)")
# Verify
r = list(api.cmd('/ip/hotspot/user/print', **{'?name': 'labibah_tes'}))
if r:
u = r[0]
print(f"\nVerify: name={u['name']} profile={u.get('profile')} mac={u.get('mac-address')} comment={u.get('comment')} limit-uptime={u.get('limit-uptime')}")
print("Status: BELUM login (limit-uptime kosong)")
print("\nPrediksi: saat A55 login lewat hotspot,")
print(" - on-login script akan deteksi comment jul/06 (expired)")
print(" - limit-uptime langsung diset = 1s")
print(" - device kena kick/limit otomatis ✅")
else:
print("GAGAL create user!")
api.close()
+78
View File
@@ -0,0 +1,78 @@
import socket, hashlib
class R:
def __init__(self,h,p=8728): self.h=h; self.p=p; self.s=None
def c(self,u,p):
self.s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); self.s.settimeout(15)
self.s.connect((self.h,self.p))
self._ws('/login','=name='+u,'=password='+p)
r=self._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
h=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
self._ws('/login','=name='+u,'=response=00'+h)
self._rr()
return self
def _el(self,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(self,w): self.s.sendall(self._el(len(w))+w.encode('utf-8'))
def _ws(self,c,*ws): self._ww(c); [self._ww(w) for w in ws]; self.s.sendall(b'\x00')
def _rw(self):
f=self._rb()
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+self._rb()[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(self._rb()[0]<<8)+self._rb()[0]
else: l=((lb&0x0F)<<24)+(self._rb()[0]<<16)+(self._rb()[0]<<8)+self._rb()[0]
d=b''
while len(d)<l: d+=self._rb()
return d.decode('utf-8',errors='replace')
def _rb(self): return self.s.recv(1)
def _rss(self):
ws=[]
while True:
w=self._rw()
if w is None: break
ws.append(w)
return ws
def _rr(self):
rs=[]
while True:
s=self._rss()
if not s: break
r=s[0]; t=s[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(self,c,**a):
ws=[c]
for k,v in a.items(): ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
self._ws(*ws)
return self._rr()
def close(self):
if self.s: self.s.close(); self.s=None
api=R('192.168.100.2').c('mi4','m14')
# Create test user with expired date
api.cmd('/ip/hotspot/user/add', name='test_reset_family', profile='Family', comment='23/07/2026 01:00:00', **{'limit-uptime': '1s'})
print("Created test user")
# Verify
for u in api.cmd('/ip/hotspot/user/print', **{'?name': 'test_reset_family'}):
print(" %s | uptime=%s | comment=%s" % (u.get('name',''), u.get('limit-uptime',''), u.get('comment','')))
api.close()
+135
View File
@@ -0,0 +1,135 @@
import socket, hashlib
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_raw(self):
"""Read a word and return raw bytes"""
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
def _read_sentence(self):
words = []
while True:
w = self._read_word_raw()
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].decode('utf-8', errors='replace')
rest = sentence[1:]
if reply == '!done': break
elif reply == '!re':
d = {}
for w in rest:
ws = w.decode('utf-8', errors='replace')
if '=' in ws:
parts = ws.split('=', 2)
d[parts[1]] = parts[2] if len(parts) >= 3 else ''
else: d[ws] = ''
responses.append(d)
elif reply == '!trap':
responses.append(('!trap', [r.decode('utf-8', errors='replace') for r in rest]))
return responses
def _read_response_raw(self):
"""Read and return raw sentences without decoding"""
sentences = []
while True:
sentence = self._read_sentence()
if not sentence: break
reply = sentence[0]
rest = sentence[1:]
if reply == b'!done': break
sentences.append((reply, rest))
return sentences
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
api = RouterOSApi('192.168.100.2')
api.connect('mi4', 'm14')
# Read raw bytes of Bulanan2 on-event
words = [command:= '/system/scheduler/print']
words.append('?name=Bulanan2')
api._write_sentence(*words)
raw_resp = api._read_response_raw()
# Find the on-event field
for reply, rest in raw_resp:
if reply == b'!re':
for w in rest:
ws = w.decode('utf-8', errors='replace')
if ws.startswith('=on-event='):
# Get raw bytes after '=on-event='
prefix = b'=on-event='
idx = w.find(prefix)
if idx >= 0:
on_event_bytes = w[idx + len(prefix):]
print(f"on-event raw bytes length: {len(on_event_bytes)}")
# Find all occurrences of :pic in raw bytes
search = b':pic'
pos = 0
while True:
pos = on_event_bytes.find(search, pos)
if pos == -1: break
context = on_event_bytes[max(0,pos-5):pos+20]
print(f"\n ':pic' at byte {pos}")
print(f" Hex: {' '.join(f'{b:02x}' for b in context)}")
print(f" ASCII: {''.join(chr(b) if 32 <= b < 127 else '?' for b in context)}")
# Show what follows each :pic
after = on_event_bytes[pos:pos+10]
print(f" Next 10 bytes: {' '.join(f'{b:02x}' for b in after)}")
pos += 4
api.close()
+127
View File
@@ -0,0 +1,127 @@
import socket, hashlib
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
api = RouterOSApi('192.168.100.2')
api.connect('mi4', 'm14')
# Get Bulanan2 on-event
resp = api.cmd('/system/scheduler/print', **{'?name': 'Bulanan2'})
raw = resp[0]['on-event']
print(f"Raw length: {len(raw)}")
print(f"Raw first 200: {raw[:200]}")
print(f":pic count: {raw.count(':pic')}")
print(f":pick count: {raw.count(':pick')}")
# Find all :pic positions
idx = 0
positions = []
while True:
idx = raw.find(':pic', idx)
if idx == -1: break
positions.append(idx)
print(f" :pic at {idx}: ...{raw[max(0,idx-8):idx+12]}...")
idx += 4
print(f"\nReplacing :pic -> :pick...")
fixed = raw.replace(':pic', ':pick')
print(f"Fixed length: {len(fixed)}")
print(f"Fixed :pic count: {fixed.count(':pic')}")
print(f"Fixed :pick count: {fixed.count(':pick')}")
match_count = 0
for i, (a, b) in enumerate(zip(raw, fixed)):
if a != b:
match_count += 1
if match_count <= 10:
print(f" Diff at {i}: '{a}' -> '{b}' (ctx: ...{raw[max(0,i-5):i+5]}...)")
# Also try manual byte-level check
raw_bytes = raw.encode('utf-8')
fixed_bytes = fixed.encode('utf-8')
print(f"\nRaw bytes length: {len(raw_bytes)}")
print(f"Fixed bytes length: {len(fixed_bytes)}")
print(f"Expected diff: {raw.count(':pic')} chars * 1 = {raw.count(':pic')} diff")
print(f"Actual diff: {len(fixed_bytes) - len(raw_bytes)}")
api.close()
+30
View File
@@ -0,0 +1,30 @@
import librouteros
api = librouteros.connect('192.168.100.2', 'mi4', 'm14', port=8728)
# Get Bulanan2 details
items = list(api('/system/scheduler/print'))
for item in items:
if item['name'] == 'Bulanan2':
print(f'Bulanan2:')
print(f' .id = {item[".id"]}')
oe = item['on-event']
print(f' on-event length = {len(oe)}')
print(f' on-event first 50: {oe[:50]}')
# Test: set on-event to very simple script
simple = ':put "test123"'
print(f'\nSetting on-event to: {simple}')
api('/system/scheduler/set', **{'.id': item['.id'], 'on-event': simple})
# Re-read
items2 = list(api('/system/scheduler/print'))
for item2 in items2:
if item2['name'] == 'Bulanan2':
oe2 = item2['on-event']
print(f'After set, on-event = {oe2}')
print(f'Match: {oe2 == simple}')
break
break
api.close()
+62
View File
@@ -0,0 +1,62 @@
import librouteros
api = librouteros.connect('192.168.100.2', 'mi4', 'm14', port=8728)
items = list(api('/system/scheduler/print'))
for item in items:
if item['name'] == 'Bulanan2':
oid = item['.id']
print(f'Bulanan2 .id = {oid}')
print(f'on-event len = {len(item["on-event"])}')
# Try setting via different .id formats
test_cmds = [
# Test 1: numeric id with comment
('comment', f'TEST_{oid}'),
]
for field, val in test_cmds:
api('/system/scheduler/set', **{'.id': oid, field: val})
# Verify comment
items = list(api('/system/scheduler/print'))
for i in items:
if i['name'] == 'Bulanan2':
print(f'Comment after: {i.get("comment")}')
# Test: set on-event with simplest possible script
print(f'\nSetting on-event with no special chars...')
try:
api('/system/scheduler/set', **{'.id': oid, 'on-event': 'put hello'})
except Exception as e:
print(f' ERROR: {e}')
# Re-read
items = list(api('/system/scheduler/print'))
for i in items:
if i['name'] == 'Bulanan2':
oe = i.get('on-event', '')
print(f'on-event now: {oe[:60]}')
print(f'Changed: {oe != item["on-event"]}')
# Test: set on-event with : character
print(f'\nSetting on-event with colon...')
try:
api('/system/scheduler/set', **{'.id': oid, 'on-event': ':put "hi"'})
except Exception as e:
print(f' ERROR: {e}')
items = list(api('/system/scheduler/print'))
for i in items:
if i['name'] == 'Bulanan2':
oe = i.get('on-event', '')
print(f'on-event now: {oe[:60]}')
expected = ':put "hi"'
print(f'Changed to :put: {oe == expected}')
# Restore comment
api('/system/scheduler/set', **{'.id': oid, 'comment': 'Monitor Profile Bulanan2'})
break
api.close()
+27
View File
@@ -0,0 +1,27 @@
import paramiko
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('192.168.100.2', username='mi4', password='m14', look_for_keys=False, allow_agent=False)
# Test various ways to get on-event
tests = [
'/system scheduler get Bulanan2 on-event',
'/system scheduler print terse where name=Bulanan2',
'/system scheduler print terse ?name=Bulanan2',
':put [/system scheduler get Bulanan2 on-event]',
':put [/system scheduler find where name=Bulanan2]',
]
for cmd in tests:
print(f"\n--- {cmd[:60]} ---")
stdin, stdout, stderr = client.exec_command(cmd)
import time
time.sleep(0.5)
out = stdout.read().decode().strip()
err = stderr.read().decode().strip()
print(f"out: {out[:200]}")
if err:
print(f"err: {err[:200]}")
client.close()
+129
View File
@@ -0,0 +1,129 @@
# Telegram Expiry Notifier + Reset Button — Deploy ke Server
## Overview
Mengganti direct call Telegram API dari RouterOS dengan PHP intermediary agar bisa kirim inline button "Reset" untuk user hotspot yang expired.
### Arsitektur
```
RouterOS scheduler (Family)
↓ call HTTP GET
↓
notify-expire.php (server)
↓ POST ke Telegram API
↓
⚠️ User X expired [🔄 Reset] (ke topik Notif Expire)
↓ admin klik "Reset"
↓ callback_query = "reset_X" ke Bot API
↓
tg-cb-poll scheduler (router, tiap 60s)
↓ call HTTP GET
↓
telegram-cb.php (server)
↓ polling getUpdates → proses callback
↓ RouterOS API: set limit-uptime=0, comment="", reset-counters
↓
✅ User X has been reset (ke topik Notif Expire)
```
### Files
| File | Fungsi |
|------|--------|
| `notify-expire.php` | Kirim notif + inline button ke Telegram |
| `telegram-cb.php` | Polling callback query + eksekusi reset |
| `setup.sh` | Setup script untuk server (install PHP + deploy) |
### Prerequisites (Server)
- Linux server (tested on Ubuntu/Debian)
- PHP 7.4+ dengan `curl` extension
- Akses keluar ke `api.telegram.org` (port 443)
- Akses ke RouterOS API (`192.168.100.2:8728`)
- `curl` binary di PATH (untuk internal call dari PHP ke Telegram)
### Setup Cepat
```bash
# 1. Copy files ke server
scp notify-expire.php player@somewhere:/home/player/telegram-notif/
scp telegram-cb.php player@somewhere:/home/player/telegram-notif/
scp setup.sh player@somewhere:/home/player/telegram-notif/
# 2. SSH ke server
ssh player@somewhere
# 3. Run setup
cd ~/telegram-notif
chmod +x setup.sh
./setup.sh
# 4. Start PHP built-in server (background)
nohup php -S 0.0.0.0:8080 -t /home/player/telegram-notif > /tmp/telegram-notif.log 2>&1 &
```
### Update RouterOS Scheduler
#### A. Family scheduler — notifikasi
Ganti on-event:
```bash
# OLD
/tool fetch url=("https://api.telegram.org/bot" . $bot . "/sendMessage?chat_id=" . $chat . "&text=" . $msg) http-method=get keep-result=no
# NEW — arahkan ke IP Tailscale server
/tool fetch url=("http://100.100.31.46:8080/notify-expire.php?key=msr-2026-reset&name=" . $name) http-method=get keep-result=no
```
#### B. Tambah scheduler polling callback
```bash
/system scheduler add name=tg-cb-poll interval=00:01:00 start-time=startup on-event=":delay 10s;/tool fetch url=\"http://100.100.31.46:8080/telegram-cb.php?key=msr-2026-reset\" http-method=get keep-result=no" disabled=no
```
### Secret Key
`msr-2026-reset` — shared secret di URL parameter `key`. Diganti sesuai kebutuhan.
### Bot Config
- Token: `5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk`
- Group: `-1002554941429` (laporkan)
- Topic ID: `42` (Notif Expire)
### RouterOS Credentials (untuk callback reset)
- IP: `192.168.100.2`
- User: `mi4`
- Password: `m14`
### Troubleshooting
**Logs**:
```bash
tail -f /tmp/telegram-notif.log
```
**Test notifikasi**:
```bash
curl "http://localhost:8080/notify-expire.php?key=msr-2026-reset&name=test_user"
```
**Test reset langsung**:
```bash
curl "http://localhost:8080/telegram-cb.php?key=msr-2026-reset&test=test_user"
```
**PHP curl extension**:
```bash
sudo apt install php-curl
```
### File Reference
Source files di `C:\Users\Admin\dev\Mikhmon Server\mikhmon\`:
- `notify-expire.php`
- `process/telegram-cb.php`
- `lib/routeros_api.class.php` (dependency untuk koneksi ke RouterOS)
+44
View File
@@ -0,0 +1,44 @@
# Telegram Expiry Notifier — Deploy ke somewhere
# Jalankan script ini. Masukkan password SSH player@somewhere ketika diminta.
$server = "player@somewhere"
$remoteDir = "/home/player/telegram-notif"
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
Write-Host "=== Deploy Telegram Notifier ke $server ===" -ForegroundColor Cyan
Write-Host ""
# 1. Buat remote directory via SSH
Write-Host "[1/4] Membuat remote directory..." -ForegroundColor Yellow
Start-Process -Wait -WindowStyle Normal -FilePath "ssh" -ArgumentList "-t $server mkdir -p $remoteDir"
Write-Host ""
# 2. Copy files via SCP
Write-Host "[2/4] Copy files via SCP..." -ForegroundColor Yellow
Start-Process -Wait -WindowStyle Normal -FilePath "scp" -ArgumentList "$scriptDir\notify-expire.php $scriptDir\telegram-cb.php $scriptDir\routeros_api.class.php $scriptDir\setup.sh ${server}:${remoteDir}/"
Write-Host ""
# 3. Run setup via SSH
Write-Host "[3/4] Run remote setup..." -ForegroundColor Yellow
Write-Host " (akan menginstall PHP + membuat systemd service)" -ForegroundColor Gray
Start-Process -Wait -WindowStyle Normal -FilePath "ssh" -ArgumentList "-t $server cd $remoteDir && chmod +x setup.sh && ./setup.sh"
Write-Host ""
# 4. Test
Write-Host "[4/4] Test..." -ForegroundColor Yellow
Start-Process -Wait -WindowStyle Normal -FilePath "ssh" -ArgumentList "-t $server curl -s 'http://localhost:8080/notify-expire.php?key=msr-2026-reset&name=test_deploy'"
Write-Host ""
Write-Host "=== Done ===" -ForegroundColor Green
Write-Host ""
Write-Host "Update RouterOS scheduler:"
Write-Host " Family on-event:"
Write-Host ' /tool fetch url=("http://100.100.31.46:8080/notify-expire.php?key=msr-2026-reset&name=" . $name) http-method=get keep-result=no'
Write-Host ""
Write-Host " Tambah scheduler:"
Write-Host ' /system scheduler add name=tg-cb-poll interval=00:01:00 start-time=startup on-event=":delay 10s;/tool fetch url=\"http://100.100.31.46:8080/telegram-cb.php?key=msr-2026-reset\" http-method=get keep-result=no" disabled=no'
Write-Host ""
Write-Host "Remote management:"
Write-Host " ssh $server"
Write-Host " sudo systemctl status telegram-notif"
Write-Host " sudo journalctl -u telegram-notif -f"
+49
View File
@@ -0,0 +1,49 @@
<?php
error_reporting(0);
$secret = 'msr-2026-reset';
if (!isset($_GET['key']) || $_GET['key'] !== $secret) { http_response_code(403); die('auth failed'); }
$user = preg_replace('/[^a-zA-Z0-9_.@-]/', '', $_GET['name']);
if (!$user) { http_response_code(400); die('missing name'); }
$bot = '5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk';
$chat = '-1002554941429';
$thread = 42;
$text = "⚠️ User *{$user}* expired";
$markup = json_encode(array(
'inline_keyboard' => array(array(array(
'text' => '🔄 Reset',
'callback_data' => "reset_{$user}"
)))
));
$data = http_build_query(array(
'chat_id' => $chat,
'message_thread_id' => $thread,
'text' => $text,
'parse_mode' => 'Markdown',
'reply_markup' => $markup,
));
function tg_post($url, $data) {
$ctx = stream_context_create(array('http' => array(
'method' => 'POST',
'header' => 'Content-Type: application/x-www-form-urlencoded',
'content' => $data,
'timeout' => 10,
)));
return @file_get_contents($url, false, $ctx);
}
$r = tg_post("https://api.telegram.org/bot{$bot}/sendMessage", $data);
if ($r === false) { http_response_code(500); echo "fail: http error"; exit; }
$result = json_decode($r, true);
if (isset($result['ok']) && $result['ok']) {
echo "sent: " . $result['result']['message_id'];
} else {
http_response_code(500);
echo "fail: " . (isset($result['description']) ? $result['description'] : 'no response');
}
+647
View File
@@ -0,0 +1,647 @@
<?php
/*****************************
*
* RouterOS PHP API class v1.6
* Author: Denis Basta
* Contributors:
* Nick Barnes
* Ben Menking (ben [at] infotechsc [dot] com)
* Jeremy Jefferson (http://jeremyj.com)
* Cristian Deluxe (djcristiandeluxe [at] gmail [dot] com)
* Mikhail Moskalev (mmv.rus [at] gmail [dot] com)
*
* http://www.mikrotik.com
* http://wiki.mikrotik.com/wiki/API_PHP_class
*
******************************/
class RouterosAPI
{
var $debug = false; // Show debug information
var $connected = false; // Connection state
var $port = 8728; // Port to connect to (default 8729 for ssl)
var $ssl = false; // Connect using SSL (must enable api-ssl in IP/Services)
var $timeout = 3; // Connection attempt timeout and data read timeout
var $attempts = 5; // Connection attempt count
var $delay = 3; // Delay between connection attempts in seconds
var $socket; // Variable for storing socket resource
var $error_no; // Variable for storing connection error number, if any
var $error_str; // Variable for storing connection error text, if any
/* Check, can be var used in foreach */
public function isIterable($var)
{
return $var !== null
&& (is_array($var)
|| $var instanceof Traversable
|| $var instanceof Iterator
|| $var instanceof IteratorAggregate
);
}
/**
* Print text for debug purposes
*
* @param string $text Text to print
*
* @return void
*/
public function debug($text)
{
if ($this->debug) {
echo $text . "\n";
}
}
/**
*
*
* @param string $length
*
* @return void
*/
public function encodeLength($length)
{
if ($length < 0x80) {
$length = chr($length);
} elseif ($length < 0x4000) {
$length |= 0x8000;
$length = chr(($length >> 8) & 0xFF) . chr($length & 0xFF);
} elseif ($length < 0x200000) {
$length |= 0xC00000;
$length = chr(($length >> 16) & 0xFF) . chr(($length >> 8) & 0xFF) . chr($length & 0xFF);
} elseif ($length < 0x10000000) {
$length |= 0xE0000000;
$length = chr(($length >> 24) & 0xFF) . chr(($length >> 16) & 0xFF) . chr(($length >> 8) & 0xFF) . chr($length & 0xFF);
} elseif ($length >= 0x10000000) {
$length = chr(0xF0) . chr(($length >> 24) & 0xFF) . chr(($length >> 16) & 0xFF) . chr(($length >> 8) & 0xFF) . chr($length & 0xFF);
}
return $length;
}
/**
* Login to RouterOS
*
* @param string $ip Hostname (IP or domain) of the RouterOS server
* @param string $login The RouterOS username
* @param string $password The RouterOS password
*
* @return boolean If we are connected or not
*/
public function connect($ip, $login, $password)
{
for ($ATTEMPT = 1; $ATTEMPT <= $this->attempts; $ATTEMPT++) {
$this->connected = false;
$PROTOCOL = ($this->ssl ? 'ssl://' : '' );
$context = stream_context_create(array('ssl' => array('ciphers' => 'ADH:ALL', 'verify_peer' => false, 'verify_peer_name' => false)));
$this->debug('Connection attempt #' . $ATTEMPT . ' to ' . $PROTOCOL . $ip . ':' . $this->port . '...');
$this->socket = @stream_socket_client($PROTOCOL . $ip.':'. $this->port, $this->error_no, $this->error_str, $this->timeout, STREAM_CLIENT_CONNECT,$context);
if ($this->socket) {
socket_set_timeout($this->socket, $this->timeout);
$this->write('/login', false);
$this->write('=name=' . $login, false);
$this->write('=password=' . $password);
$RESPONSE = $this->read(false);
if (isset($RESPONSE[0])) {
if ($RESPONSE[0] == '!done') {
if (!isset($RESPONSE[1])) {
// Login method post-v6.43
$this->connected = true;
break;
} else {
// Login method pre-v6.43
$MATCHES = array();
if (preg_match_all('/[^=]+/i', $RESPONSE[1], $MATCHES)) {
if ($MATCHES[0][0] == 'ret' && strlen($MATCHES[0][1]) == 32) {
$this->write('/login', false);
$this->write('=name=' . $login, false);
$this->write('=response=00' . md5(chr(0) . $password . pack('H*', $MATCHES[0][1])));
$RESPONSE = $this->read(false);
if (isset($RESPONSE[0]) && $RESPONSE[0] == '!done') {
$this->connected = true;
break;
}
}
}
}
}
}
fclose($this->socket);
}
sleep($this->delay);
}
if ($this->connected) {
$this->debug('Connected...');
} else {
$this->debug('Error...');
}
return $this->connected;
}
/**
* Disconnect from RouterOS
*
* @return void
*/
public function disconnect()
{
// let's make sure this socket is still valid. it may have been closed by something else
if( is_resource($this->socket) ) {
fclose($this->socket);
}
$this->connected = false;
$this->debug('Disconnected...');
}
/**
* Parse response from Router OS
*
* @param array $response Response data
*
* @return array Array with parsed data
*/
public function parseResponse($response)
{
if (is_array($response)) {
$PARSED = array();
$CURRENT = null;
$singlevalue = null;
foreach ($response as $x) {
if (in_array($x, array('!fatal','!re','!trap'))) {
if ($x == '!re') {
$CURRENT =& $PARSED[];
} else {
$CURRENT =& $PARSED[$x][];
}
} elseif ($x != '!done') {
$MATCHES = array();
if (preg_match_all('/[^=]+/i', $x, $MATCHES)) {
if ($MATCHES[0][0] == 'ret') {
$singlevalue = $MATCHES[0][1];
}
$CURRENT[$MATCHES[0][0]] = (isset($MATCHES[0][1]) ? $MATCHES[0][1] : '');
}
}
}
if (empty($PARSED) && !is_null($singlevalue)) {
$PARSED = $singlevalue;
}
return $PARSED;
} else {
return array();
}
}
/**
* Parse response from Router OS
*
* @param array $response Response data
*
* @return array Array with parsed data
*/
public function parseResponse4Smarty($response)
{
if (is_array($response)) {
$PARSED = array();
$CURRENT = null;
$singlevalue = null;
foreach ($response as $x) {
if (in_array($x, array('!fatal','!re','!trap'))) {
if ($x == '!re') {
$CURRENT =& $PARSED[];
} else {
$CURRENT =& $PARSED[$x][];
}
} elseif ($x != '!done') {
$MATCHES = array();
if (preg_match_all('/[^=]+/i', $x, $MATCHES)) {
if ($MATCHES[0][0] == 'ret') {
$singlevalue = $MATCHES[0][1];
}
$CURRENT[$MATCHES[0][0]] = (isset($MATCHES[0][1]) ? $MATCHES[0][1] : '');
}
}
}
foreach ($PARSED as $key => $value) {
$PARSED[$key] = $this->arrayChangeKeyName($value);
}
return $PARSED;
if (empty($PARSED) && !is_null($singlevalue)) {
$PARSED = $singlevalue;
}
} else {
return array();
}
}
/**
* Change "-" and "/" from array key to "_"
*
* @param array $array Input array
*
* @return array Array with changed key names
*/
public function arrayChangeKeyName(&$array)
{
if (is_array($array)) {
foreach ($array as $k => $v) {
$tmp = str_replace("-", "_", $k);
$tmp = str_replace("/", "_", $tmp);
if ($tmp) {
$array_new[$tmp] = $v;
} else {
$array_new[$k] = $v;
}
}
return $array_new;
} else {
return $array;
}
}
/**
* Read data from Router OS
*
* @param boolean $parse Parse the data? default: true
*
* @return array Array with parsed or unparsed data
*/
public function read($parse = true)
{
$RESPONSE = array();
$receiveddone = false;
while (true) {
// Read the first byte of input which gives us some or all of the length
// of the remaining reply.
$BYTE = ord(fread($this->socket, 1));
$LENGTH = 0;
// If the first bit is set then we need to remove the first four bits, shift left 8
// and then read another byte in.
// We repeat this for the second and third bits.
// If the fourth bit is set, we need to remove anything left in the first byte
// and then read in yet another byte.
if ($BYTE & 128) {
if (($BYTE & 192) == 128) {
$LENGTH = (($BYTE & 63) << 8) + ord(fread($this->socket, 1));
} else {
if (($BYTE & 224) == 192) {
$LENGTH = (($BYTE & 31) << 8) + ord(fread($this->socket, 1));
$LENGTH = ($LENGTH << 8) + ord(fread($this->socket, 1));
} else {
if (($BYTE & 240) == 224) {
$LENGTH = (($BYTE & 15) << 8) + ord(fread($this->socket, 1));
$LENGTH = ($LENGTH << 8) + ord(fread($this->socket, 1));
$LENGTH = ($LENGTH << 8) + ord(fread($this->socket, 1));
} else {
$LENGTH = ord(fread($this->socket, 1));
$LENGTH = ($LENGTH << 8) + ord(fread($this->socket, 1));
$LENGTH = ($LENGTH << 8) + ord(fread($this->socket, 1));
$LENGTH = ($LENGTH << 8) + ord(fread($this->socket, 1));
}
}
}
} else {
$LENGTH = $BYTE;
}
$_ = "";
// If we have got more characters to read, read them in.
if ($LENGTH > 0) {
$_ = "";
$retlen = 0;
while ($retlen < $LENGTH) {
$toread = $LENGTH - $retlen;
$_ .= fread($this->socket, $toread);
$retlen = strlen($_);
}
$RESPONSE[] = $_;
$this->debug('>>> [' . $retlen . '/' . $LENGTH . '] bytes read.');
}
// If we get a !done, make a note of it.
if ($_ == "!done") {
$receiveddone = true;
}
$STATUS = socket_get_status($this->socket);
if ($LENGTH > 0) {
$this->debug('>>> [' . $LENGTH . ', ' . $STATUS['unread_bytes'] . ']' . $_);
}
if ((!$this->connected && !$STATUS['unread_bytes']) || ($this->connected && !$STATUS['unread_bytes'] && $receiveddone)) {
break;
}
}
if ($parse) {
$RESPONSE = $this->parseResponse($RESPONSE);
}
return $RESPONSE;
}
/**
* Write (send) data to Router OS
*
* @param string $command A string with the command to send
* @param mixed $param2 If we set an integer, the command will send this data as a "tag"
* If we set it to boolean true, the funcion will send the comand and finish
* If we set it to boolean false, the funcion will send the comand and wait for next command
* Default: true
*
* @return boolean Return false if no command especified
*/
public function write($command, $param2 = true)
{
if ($command) {
$data = explode("\n", $command);
foreach ($data as $com) {
$com = trim($com);
fwrite($this->socket, $this->encodeLength(strlen($com)) . $com);
$this->debug('<<< [' . strlen($com) . '] ' . $com);
}
if (gettype($param2) == 'integer') {
fwrite($this->socket, $this->encodeLength(strlen('.tag=' . $param2)) . '.tag=' . $param2 . chr(0));
$this->debug('<<< [' . strlen('.tag=' . $param2) . '] .tag=' . $param2);
} elseif (gettype($param2) == 'boolean') {
fwrite($this->socket, ($param2 ? chr(0) : ''));
}
return true;
} else {
return false;
}
}
/**
* Write (send) data to Router OS
*
* @param string $com A string with the command to send
* @param array $arr An array with arguments or queries
*
* @return array Array with parsed
*/
public function comm($com, $arr = array())
{
$count = count($arr);
$this->write($com, !$arr);
$i = 0;
if ($this->isIterable($arr)) {
foreach ($arr as $k => $v) {
switch ($k[0]) {
case "?":
$el = "$k=$v";
break;
case "~":
$el = "$k~$v";
break;
default:
$el = "=$k=$v";
break;
}
$last = ($i++ == $count - 1);
$this->write($el, $last);
}
}
return $this->read();
}
/**
* Standard destructor
*
* @return void
*/
public function __destruct()
{
$this->disconnect();
}
}
// encrypt decript
function encrypt($string, $key=128) {
$result = '';
for($i=0, $k= strlen($string); $i<$k; $i++) {
$char = substr($string, $i, 1);
$keychar = substr($key, ($i % strlen($key))-1, 1);
$char = chr(ord($char)+ord($keychar));
$result .= $char;
}
return base64_encode($result);
}
function decrypt($string, $key=128) {
$result = '';
$string = base64_decode($string);
for($i=0, $k=strlen($string); $i< $k ; $i++) {
$char = substr($string, $i, 1);
$keychar = substr($key, ($i % strlen($key))-1, 1);
$char = chr(ord($char)-ord($keychar));
$result .= $char;
}
return $result;
}
// Reformat date time MikroTik
// by Laksamadi Guko
function formatInterval($dtm){
$val_convert = $dtm;
$new_format = str_replace("s", "", str_replace("m", "m ", str_replace("h", "h ", str_replace("d", "d ", str_replace("w", "w ", $val_convert)))));
return $new_format;
}
function formatDTM($dtm){
if(substr($dtm, 1,1) == "d" || substr($dtm, 2,1) == "d"){
$day = explode("d",$dtm)[0]."d";
$day = str_replace("d", "d ", str_replace("w", "w ", $day));
$dtm = explode("d",$dtm)[1];
}elseif(substr($dtm, 1,1) == "w" && substr($dtm, 3,1) == "d" || substr($dtm, 2,1) == "w" && substr($dtm, 4,1) == "d"){
$day = explode("d",$dtm)[0]."d";
$day = str_replace("d", "d ", str_replace("w", "w ", $day));
$dtm = explode("d",$dtm)[1];
}elseif (substr($dtm, 1,1) == "w" || substr($dtm, 2,1) == "w" ) {
$day = explode("w",$dtm)[0]."w";
$day = str_replace("d", "d ", str_replace("w", "w ", $day));
$dtm = explode("w",$dtm)[1];
}
// secs
if(strlen($dtm) == "2" && substr($dtm, -1) == "s"){
$format = $day." 00:00:0".substr($dtm, 0,-1);
}elseif(strlen($dtm) == "3" && substr($dtm, -1) == "s"){
$format = $day." 00:00:".substr($dtm, 0,-1);
//minutes
}elseif(strlen($dtm) == "2" && substr($dtm, -1) == "m"){
$format = $day." 00:0".substr($dtm, 0,-1).":00";
}elseif(strlen($dtm) == "3" && substr($dtm, -1) == "m"){
$format = $day." 00:".substr($dtm, 0,-1).":00";
//hours
}elseif(strlen($dtm) == "2" && substr($dtm, -1) == "h"){
$format = $day." 0".substr($dtm, 0,-1).":00:00";
}elseif(strlen($dtm) == "3" && substr($dtm, -1) == "h"){
$format = $day." ".substr($dtm, 0,-1).":00:00";
//minutes -secs
}elseif(strlen($dtm) == "4" && substr($dtm, -1) == "s" && substr($dtm,1,-2) == "m"){
$format = $day." "."00:0".substr($dtm, 0,1).":0".substr($dtm, 2,-1);
}elseif(strlen($dtm) == "5" && substr($dtm, -1) == "s" && substr($dtm,1,-3) == "m"){
$format = $day." "."00:0".substr($dtm, 0,1).":".substr($dtm, 2,-1);
}elseif(strlen($dtm) == "5" && substr($dtm, -1) == "s" && substr($dtm,2,-2) == "m"){
$format = $day." "."00:".substr($dtm, 0,2).":0".substr($dtm, 3,-1);
}elseif(strlen($dtm) == "6" && substr($dtm, -1) == "s" && substr($dtm,2,-3) == "m"){
$format = $day." "."00:".substr($dtm, 0,2).":".substr($dtm, 3,-1);
//hours -secs
}elseif(strlen($dtm) == "4" && substr($dtm, -1) == "s" && substr($dtm,1,-2) == "h"){
$format = $day." 0".substr($dtm, 0,1).":00:0".substr($dtm, 2,-1);
}elseif(strlen($dtm) == "5" && substr($dtm, -1) == "s" && substr($dtm,1,-3) == "h"){
$format = $day." 0".substr($dtm, 0,1).":00:".substr($dtm, 2,-1);
}elseif(strlen($dtm) == "5" && substr($dtm, -1) == "s" && substr($dtm,2,-2) == "h"){
$format = $day." ".substr($dtm, 0,2).":00:0".substr($dtm, 3,-1);
}elseif(strlen($dtm) == "6" && substr($dtm, -1) == "s" && substr($dtm,2,-3) == "h"){
$format = $day." ".substr($dtm, 0,2).":00:".substr($dtm, 3,-1);
//hours -secs
}elseif(strlen($dtm) == "4" && substr($dtm, -1) == "m" && substr($dtm,1,-2) == "h"){
$format = $day." 0".substr($dtm, 0,1).":0".substr($dtm, 2,-1).":00";
}elseif(strlen($dtm) == "5" && substr($dtm, -1) == "m" && substr($dtm,1,-3) == "h"){
$format = $day." 0".substr($dtm, 0,1).":".substr($dtm, 2,-1).":00";
}elseif(strlen($dtm) == "5" && substr($dtm, -1) == "m" && substr($dtm,2,-2) == "h"){
$format = $day." ".substr($dtm, 0,2).":0".substr($dtm, 3,-1).":00";
}elseif(strlen($dtm) == "6" && substr($dtm, -1) == "m" && substr($dtm,2,-3) == "h"){
$format = $day." ".substr($dtm, 0,2).":".substr($dtm, 3,-1).":00";
//hours minutes secs
}elseif(strlen($dtm) == "6" && substr($dtm, -1) == "s" && substr($dtm,3,-2) == "m" && substr($dtm,1,-4) == "h"){
$format = $day." 0".substr($dtm, 0,1).":0".substr($dtm, 2,-3).":0".substr($dtm, 4,-1);
}elseif(strlen($dtm) == "7" && substr($dtm, -1) == "s" && substr($dtm,3,-3) == "m" && substr($dtm,1,-5) == "h"){
$format = $day." 0".substr($dtm, 0,1).":0".substr($dtm, 2,-4).":".substr($dtm, 4,-1);
}elseif(strlen($dtm) == "7" && substr($dtm, -1) == "s" && substr($dtm,4,-2) == "m" && substr($dtm,1,-5) == "h"){
$format = $day." 0".substr($dtm, 0,1).":".substr($dtm, 2,-3).":0".substr($dtm, 5,-1);
}elseif(strlen($dtm) == "8" && substr($dtm, -1) == "s" && substr($dtm,4,-3) == "m" && substr($dtm,1,-6) == "h"){
$format = $day." 0".substr($dtm, 0,1).":".substr($dtm, 2,-4).":".substr($dtm, 5,-1);
}elseif(strlen($dtm) == "7" && substr($dtm, -1) == "s" && substr($dtm,4,-2) == "m" && substr($dtm,2,-4) == "h"){
$format = $day." ".substr($dtm, 0,2).":0".substr($dtm, 3,-3).":0".substr($dtm, 5,-1);
}elseif(strlen($dtm) == "8" && substr($dtm, -1) == "s" && substr($dtm,4,-3) == "m" && substr($dtm,2,-5) == "h"){
$format = $day." ".substr($dtm, 0,2).":0".substr($dtm, 3,-4).":".substr($dtm, 5,-1);
}elseif(strlen($dtm) == "8" && substr($dtm, -1) == "s" && substr($dtm,5,-2) == "m" && substr($dtm,2,-5) == "h"){
$format = $day." ".substr($dtm, 0,2).":".substr($dtm, 3,-3).":0".substr($dtm, 6,-1);
}elseif(strlen($dtm) == "9" && substr($dtm, -1) == "s" && substr($dtm,5,-3) == "m" && substr($dtm,2,-6) == "h"){
$format = $day." ".substr($dtm, 0,2).":".substr($dtm, 3,-4).":".substr($dtm, 6,-1);
}else{
$format = $dtm;
}
return $format;
}
function randN($length) {
$chars = "23456789";
$charArray = str_split($chars);
$charCount = strlen($chars);
$result = "";
for($i=1;$i<=$length;$i++)
{
$randChar = rand(0,$charCount-1);
$result .= $charArray[$randChar];
}
return $result;
}
function randUC($length) {
$chars = "ABCDEFGHJKLMNPRSTUVWXYZ";
$charArray = str_split($chars);
$charCount = strlen($chars);
$result = "";
for($i=1;$i<=$length;$i++)
{
$randChar = rand(0,$charCount-1);
$result .= $charArray[$randChar];
}
return $result;
}
function randLC($length) {
$chars = "abcdefghijkmnprstuvwxyz";
$charArray = str_split($chars);
$charCount = strlen($chars);
$result = "";
for($i=1;$i<=$length;$i++)
{
$randChar = rand(0,$charCount-1);
$result .= $charArray[$randChar];
}
return $result;
}
function randULC($length) {
$chars = "ABCDEFGHJKLMNPRSTUVWXYZabcdefghijkmnprstuvwxyz";
$charArray = str_split($chars);
$charCount = strlen($chars);
$result = "";
for($i=1;$i<=$length;$i++)
{
$randChar = rand(0,$charCount-1);
$result .= $charArray[$randChar];
}
return $result;
}
function randNLC($length) {
$chars = "23456789abcdefghijkmnprstuvwxyz";
$charArray = str_split($chars);
$charCount = strlen($chars);
$result = "";
for($i=1;$i<=$length;$i++)
{
$randChar = rand(0,$charCount-1);
$result .= $charArray[$randChar];
}
return $result;
}
function randNUC($length) {
$chars = "23456789ABCDEFGHJKLMNPRSTUVWXYZ";
$charArray = str_split($chars);
$charCount = strlen($chars);
$result = "";
for($i=1;$i<=$length;$i++)
{
$randChar = rand(0,$charCount-1);
$result .= $charArray[$randChar];
}
return $result;
}
function randNULC($length) {
$chars = "23456789ABCDEFGHJKLMNPRSTUVWXYZabcdefghijkmnprstuvwxyz";
$charArray = str_split($chars);
$charCount = strlen($chars);
$result = "";
for($i=1;$i<=$length;$i++)
{
$randChar = rand(0,$charCount-1);
$result .= $charArray[$randChar];
}
return $result;
}
?>
+50
View File
@@ -0,0 +1,50 @@
#!/bin/bash
set -e
echo "=== Telegram Expiry Notifier — Setup ==="
DIR="/home/player/telegram-notif"
PORT=8080
SERVICE="telegram-notif"
# 1. Install PHP + curl
echo "[1/4] Installing PHP + php-curl..."
sudo apt update -qq
sudo apt install -y -qq php-cli php-curl curl 2>/dev/null
# 2. Create directory
echo "[2/4] Creating directory..."
mkdir -p "$DIR"
# 3. Copy files (run this script from the same dir as the PHP files)
echo "[3/4] Copying files..."
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cp "$SCRIPT_DIR/notify-expire.php" "$DIR/"
cp "$SCRIPT_DIR/telegram-cb.php" "$DIR/"
# 4. Create systemd service
echo "[4/4] Creating systemd service..."
sudo tee /etc/systemd/system/$SERVICE.service > /dev/null <<EOF
[Unit]
Description=Telegram Expiry Notifier
After=network.target
[Service]
ExecStart=/usr/bin/php -S 0.0.0.0:$PORT -t $DIR
WorkingDirectory=$DIR
Restart=always
RestartSec=5
User=player
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable $SERVICE
sudo systemctl restart $SERVICE
echo "=== Done ==="
echo "Service: sudo systemctl status $SERVICE"
echo "Test: curl http://localhost:$PORT/notify-expire.php?key=msr-2026-reset&name=test"
echo "Logs: sudo journalctl -u $SERVICE -f"
+127
View File
@@ -0,0 +1,127 @@
<?php
error_reporting(0);
$secret = 'msr-2026-reset';
if (!isset($_GET['key']) || $_GET['key'] !== $secret) { http_response_code(403); die('auth failed'); }
$bot = '5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk';
function tg_api($method, $params = array()) {
global $bot;
$url = "https://api.telegram.org/bot{$bot}/{$method}";
if (!empty($params)) {
$data = http_build_query($params);
$ctx = stream_context_create(array('http' => array(
'method' => 'POST',
'header' => 'Content-Type: application/x-www-form-urlencoded',
'content' => $data,
'timeout' => 15,
)));
$r = @file_get_contents($url, false, $ctx);
} else {
$r = @file_get_contents($url, false, stream_context_create(array('http' => array('timeout' => 5))));
}
return $r ? json_decode($r, true) : null;
}
if (isset($_GET['test'])) {
$username = preg_replace('/[^a-zA-Z0-9_.@-]/', '', $_GET['test']);
if (!$username) { die('invalid username'); }
include_once(dirname(__DIR__) . '/lib/routeros_api.class.php');
$api = new RouterosAPI();
$api->debug = false;
$api->port = 33206;
if (!$api->connect('remote.vpnmurahjogja.my.id', 'mi4', 'm14')) { die('conn fail'); }
$found = $api->comm('/ip/hotspot/user/print', array('?name' => $username));
if (empty($found)) { $api->disconnect(); die('user not found'); }
$uid = $found[0]['.id'];
$api->comm('/ip/hotspot/user/set', array('.id' => $uid, 'limit-uptime' => '0', 'comment' => ''));
$api->comm('/ip/hotspot/user/reset-counters', array('.id' => $uid));
$sch = $api->comm('/system/scheduler/print', array('?name' => $username));
if (!empty($sch) && isset($sch[0]['.id'])) {
$api->comm('/system/scheduler/remove', array('.id' => $sch[0]['.id']));
}
$api->disconnect();
tg_api('sendMessage', array(
'chat_id' => '-1002554941429',
'message_thread_id' => 42,
'text' => "✅ *{$username}* has been reset\nReady to use again",
'parse_mode' => 'Markdown',
));
echo "reset ok: {$username}";
exit;
}
$updates = tg_api('getUpdates', array('timeout' => 5, 'allowed_updates' => '["callback_query"]'));
if (!$updates || !isset($updates['ok']) || !$updates['ok']) { echo "no updates"; exit; }
$processed = 0;
$last_id = 0;
include_once(dirname(__DIR__) . '/lib/routeros_api.class.php');
foreach ($updates['result'] as $u) {
$cb = isset($u['callback_query']) ? $u['callback_query'] : null;
if (!$cb) continue;
$data = isset($cb['data']) ? $cb['data'] : '';
$cb_id = isset($cb['id']) ? $cb['id'] : '';
$last_id = max($last_id, $u['update_id']);
if (substr($data, 0, 6) !== 'reset_') continue;
$username = substr($data, 6);
$username = preg_replace('/[^a-zA-Z0-9_.@-]/', '', $username);
if (!$username) continue;
$api = new RouterosAPI();
$api->debug = false;
$api->port = 33206;
if (!$api->connect('remote.vpnmurahjogja.my.id', 'mi4', 'm14')) {
echo "conn fail for {$username} ";
continue;
}
$found = $api->comm('/ip/hotspot/user/print', array('?name' => $username));
if (empty($found)) {
$api->disconnect();
tg_api('answerCallbackQuery', array(
'callback_query_id' => $cb_id,
'text' => "User {$username} not found",
'show_alert' => true,
));
continue;
}
$uid = $found[0]['.id'];
$api->comm('/ip/hotspot/user/set', array('.id' => $uid, 'limit-uptime' => '0', 'comment' => ''));
$api->comm('/ip/hotspot/user/reset-counters', array('.id' => $uid));
$sch = $api->comm('/system/scheduler/print', array('?name' => $username));
if (!empty($sch) && isset($sch[0]['.id'])) {
$api->comm('/system/scheduler/remove', array('.id' => $sch[0]['.id']));
}
$api->disconnect();
tg_api('answerCallbackQuery', array(
'callback_query_id' => $cb_id,
'text' => "✅ {$username} reset!",
'show_alert' => false,
));
tg_api('sendMessage', array(
'chat_id' => '-1002554941429',
'message_thread_id' => 42,
'text' => "✅ *{$username}* has been reset\nReady to use again",
'parse_mode' => 'Markdown',
));
$processed++;
}
if ($last_id > 0) {
tg_api('getUpdates', array('offset' => $last_id + 1, 'timeout' => 1));
}
echo "processed: {$processed}";
+74
View File
@@ -0,0 +1,74 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(60)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
for name in ['Family', 'Bulanan2', 'Bulanan4']:
r = api.cmd('/system/scheduler/print', **{'?name': name})
for x in r:
if isinstance(x,dict):
print('=== %s (id=%s) ===' % (name, x.get('.id')))
print(x.get('on-event',''))
print()
api.close()
+82
View File
@@ -0,0 +1,82 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(60)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
name = 'TEST-ENDTOEND'
url = ('https://api.telegram.org/bot5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk/'
'sendMessage?chat_id=-1002554941429&message_thread_id=42&text=' + name + '+expired')
print('fetching:', url[:100] + '...')
res = api.cmd('/tool/fetch', **{'url': url, 'mode': 'https', 'http-method': 'get', 'keep-result': 'yes'})
print('fetch result:', res)
import time
time.sleep(3)
files = api.cmd('/file/print', **{'?name': '*.txt'})
for f in files:
if isinstance(f,dict):
print('FILE:', f.get('name'), f.get('size'), 'bytes')
api.close()
+73
View File
@@ -0,0 +1,73 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(60)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
files = api.cmd('/file/print')
for f in files:
if isinstance(f,dict):
n = f.get('name','')
if 'TEST' in n:
print('FILE:', repr(n), 'size=', f.get('size'))
api.close()
+119
View File
@@ -0,0 +1,119 @@
import socket, hashlib, time
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(15)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
# Step 1: Set correct clock
print('Setting clock to jul/24/2026 12:00:00...')
api.cmd('/system/clock/set', **{'date': 'jul/24/2026', 'time': '12:00:00'})
clk = api.cmd('/system/clock/print')
print('Clock now: date=%s time=%s' % (clk[0].get('date',''), clk[0].get('time','')))
# Step 2: Enable NTP
print()
print('Enabling NTP client...')
api.cmd('/system/ntp/client/set', **{'enabled': 'yes', 'mode': 'unicast', 'primary-ntp': 'id.pool.ntp.org'})
ntp = api.cmd('/system/ntp/client/print')
print('NTP: enabled=%s mode=%s primary=%s' % (ntp[0].get('enabled',''), ntp[0].get('mode',''), ntp[0].get('primary-ntp','')))
# Step 3: Trigger Bulanan4 scheduler manually
print()
print('Triggering Bulanan4 scheduler...')
api.cmd('/system/scheduler/run', numbers='Bulanan4')
time.sleep(3)
# Step 4: Check Nadia
users = api.cmd('/ip/hotspot/user/print', **{'?name': 'Nadia'})
if users:
u = users[0]
print('Nadia after trigger: limit-uptime=%s disabled=%s' % (u.get('limit-uptime','?'), u.get('disabled','?')))
else:
print('Nadia: REMOVED from hotspot users!')
# Step 5: Check all Bulanan4 users for expired
print()
print('=== Bulanan4 users after fix ===')
still_active = 0
removed = 0
for u in api.cmd('/ip/hotspot/user/print', **{'?profile': 'Bulanan4'}):
n = u.get('name','')
c = u.get('comment','')
lim = u.get('limit-uptime','')
if c and lim == '1s':
removed += 1
status = 'REMOVED(1s)'
elif c and not lim:
still_active += 1
status = 'ACTIVE'
else:
status = 'NOLIMIT'
print(' %s | comment=%s | %s' % (n, c, status))
print()
print('Still active: %d | Removed(1s): %d' % (still_active, removed))
api.close()
+15
View File
@@ -0,0 +1,15 @@
import sys
for f in ['/tmp/aup.php', '/tmp/upbn.php']:
with open(f, 'r') as fh:
content = fh.read()
original = content
content = content.replace(':pick', '\x00PG\x00')
content = content.replace(':pic', ':pick')
content = content.replace('\x00PG\x00', ':pick')
if content != original:
with open(f, 'w') as fh:
fh.write(content)
n = content.count(':pick') - original.count(':pick')
print('FIXED: ' + f + ' (' + str(n) + ' occurrences)')
else:
print('NOCHANGE: ' + f)
+81
View File
@@ -0,0 +1,81 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(30)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
# Test current DNS
print('=== Current DNS servers: 192.168.53.1 ===')
r = api.cmd('/ip/dns/query', **{'server': '192.168.53.1', 'name': 'msr.s.dimanaaja.biz.id'})
print(' -> %s' % str(r)[:300])
# Try direct public DNS via /tool fetch with IP (skip DNS)
print()
print('=== fetch via public IP of cloudflare tunnel domain? ===')
# Resolve ssh.dimanaaja.biz.id (public tunnel domain) from local machine
import subprocess
out = subprocess.run(['nslookup', 'ssh.dimanaaja.biz.id', '8.8.8.8'], capture_output=True, text=True).stdout
print(out)
api.close()
+130
View File
@@ -0,0 +1,130 @@
import paramiko
import time
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('192.168.100.2', username='mi4', password='m14', look_for_keys=False, allow_agent=False)
def router_cmd(cmd, wait=0.5):
time.sleep(wait)
stdin, stdout, stderr = client.exec_command(cmd)
time.sleep(0.5)
out = stdout.read().decode().strip()
err = stderr.read().decode().strip()
if err: print(f" ERR: {err[:150]}")
return out
def flush_shell(chan):
out = b''
chan.settimeout(1.0)
while True:
try:
chunk = chan.recv(65535)
if not chunk: break
out += chunk
except:
break
return out.decode(errors='replace')
schedulers = ["Bulanan2", "Bulanan4", "vc-2K3J", "vc-5K10J", "Family", "DevB2"]
print("=== Fixing scheduler on-event ===")
for s in schedulers:
print(f"\n--- {s} ---")
raw = router_cmd(f':put [/system scheduler get {s} on-event]')
if not raw:
print(" Empty on-event, skipping")
continue
if ':pic' not in raw:
print(" No :pic found, skipping")
continue
fixed = raw.replace(':pic', ':pick')
count = raw.count(':pic')
print(f" Fixed {count} :pic -> :pick (len={len(fixed)})")
# Escape for CLI: $->\$, "->\", \->\\
esc = fixed.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$')
temp = f"tmp_{s}"
chan = client.invoke_shell()
chan.settimeout(10)
time.sleep(1.5)
flush_shell(chan)
chan.send(f'/system script remove {temp}\n'); time.sleep(1); flush_shell(chan)
chan.send(f'/system script add name={temp} source="{esc}"\n'); time.sleep(2)
out = flush_shell(chan)
if 'failure' in out.lower():
print(f" Script create FAILED: {out[:150]}")
chan.close()
continue
print(" Temp script OK")
chan.send(f'/system scheduler set {s} on-event=[/system script get {temp} source]\n')
time.sleep(2); out = flush_shell(chan)
if 'failure' in out.lower():
print(f" Scheduler set FAILED: {out[:150]}")
else:
print(" Scheduler set OK")
chan.send(f'/system script remove {temp}\n'); time.sleep(1); flush_shell(chan)
chan.close()
print("\n=== Verification (scheduler on-event) ===")
for s in schedulers:
oe = router_cmd(f':put [/system scheduler get {s} on-event]')
hp = ':pic' in oe; hk = ':pick' in oe
print(f" {s}: {'OK' if hk and not hp else 'BROKEN'} (pic={hp} pick={hk})")
profiles = ["Bulanan2", "Bulanan4", "vc-2K3J", "vc-5K10J", "Family", "home", "DevB2"]
print("\n\n=== Fixing profile on-login ===")
for p in profiles:
print(f"\n--- {p} ---")
raw = router_cmd(f':put [/ip hotspot user/profile get {p} on-login]')
if not raw:
print(" Empty on-login, skipping")
continue
if ':pic' not in raw:
print(" No :pic found, skipping")
continue
fixed = raw.replace(':pic', ':pick')
count = raw.count(':pic')
print(f" Fixed {count} :pic -> :pick")
esc = fixed.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$')
temp = f"tmp_{p}"
chan = client.invoke_shell()
chan.settimeout(10)
time.sleep(1.5)
flush_shell(chan)
chan.send(f'/system script remove {temp}\n'); time.sleep(1); flush_shell(chan)
chan.send(f'/system script add name={temp} source="{esc}"\n'); time.sleep(2)
out = flush_shell(chan)
if 'failure' in out.lower():
print(f" Script create FAILED: {out[:150]}")
chan.close()
continue
print(" Temp script OK")
chan.send(f'/ip hotspot user/profile set {p} on-login=[/system script get {temp} source]\n')
time.sleep(2); out = flush_shell(chan)
if 'failure' in out.lower():
print(f" Profile set FAILED: {out[:150]}")
else:
print(" Profile set OK")
chan.send(f'/system script remove {temp}\n'); time.sleep(1); flush_shell(chan)
chan.close()
print("\n=== Profile Verification ===")
for p in profiles:
ol = router_cmd(f':put [/ip hotspot user/profile get {p} on-login]')
hp = ':pic' in ol; hk = ':pick' in ol
print(f" {p}: {'OK' if hk and not hp else 'BROKEN'} (pic={hp} pick={hk})")
client.close()
print("\nALL DONE")
+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")
+128
View File
@@ -0,0 +1,128 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(15)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
# Get current Family on-event
for si in api.cmd('/system/scheduler/print'):
if si['name'] == 'Family':
old_oe = si.get('on-event','')
oid = si.get('.id','')
print('Current on-event length: %d' % len(old_oe))
print()
# The fix: wrap notify block with limit-uptime check
# Old pattern:
# [ /ip hotspot user set limit-uptime=1s $i ];
# [ /ip hotspot active remove [find where user=$name] ];
# :local msg ("User " . $name . " expired");
# /tool fetch url=("http://192.168.100.6/notify-expire.php?key=msr-2026-reset&name=" . $name) http-method=get keep-result=no ;
#
# New pattern:
# :local curlim [ /ip hotspot user get $i limit-uptime ];
# [ /ip hotspot user set limit-uptime=1s $i ];
# [ /ip hotspot active remove [find where user=$name] ];
# :if ($curlim = "") do={
# :local msg ("User " . $name . " expired");
# /tool fetch url=("http://192.168.100.6/notify-expire.php?key=msr-2026-reset&name=" . $name) http-method=get keep-result=no ;
# }
old_block = '[ /ip hotspot user set limit-uptime=1s $i ]; [ /ip hotspot active remove [find where user=$name] ]; :local msg ("User " . $name . " expired"); /tool fetch url=("http://192.168.100.6/notify-expire.php?key=msr-2026-reset&name=" . $name) http-method=get keep-result=no ;'
new_block = ':local curlim [ /ip hotspot user get $i limit-uptime ]; [ /ip hotspot user set limit-uptime=1s $i ]; [ /ip hotspot active remove [find where user=$name] ]; :if ($curlim = "") do={ :local msg ("User " . $name . " expired"); /tool fetch url=("http://192.168.100.6/notify-expire.php?key=msr-2026-reset&name=" . $name) http-method=get keep-result=no ; }'
if old_block in old_oe:
new_oe = old_oe.replace(old_block, new_block)
if new_oe != old_oe:
print('Applying fix...')
api.cmd('/system/scheduler/set', **{'.id': oid, 'on-event': new_oe})
# Verify
for si2 in api.cmd('/system/scheduler/print'):
if si2['name'] == 'Family':
oe2 = si2.get('on-event','')
if ':curlim' in oe2 and 'curlim' in oe2:
print('FIX VERIFIED: dedup check added for limit-uptime')
print('New length: %d' % len(oe2))
else:
print('FIX FAILED')
break
else:
print('ERROR: replacement produced same string')
else:
print('ERROR: old_block not found in on-event')
print()
print('First 100 chars of old_oe:')
print(old_oe[:100])
print()
print('Checking partial match...')
if 'set limit-uptime=1s' in old_oe:
print(' Found: set limit-uptime=1s')
if 'notify-expire.php' in old_oe:
print(' Found: notify-expire.php')
if 'keep-result=no' in old_oe:
print(' Found: keep-result=no')
api.close()
+108
View File
@@ -0,0 +1,108 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(15)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
# Get Family on-event and find exact block
for si in api.cmd('/system/scheduler/print'):
if si['name'] == 'Family':
oe = si.get('on-event','')
oid = si.get('.id','')
# Find the notification block - from "set limit-uptime=1s" to end
idx = oe.find('set limit-uptime=1s')
print('Found block at index: %d' % idx)
print()
print('Block from set limit-uptime:')
block = oe[idx:]
print(block)
print()
print('Block length: %d' % len(block))
# Build new block
new_block = ':local curlim [ /ip hotspot user get $i limit-uptime ]; ' + block
print()
print('New block:')
print(new_block[:200] + '...')
# Replace
new_oe = oe[:idx] + new_block
print()
print('New on-event length: %d (was %d)' % (len(new_oe), len(oe)))
# Apply
print()
print('Applying...')
api.cmd('/system/scheduler/set', **{'.id': oid, 'on-event': new_oe})
# Verify
for si2 in api.cmd('/system/scheduler/print'):
if si2['name'] == 'Family':
oe2 = si2.get('on-event','')
print('Verified: curlim check=%s, length=%d' % ('curlim' in oe2, len(oe2)))
break
break
api.close()
+238
View File
@@ -0,0 +1,238 @@
import socket
import struct
import 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))
# Login
self._write_sentence('/login', f'=name={username}', f'=password={password}')
resp = self._read_response()
if '!trap' in resp:
# Try v6.43+ challenge-response auth
challenge = None
for item in resp:
if isinstance(item, dict) and 'ret' in item:
challenge = item['ret']
if challenge and len(challenge) == 32:
import hashlib
response = '00' + hashlib.md5(b'\x00' + password.encode() + bytes.fromhex(challenge)).hexdigest()
self._write_sentence('/login', f'=name={username}', f'=response={response}')
resp = self._read_response()
if '!trap' in resp:
raise Exception(f"Login failed: {resp}")
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):
data = word.encode('utf-8')
self.sock.sendall(self._encode_length(len(data)) + data)
def _write_sentence(self, cmd, *words):
self._write_word(cmd)
for w in words:
self._write_word(w)
self.sock.sendall(b'\x00') # End-of-sentence
def _read_byte(self):
return self.sock.recv(1)
def _read_word(self):
first = self._read_byte()
if first == b'\x00':
return None
length_byte = first[0]
if length_byte & 0x80:
if (length_byte & 0xC0) == 0x80:
length = ((length_byte & 0x3F) << 8) + self._read_byte()[0]
elif (length_byte & 0xE0) == 0xC0:
length = ((length_byte & 0x1F) << 16) + (self._read_byte()[0] << 8) + self._read_byte()[0]
elif (length_byte & 0xF0) == 0xE0:
b2 = self._read_byte()[0]; b3 = self._read_byte()[0]; b4 = self._read_byte()[0]
length = ((length_byte & 0x0F) << 24) + (b2 << 16) + (b3 << 8) + b4
else:
length = (self._read_byte()[0] << 24) + (self._read_byte()[0] << 16) + (self._read_byte()[0] << 8) + self._read_byte()[0]
else:
length = length_byte
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:
word = self._read_word()
if word is None:
break
words.append(word)
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)
if len(parts) >= 3:
d[parts[1]] = parts[2]
elif len(parts) == 2:
d[parts[0]] = parts[1]
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
# ===== MAIN FIX =====
api = RouterOSApi('192.168.100.2')
api.connect('mi4', 'm14')
schedulers = ["Bulanan2", "Bulanan4", "vc-2K3J", "vc-5K10J", "Family", "DevB2"]
print("=== Fixing scheduler on-event ===")
for s in schedulers:
print(f"\n--- {s} ---")
resp = api.cmd('/system/scheduler/print', **{'?name': s})
if not resp:
print(f" Not found")
continue
oe = resp[0].get('on-event', '')
oid = resp[0].get('.id', '')
if ':pic' not in oe:
print(f" No :pic found, skipping")
continue
fixed = oe.replace(':pic', ':pick')
count = oe.count(':pic')
print(f" Fixed {count} :pic -> :pick (len={len(fixed)})")
# Set on-event
set_resp = api.cmd('/system/scheduler/set', **{'.id': oid, 'on-event': fixed})
# Check for traps
is_trap = any(isinstance(r, tuple) and r[0] == '!trap' for r in set_resp)
if is_trap:
for r in set_resp:
if isinstance(r, tuple) and r[0] == '!trap':
print(f" !trap: {r[1]}")
else:
print(f" Set OK (no traps)")
# Verify
verify = api.cmd('/system/scheduler/print', **{'?name': s})
if verify:
oe2 = verify[0].get('on-event', '')
has_pic = ':pic' in oe2
has_pick = ':pick' in oe2
print(f" Verify: pic={has_pic} pick={has_pick}")
if not has_pic and has_pick:
print(f" FIXED!")
elif has_pic:
print(f" STILL BROKEN")
# Fix profile on-login
profiles = ["Bulanan2", "Bulanan4", "vc-2K3J", "vc-5K10J", "Family", "home", "DevB2"]
print("\n\n=== Fixing profile on-login ===")
for p in profiles:
print(f"\n--- {p} ---")
resp = api.cmd('/ip/hotspot/user/profile/print', **{'?name': p})
if not resp:
print(f" Not found")
continue
ol = resp[0].get('on-login', '')
oid = resp[0].get('.id', '')
if ':pic' not in ol:
print(f" No :pic found, skipping")
continue
fixed = ol.replace(':pic', ':pick')
count = ol.count(':pic')
print(f" Fixed {count} :pic -> :pick")
set_resp = api.cmd('/ip/hotspot/user/profile/set', **{'.id': oid, 'on-login': fixed})
is_trap = any(isinstance(r, tuple) and r[0] == '!trap' for r in set_resp)
if is_trap:
for r in set_resp:
if isinstance(r, tuple) and r[0] == '!trap':
print(f" !trap: {r[1]}")
else:
print(f" Set OK")
verify = api.cmd('/ip/hotspot/user/profile/print', **{'?name': p})
if verify:
ol2 = verify[0].get('on-login', '')
hp = ':pic' in ol2; hk = ':pick' in ol2
print(f" Verify: pic={hp} pick={hk}")
print(f" {'FIXED!' if not hp and hk else 'STILL BROKEN'}")
api.close()
print("\nALL DONE")
+107
View File
@@ -0,0 +1,107 @@
import paramiko
import re
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('192.168.100.2', username='mi4', password='m14', look_for_keys=False, allow_agent=False)
chan = client.invoke_shell()
chan.settimeout(30)
def send_cmd(cmd, wait=2):
import time
chan.send(cmd + '\n')
time.sleep(wait)
output = b''
while chan.recv_ready():
output += chan.recv(65535)
return output.decode('utf-8', errors='replace')
# Get current scheduler on-event scripts
schedulers = ["Bulanan2", "Bulanan4", "vc-2K3J", "vc-5K10J"]
for s in schedulers:
print(f"\n=== Fixing scheduler: {s} ===")
# Get current on-event
stdin, stdout, stderr = client.exec_command(f'/system scheduler get {s} on-event')
on_event = stdout.read().decode().strip()
err = stderr.read().decode().strip()
if err:
print(f" ERROR reading {s}: {err}")
continue
# Fix :pic to :pick
fixed = on_event.replace(':pic', ':pick')
if fixed == on_event:
print(f" No :pic found - skipping")
continue
# How many :pic -> :pick replacements?
count = on_event.count(':pic')
print(f" Fixed {count} occurrences of :pic")
# Set via API - need careful quoting
# Use a temporary script approach to avoid quoting hell
stdin, stdout, stderr = client.exec_command(f'/system scheduler set {s} disabled=no on-event="{fixed}"')
result = stdout.read().decode().strip()
err = stderr.read().decode().strip()
if result: print(f" Result: {result}")
if err: print(f" ERROR: {err}")
# Verify
stdin, stdout, stderr = client.exec_command(f'/system scheduler get {s} on-event')
verify = stdout.read().decode().strip()
verr = stderr.read().decode().strip()
if verr:
print(f" Verify error: {verr}")
elif ':pic' in verify:
print(f" WARNING: :pic still present!")
elif ':pick' in verify:
print(f" VERIFIED: :pick present, script fixed!")
else:
print(f" UNEXPECTED: neither :pic nor :pick found")
print(f" First 100 chars: {verify[:100]}")
# Now fix the on-login scripts in the hotspot profiles
print(f"\n\n=== Fixing hotspot profile on-login scripts ===")
profiles_to_fix = ["Bulanan2", "Bulanan4", "vc-2K3J", "vc-5K10J", "Family", "DevB2"]
for p in profiles_to_fix:
print(f"\n--- Profile: {p} ---")
stdin, stdout, stderr = client.exec_command(f'/ip hotspot user/profile get {p} on-login')
on_login = stdout.read().decode().strip()
err = stderr.read().decode().strip()
if err:
print(f" ERROR reading: {err}")
continue
if ':pic' not in on_login:
print(f" No :pic found (already fixed)")
continue
count = on_login.count(':pic')
fixed = on_login.replace(':pic', ':pick')
print(f" Fixed {count} occurrences")
# It might be easier to just use the set command
import time
chan.send(f'/ip hotspot user/profile set {p} on-login="{fixed}"\n')
time.sleep(1)
while chan.recv_ready():
time.sleep(0.5)
# Verify
stdin, stdout, stderr = client.exec_command(f'/ip hotspot user/profile get {p} on-login')
verify = stdout.read().decode().strip()
verr = stderr.read().decode().strip()
if verr:
print(f" Verify error: {verr}")
elif ':pic' in verify:
print(f" WARNING: :pic still present in on-login!")
elif ':pick' in verify:
print(f" VERIFIED: on-login fixed!")
else:
print(f" First 100: {verify[:100]}")
client.close()
print("\n\nDONE")
+41
View File
@@ -0,0 +1,41 @@
import librouteros
api = librouteros.connect('192.168.100.2', 'mi4', 'm14', port=8728)
print("=== Fixing scheduler on-event scripts ===")
for item in list(api('/system/scheduler/print')):
name = item['name']
on_event = item.get('on-event', '')
if ':pic' not in on_event:
continue
fixed = on_event.replace(':pic', ':pick')
count = on_event.count(':pic')
api('/system/scheduler/set', **{'.id': item['.id'], 'on-event': fixed})
print(f" {name}: fixed {count} :pic -> :pick")
print("\n=== Fixing hotspot profile on-login scripts ===")
for item in list(api('/ip/hotspot/user/profile/print')):
name = item['name']
on_login = item.get('on-login', '')
if ':pic' not in on_login:
continue
fixed = on_login.replace(':pic', ':pick')
count = on_login.count(':pic')
api('/ip/hotspot/user/profile/set', **{'.id': item['.id'], 'on-login': fixed})
print(f" {name}: fixed {count} :pic -> :pick")
print("\n=== Verification ===")
still_broken = False
for item in list(api('/system/scheduler/print')):
if ':pic' in item.get('on-event', ''):
print(f" BROKEN: {item['name']} scheduler still has :pic!")
still_broken = True
for item in list(api('/ip/hotspot/user/profile/print')):
if ':pic' in item.get('on-login', ''):
print(f" BROKEN: {item['name']} profile on-login still has :pic!")
still_broken = True
if not still_broken:
print(" ALL FIXED - no :pic remaining")
api.close()
print("\nDONE")
+77
View File
@@ -0,0 +1,77 @@
import librouteros
api = librouteros.connect('192.168.100.2', 'mi4', 'm14', port=8728)
schedulers = ["Bulanan2", "Bulanan4", "vc-2K3J", "vc-5K10J", "Family", "DevB2"]
for name in schedulers:
items = list(api('/system/scheduler/print', **{'?name': name}))
if not items:
print(f"{name}: not found")
continue
item = items[0]
oe = item['on-event']
if ':pic' not in oe:
print(f"{name}: already fixed")
continue
fixed = oe.replace(':pic', ':pick')
count = oe.count(':pic')
# Try set with kwargs (word=value syntax)
try:
api('/system/scheduler/set', '.id=' + item['.id'], 'on-event=' + fixed)
print(f"{name}: set OK (fixed {count})")
except Exception as e:
print(f"{name}: ERROR - {e}")
# Try alt approach: set disabled and on-event separately
try:
api('/system/scheduler/set', '.id=' + item['.id'], 'on-event=' + fixed)
except Exception as e2:
print(f"{name}: alt also failed - {e2}")
# Verify
print("\n=== Verification ===")
all_ok = True
for item in list(api('/system/scheduler/print')):
name = item['name']
oe = item.get('on-event', '')
if ':pic' in oe:
print(f" STILL BROKEN: {name}")
all_ok = False
else:
print(f" OK: {name}")
if all_ok:
print(" ALL SCHEDULERS FIXED!")
# Fix profile on-login
print("\n=== Fixing profile on-login ===")
for item in list(api('/ip/hotspot/user/profile/print')):
name = item['name']
ol = item.get('on-login', '')
if ':pic' not in ol:
print(f"{name}: already fixed")
continue
fixed = ol.replace(':pic', ':pick')
count = ol.count(':pic')
try:
api('/ip/hotspot/user/profile/set', '.id=' + item['.id'], 'on-login=' + fixed)
print(f"{name}: fixed {count}")
except Exception as e:
print(f"{name}: ERROR - {e}")
# Verify profiles
print("\n=== Profile Verification ===")
all_ok = True
for item in list(api('/ip/hotspot/user/profile/print')):
name = item['name']
ol = item.get('on-login', '')
if ':pic' in ol:
print(f" STILL BROKEN: {name} (on-login)")
all_ok = False
if all_ok:
print(" ALL PROFILES FIXED!")
api.close()
+72
View File
@@ -0,0 +1,72 @@
import librouteros
api = librouteros.connect('192.168.100.2', 'mi4', 'm14', port=8728)
scheduler_names = ["Bulanan2", "Bulanan4", "vc-2K3J", "vc-5K10J", "Family", "DevB2"]
print("=== Fixing scheduler on-event ===")
all_schedulers = list(api('/system/scheduler/print'))
for item in all_schedulers:
name = item['name']
if name not in scheduler_names:
continue
oe = item['on-event']
if ':pic' not in oe:
print(f"{name}: no :pic found (already fixed)")
continue
fixed = oe.replace(':pic', ':pick')
count = oe.count(':pic')
try:
api('/system/scheduler/set', '.id=' + item['.id'], 'on-event=' + fixed)
print(f"{name}: fixed {count} occurrences")
except Exception as e:
print(f"{name}: ERROR - {e}")
# Verify
print("\n=== Verification ===")
for item in list(api('/system/scheduler/print')):
name = item['name']
oe = item.get('on-event', '')
has_pic = ':pic' in oe
has_pick = ':pick' in oe
status = 'FIXED' if has_pick and not has_pic else ('BROKEN' if has_pic else 'NONE')
print(f" {name}: {status} (pic={has_pic} pick={has_pick})")
# Fix profile on-login
print("\n=== Fixing profile on-login ===")
profile_names = ["Bulanan2", "Bulanan4", "vc-2K3J", "vc-5K10J", "Family", "home", "DevB2"]
for item in list(api('/ip/hotspot/user/profile/print')):
name = item['name']
if name not in profile_names:
continue
ol = item.get('on-login', '')
if ':pic' not in ol:
print(f"{name}: no :pic found (already fixed)")
continue
fixed = ol.replace(':pic', ':pick')
count = ol.count(':pic')
try:
api('/ip/hotspot/user/profile/set', '.id=' + item['.id'], 'on-login=' + fixed)
print(f"{name}: fixed {count} occurrences")
except Exception as e:
print(f"{name}: ERROR - {e}")
# Verify profiles
print("\n=== Profile Verification ===")
for item in list(api('/ip/hotspot/user/profile/print')):
name = item['name']
ol = item.get('on-login', '')
has_pic = ':pic' in ol
has_pick = ':pick' in ol
status = 'FIXED' if has_pick and not has_pic else ('BROKEN' if has_pic else 'NONE')
print(f" {name}: {status}")
api.close()
print("\nDONE")
+69
View File
@@ -0,0 +1,69 @@
import librouteros
api = librouteros.connect('192.168.100.2', 'mi4', 'm14', port=8728)
fix_schedulers = ["Bulanan2", "Bulanan4", "vc-2K3J", "vc-5K10J", "Family", "DevB2"]
print("=== Fixing scheduler on-event ===")
for item in list(api('/system/scheduler/print')):
name = item['name']
if name not in fix_schedulers:
continue
oe = item['on-event']
if ':pic' not in oe:
print(f"{name}: no :pic found, skipping")
continue
fixed = oe.replace(':pic', ':pick')
count = oe.count(':pic')
api('/system/scheduler/set', **{'.id': item['.id'], 'on-event': fixed})
print(f"{name}: fixed {count} :pic -> :pick")
print("\n=== Verifying scheduler on-event ===")
for item in list(api('/system/scheduler/print')):
name = item['name']
if name not in fix_schedulers:
continue
oe = item.get('on-event', '')
has_pic = ':pic' in oe
has_pick = ':pick' in oe
status = 'OK' if has_pick and not has_pic else ('BROKEN' if has_pic else 'unknown')
print(f" {name}: {status}")
# Restore Bulanan2 comment from TEST_FIX_123
api('/system/scheduler/set', **{'.id': '*1', 'comment': 'Monitor Profile Bulanan2'})
print("\n=== Fixing profile on-login ===")
fix_profiles = ["Bulanan2", "Bulanan4", "vc-2K3J", "vc-5K10J", "Family", "home", "DevB2"]
for item in list(api('/ip/hotspot/user/profile/print')):
name = item['name']
if name not in fix_profiles:
continue
ol = item.get('on-login', '')
if ':pic' not in ol:
print(f"{name}: no :pic found, skipping")
continue
fixed = ol.replace(':pic', ':pick')
count = ol.count(':pic')
api('/ip/hotspot/user/profile/set', **{'.id': item['.id'], 'on-login': fixed})
print(f"{name}: fixed {count} :pic -> :pick")
print("\n=== Verifying profile on-login ===")
for item in list(api('/ip/hotspot/user/profile/print')):
name = item['name']
if name not in fix_profiles:
continue
ol = item.get('on-login', '')
has_pic = ':pic' in ol
has_pick = ':pick' in ol
status = 'OK' if has_pick and not has_pic else ('BROKEN' if has_pic else 'unknown')
print(f" {name}: {status}")
api.close()
print("\nALL DONE")
+59
View File
@@ -0,0 +1,59 @@
import paramiko
import time
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('192.168.100.2', username='mi4', password='m14', look_for_keys=False, allow_agent=False)
def run(cmd, wait=1.0):
time.sleep(wait)
stdin, stdout, stderr = client.exec_command(cmd)
time.sleep(1.0)
out = stdout.read().decode().strip()
err = stderr.read().decode().strip()
if err:
print(f" ERR: {err[:200]}")
return out
def esc_for_routeros(s):
return s.replace('"', '\\"').replace('$', '\\$').replace('\\', '\\\\')
# Test: simple script creation via exec_command
print("=== Test: script create via exec_command ===")
test_code = ':put "test123"'
esc_test = esc_for_routeros(test_code)
print(f"Escaped: {esc_test}")
result = run(f'/system script add name=test_esc source="{esc_test}"')
print(f"Create result: '{result}'")
result = run(':put [/system script get test_esc source]')
print(f"Stored source: '{result}'")
run('/system script remove test_esc')
print()
if 'test123' not in result:
print("Script creation NOT working! Debugging further...")
# Try a simpler approach: no special chars at all
result2 = run('/system script add name=test2 source=":put hello"')
print(f"Simple create: '{result2}'")
result2 = run(':put [/system script get test2 source]')
print(f"Simple source: '{result2}'")
run('/system script remove test2')
# Try without quotes
result3 = run('/system script add name=test3 source="hello"')
print(f"Min create: '{result3}'")
result3 = run(':put [/system script get test3 source]')
print(f"Min source: '{result3}'")
run('/system script remove test3')
# Try with just a dollar sign
result4 = run('/system script add name=test4 source=":put \\$x"')
print(f"$ create: '{result4}'")
result4 = run(':put [/system script get test4 source]')
print(f"$ source: '{result4}'")
run('/system script remove test4')
client.close()
+131
View File
@@ -0,0 +1,131 @@
import paramiko
import time
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('192.168.100.2', username='mi4', password='m14', look_for_keys=False, allow_agent=False)
# Get current scheduler on-event scripts
schedulers = ["Bulanan2", "Bulanan4", "vc-2K3J", "vc-5K10J", "Family", "DevB2"]
# Use shell for multi-step operations
chan = client.invoke_shell()
chan.settimeout(15)
time.sleep(2)
def read_output():
out = b''
while chan.recv_ready():
out += chan.recv(65535)
return out.decode('utf-8', errors='replace')
# Clear initial banner
banner = read_output()
for s in schedulers:
print(f"\n=== Fixing {s} ===")
# Step 1: Get current on-event via exec_command
stdin, stdout, stderr = client.exec_command(f'/system scheduler get {s} on-event')
on_event = stdout.read().decode().strip()
err = stderr.read().decode().strip()
if err:
print(f" Error reading on-event: {err}")
continue
if ':pic' not in on_event:
print(f" No :pic found, skipping")
continue
fixed = on_event.replace(':pic', ':pick')
count = on_event.count(':pic')
# Step 2: Create temp script with fixed content
# Escape the content for RouterOS CLI: replace " with \", $ with \$
escaped = fixed.replace('"', '\\"').replace('$', '\\$')
temp_name = f"fix_{s}"
cmd = f'/system script add name={temp_name} source="{escaped}"'
chan.send(cmd + '\n')
time.sleep(1.5)
out = read_output()
if 'failure' in out.lower():
# Maybe already exists, try remove first
chan.send(f'/system script remove {temp_name}\n')
time.sleep(1)
read_output()
chan.send(cmd + '\n')
time.sleep(1.5)
out = read_output()
print(f" Temp script created: {out[:100]}")
# Step 3: Set scheduler on-event to use temp script source
cmd = f'/system scheduler set {s} on-event=[/system script get {temp_name} source] disabled=no'
chan.send(cmd + '\n')
time.sleep(1.5)
out = read_output()
print(f" Scheduler set: {out[:100]}")
# Step 4: Remove temp script
chan.send(f'/system script remove {temp_name}\n')
time.sleep(1)
read_output()
print("\n=== Verification ===")
for s in schedulers:
stdin, stdout, stderr = client.exec_command(f'/system scheduler get {s} on-event')
oe = stdout.read().decode().strip()
has_pic = ':pic' in oe
has_pick = ':pick' in oe
status = 'OK' if has_pick and not has_pic else 'BROKEN'
print(f" {s}: {status} (pic={has_pic} pick={has_pick})")
# Also fix profile on-login via same approach
print("\n=== Fixing profile on-login ===")
profiles = ["Bulanan2", "Bulanan4", "vc-2K3J", "vc-5K10J", "Family", "home", "DevB2"]
for p in profiles:
print(f"\n--- {p} ---")
stdin, stdout, stderr = client.exec_command(f'/ip hotspot user/profile get {p} on-login')
ol = stdout.read().decode().strip()
err = stderr.read().decode().strip()
if err:
print(f" Error: {err}")
continue
if ':pic' not in ol:
print(f" No :pic found, skipping")
continue
fixed = ol.replace(':pic', ':pick')
count = ol.count(':pic')
escaped = fixed.replace('"', '\\"').replace('$', '\\$')
temp_name = f"fix_{p}_login"
chan.send(f'/system script add name={temp_name} source="{escaped}"\n')
time.sleep(1.5)
out = read_output()
print(f" Temp script created: {out[:80]}")
cmd = f'/ip hotspot user/profile set {p} on-login=[/system script get {temp_name} source]'
chan.send(cmd + '\n')
time.sleep(1.5)
out = read_output()
print(f" Profile set: {out[:80]}")
chan.send(f'/system script remove {temp_name}\n')
time.sleep(1)
read_output()
print("\n=== Profile Verification ===")
for p in profiles:
stdin, stdout, stderr = client.exec_command(f'/ip hotspot user/profile get {p} on-login')
ol = stdout.read().decode().strip()
has_pic = ':pic' in ol
has_pick = ':pick' in ol
status = 'OK' if has_pick and not has_pic else 'BROKEN'
print(f" {p}: {status}")
client.close()
print("\nALL DONE")
+121
View File
@@ -0,0 +1,121 @@
import socket, hashlib, time
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(15)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
# Try running scheduler with correct API syntax
print('Method 1: system scheduler run with name query...')
r = api.cmd('/system/scheduler/run', **{'?name': 'Bulanan4'})
print(' Result: %s' % r)
time.sleep(2)
# Check Nadia
users = api.cmd('/ip/hotspot/user/print', **{'?name': 'Nadia'})
if users:
u = users[0]
print('Nadia after run: limit-uptime=%s' % u.get('limit-uptime','?'))
else:
print('Nadia: REMOVED!')
# Method 2: Direct set limit-uptime=1s
print()
print('Method 2: Direct set limit-uptime=1s for expired users...')
now = 'jul/24/2026'
expired = []
for u in api.cmd('/ip/hotspot/user/print', **{'?profile': 'Bulanan4'}):
c = u.get('comment','')
n = u.get('name','')
lim = u.get('limit-uptime','')
if c and not lim and '/' in c:
# Parse date
parts = c.split(' ')[0] # "jul/20/2026"
mon, day, year = parts.split('/')
months = {'jan':1,'feb':2,'mar':3,'apr':4,'may':5,'jun':6,'jul':7,'aug':8,'sep':9,'oct':10,'nov':11,'dec':12}
exp_day = int(day)
exp_mon = months.get(mon, 0)
exp_year = int(year)
cur_day = 24
cur_mon = 7
cur_year = 2026
exp_num = exp_year*10000 + exp_mon*100 + exp_day
cur_num = cur_year*10000 + cur_mon*100 + cur_day
if exp_num < cur_num:
expired.append(u['.id'])
print(' %s expires %s -> SET limit-uptime=1s' % (n, c))
if expired:
api.cmd('/ip/hotspot/user/set', **{'numbers': ','.join(expired), 'limit-uptime': '1s'})
print()
print('Verifying...')
for u in api.cmd('/ip/hotspot/user/print', **{'?profile': 'Bulanan4'}):
n = u.get('name','')
lim = u.get('limit-uptime','')
if lim:
print(' %s -> limit-uptime=%s' % (n, lim))
else:
print(' No expired users found')
api.close()
+11
View File
@@ -0,0 +1,11 @@
#!/bin/sh
echo "=== php ini error ==="
grep -E "error_log|display_errors" /etc/php81/php.ini 2>/dev/null | head
echo "=== fpm pool ==="
grep -E "error_log|log_level|catch_workers|php_admin" /etc/php81/php-fpm.d/www.conf 2>/dev/null | head -20
echo "=== fpm master conf ==="
grep -E "error_log|log_level" /etc/php81/php-fpm.conf 2>/dev/null
echo "=== write test to /tmp ==="
php -r 'file_put_contents("/tmp/phpcheck.txt", "from-cli"); echo "written\n";'
ls -la /var/log/php81/ 2>/dev/null
ls -la /tmp/phpcheck.txt 2>/dev/null
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
php -r 'var_dump(function_exists("curl_init"));'
echo "=== install ==="
apk add --no-cache php81-curl 2>&1 | tail -6
echo "=== recheck ==="
php -r 'var_dump(function_exists("curl_init"));'
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
apk add --no-cache php81-curl 2>&1 | tail -6
echo "=== recheck ==="
php -r 'var_dump(function_exists("curl_init"));'
+81
View File
@@ -0,0 +1,81 @@
import socket, hashlib, urllib.parse
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(30)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
token='5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk'
chat='-1002554941429'
thread='42'
text = 'TEST notif scheduler router: user TESTNAME expired'
url = 'https://api.telegram.org/bot%s/sendMessage?chat_id=%s&message_thread_id=%s&text=%s' % (token, chat, thread, urllib.parse.quote_plus(text))
print('URL:', url[:100], '...')
r = api.cmd('/tool/fetch', url=url, mode='https', **{'http-method': 'get', 'keep-result': 'no'})
for x in r:
if isinstance(x, dict): print(' ', x.get('status'), x.get('downloaded'), x.get('duration'))
else: print(' ', x)
print('trap?', any(isinstance(x,tuple) for x in r))
api.close()
+85
View File
@@ -0,0 +1,85 @@
import socket, hashlib, urllib.parse
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(30)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
token='5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk'
chat='-1002554941429'
thread='42'
text = 'TEST2 from router'
url = 'https://api.telegram.org/bot%s/sendMessage?chat_id=%s&message_thread_id=%s&text=%s' % (token, chat, thread, urllib.parse.quote_plus(text))
r = api.cmd('/tool/fetch', url=url, mode='https', **{'http-method': 'get', 'keep-result': 'yes'})
for x in r:
if isinstance(x, dict): print(' ', x.get('status'), '| downloaded:', x.get('downloaded'))
else: print(' ', x)
# baca file hasil default (biasanya 'fetch')
r = api.cmd('/file/print')
for x in r:
if isinstance(x,dict) and ('fetch' in x.get('name','') or x.get('type')=='file'):
print(' file:', x.get('name'))
api.close()
+73
View File
@@ -0,0 +1,73 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(60)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
files = api.cmd('/file/print')
for f in files:
if isinstance(f,dict):
n = f.get('name','')
if 'send' in n.lower() or n.endswith('.txt') or 'fetch' in n.lower():
print('FILE:', n, f.get('size'))
api.close()
+78
View File
@@ -0,0 +1,78 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(30)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
r = api.cmd('/system/scheduler/print')
for x in r:
if isinstance(x,dict):
print('---')
print('id:', x.get('.id'), '| name:', x.get('name'))
print('interval:', x.get('interval'), '| start-time:', x.get('start-time'), '| policy:', x.get('policy'))
oe = x.get('on-event','')
print('on-event len:', len(oe))
print('on-event:', oe)
api.close()
+76
View File
@@ -0,0 +1,76 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(60)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
name='TMR-1'
# buat user test: limit-uptime 10s, profile hotspot
print('add:', api.cmd('/ip/hotspot/user/add', **{'name': name, 'password': 'test123', 'limit-uptime': '10s', 'profile': 'default'}))
# verifikasi
r = api.cmd('/ip/hotspot/user/print', **{'?name': name})
for x in r:
if isinstance(x,dict):
print('USER:', x.get('name'), 'limit-uptime=', x.get('limit-uptime'), 'id=', x.get('.id'))
api.close()
+84
View File
@@ -0,0 +1,84 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(30)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
OLD = '/tool fetch url=("http://192.168.100.6/notify-expire.php?key=msr-2026-reset&name=" . $name) http-method=get keep-result=no'
NEW = '/tool fetch url=("https://api.telegram.org/bot5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk/sendMessage?chat_id=-1002554941429&message_thread_id=42&text=" . $name . "+expired") mode=https http-method=get keep-result=no'
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
for name in ['Family', 'Bulanan2', 'Bulanan4']:
r = api.cmd('/system/scheduler/print', **{'?name': name})
for x in r:
if isinstance(x,dict):
oe = x.get('on-event','')
if OLD in oe:
new = oe.replace(OLD, NEW)
res = api.cmd('/system/scheduler/set', **{'.id': x.get('.id'), 'on-event': new})
ok = not any(isinstance(t,tuple) and t[0]=='!trap' for t in res)
print('%s patched %s (len %d -> %d)' % (name, 'OK' if ok else 'FAIL '+str(res), len(oe), len(new)))
else:
print('%s: OLD pattern NOT found!' % name)
api.close()
+100
View File
@@ -0,0 +1,100 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(60)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
# OLD fetch (tanpa tombol) - persis dari on-event
OLD = ('/tool fetch url=("https://api.telegram.org/bot5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk/'
'sendMessage?chat_id=-1002554941429&message_thread_id=42&text=" . $name . "+expired") '
'mode=https http-method=get keep-result=no')
# NEW fetch + reply_markup (inline keyboard, tombol Reset, callback_data=reset_$name)
# JSON: {"inline_keyboard":[[{"text":"🔄 Reset","callback_data":"reset_<name>"}]]}
# URL-encoded:
MARKUP_PREFIX = '%7B%22inline_keyboard%22%3A%5B%5B%7B%22text%22%3A%22%F0%9F%94%84%20Reset%22%2C%22callback_data%22%3A%22reset_'
MARKUP_SUFFIX = '%22%7D%5D%5D%7D'
NEW = ('/tool fetch url=("https://api.telegram.org/bot5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk/'
'sendMessage?chat_id=-1002554941429&message_thread_id=42&text=" . $name . "+expired'
'&reply_markup=" . "' + MARKUP_PREFIX + '" . $name . "' + MARKUP_SUFFIX + '") '
'mode=https http-method=get keep-result=no')
print('NEW snippet tail:', NEW[-120:])
print()
for name in ['Family', 'Bulanan2', 'Bulanan4']:
r = api.cmd('/system/scheduler/print', **{'?name': name})
for x in r:
if isinstance(x,dict):
oe = x.get('on-event','')
if OLD in oe:
new = oe.replace(OLD, NEW)
res = api.cmd('/system/scheduler/set', **{'.id': x.get('.id'), 'on-event': new})
ok = not any(isinstance(t,tuple) and t[0]=='!trap' for t in res)
print('%s patched %s (len %d -> %d)' % (name, 'OK' if ok else 'FAIL '+str(res), len(oe), len(new)))
else:
print('%s: OLD pattern NOT found!' % name)
api.close()
+86
View File
@@ -0,0 +1,86 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(30)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
# Ambil Family, patch, dan capture response set
r = api.cmd('/system/scheduler/print', **{'?name': 'Family'})
x = [x for x in r if isinstance(x,dict)][0]
oe = x.get('on-event','')
OLD = '/tool fetch url=("http://192.168.100.6/notify-expire.php?key=msr-2026-reset&name=" . $name) http-method=get keep-result=no'
NEW = '/tool fetch url=("https://api.telegram.org/bot5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk/sendMessage?chat_id=-1002554941429&message_thread_id=42&text=" . $name . "+expired") mode=https http-method=get keep-result=no'
new = oe.replace(OLD, NEW)
print('old in oe:', OLD in oe)
res = api.cmd('/system/scheduler/set', **{'=.id': x.get('.id'), 'on-event': new})
print('set response:', res)
# baca lagi
r2 = api.cmd('/system/scheduler/print', **{'?name': 'Family'})
for y in r2:
if isinstance(y,dict):
oe2 = y.get('on-event','')
print('after: len', len(oe2), 'telegram.org:', 'api.telegram.org' in oe2, 'old100.6:', '192.168.100.6' in oe2)
api.close()
+81
View File
@@ -0,0 +1,81 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(30)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
# test set sederhana
r = api.cmd('/system/scheduler/print', **{'?name': 'Family'})
x = [x for x in r if isinstance(x,dict)][0]
print('id:', x.get('.id'))
# 1. coba set dengan nilai simple
res = api.cmd('/system/scheduler/set', **{'=.id': x.get('.id'), 'on-event': ':put "hello"'})
print('test1 (simple):', res)
# 2. coba cek apakah on-event adalah param yang benar - coba hanya ganti interval
res2 = api.cmd('/system/scheduler/set', **{'=.id': x.get('.id'), 'interval': '2m37s'})
print('test2 (interval):', res2)
api.close()
+75
View File
@@ -0,0 +1,75 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(30)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
# coba set dengan numbers
res = api.cmd('/system/scheduler/set', **{'=numbers': 'Family', 'interval': '2m37s'})
print('test numbers=name:', res)
res2 = api.cmd('/system/scheduler/set', **{'=numbers': '5', 'interval': '2m37s'})
print('test numbers=5:', res2)
api.close()
+81
View File
@@ -0,0 +1,81 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(30)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
# test set pada user hotspot (known working?) - cek apakah cmd helper OK
r = api.cmd('/ip/hotspot/user/print', **{'?name': 'Nadia'})
print('Nadia found:', bool([x for x in r if isinstance(x,dict)]))
res = api.cmd('/ip/hotspot/user/set', **{'=numbers': 'Nadia', 'comment': 'test-set'})
print('user set numbers=name:', res)
# revert
res2 = api.cmd('/ip/hotspot/user/set', **{'=numbers': 'Nadia', 'comment': 'aug/20/2026 23:59:59'})
print('user set revert:', res2)
# sekarang coba scheduler dengan =.id
res3 = api.cmd('/system/scheduler/set', **{'=.id': '*5', 'interval': '2m37s'})
print('sched set .id:', res3)
api.close()
+81
View File
@@ -0,0 +1,81 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(30)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
# print Family scheduler dulu utk dapat .id asli
r = api.cmd('/system/scheduler/print', **{'?name': 'Family'})
x = [x for x in r if isinstance(x,dict)][0]
print('id:', x.get('.id'))
# test set dengan kunci TANPA '='
res = api.cmd('/system/scheduler/set', **{'numbers': x.get('.id'), 'interval': '2m37s'})
print('sched set numbers(no=):', res)
# test lain: gunakan =.id dengan key '.id'
res2 = api.cmd('/system/scheduler/set', **{'.id': x.get('.id'), 'interval': '2m37s'})
print('sched set .id(no=):', res2)
api.close()
+2
View File
@@ -0,0 +1,2 @@
<?php
echo "PHPOK " . PHP_VERSION;
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
# Poller untuk tombol "Reset" di pesan expired hotspot (msr)
# Dijalankan setiap menit dari cron host. Menjamin container hidup
# (sablier bisa mematikannya setelah 5m idle) lalu poll callback query.
CB_KEY="msr-2026-reset"
URL="http://172.18.0.15/telegram-cb.php?key=${CB_KEY}"
docker start mikhmon >/dev/null 2>&1
sleep 1
curl -s -m 30 "$URL" >/dev/null 2>&1
+96
View File
@@ -0,0 +1,96 @@
import socket, hashlib
class R:
def __init__(self,h,p=8728): self.h=h; self.p=p; self.s=None
def c(self,u,p):
self.s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); self.s.settimeout(10)
self.s.connect((self.h,self.p))
self._ws('/login','=name='+u,'=password='+p)
r=self._rr()
if any(isinstance(x,tuple) and x[0]=='!trap' for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
import hashlib as h
resp='00'+h.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
self._ws('/login','=name='+u,'=response=00'+resp)
self._rr()
return self
def _el(self,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
elif l<0x200000: l|=0xC00000; return bytes([(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
elif l<0x10000000: l|=0xE0000000; return bytes([(l>>24)&0xFF,(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
else: return bytes([0xF0,(l>>24)&0xFF,(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(self,w): self.s.sendall(self._el(len(w))+w.encode('utf-8'))
def _ws(self,c,*ws): self._ww(c); [self._ww(w) for w in ws]; self.s.sendall(b'\x00')
def _rb(self): return self.s.recv(1)
def _rw(self):
f=self._rb()
if f==b'\x00': return None
lb=f[0]
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+self._rb()[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(self._rb()[0]<<8)+self._rb()[0]
elif (lb&0xF0)==0xE0: l=((lb&0x0F)<<24)+(self._rb()[0]<<16)+(self._rb()[0]<<8)+self._rb()[0]
else: l=(self._rb()[0]<<24)+(self._rb()[0]<<16)+(self._rb()[0]<<8)+self._rb()[0]
else: l=lb
d=b''
while len(d)<l:
c=self.s.recv(l-len(d))
if not c: break
d+=c
return d
def _rss(self):
ws=[]
while True:
w=self._rw()
if w is None: break
ws.append(w)
return ws
def _rr(self):
rs=[]
while True:
s=self._rss()
if not s: break
r=s[0]; t=s[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(self,c,**a):
ws=[c]
for k,v in a.items():
if k.startswith('?'): ws.append(f'{k}={v}')
else: ws.append(f'={k}={v}')
self._ws(*ws)
return self._rr()
def close(self):
if self.s: self.s.close(); self.s=None
api=R('192.168.100.2').c('mi4','m14')
# Dump on-event Bulanan2
for s in api.cmd('/system/scheduler/print'):
if s['name'] == 'Bulanan2':
on_event = s.get('on-event', '')
print(f"=== Bulanan2 on-event ({len(on_event)} bytes) ===")
print(on_event)
break
# Dump on-login profile Bulanan2
for p in api.cmd('/ip/hotspot/profile/print'):
if p['name'] == 'Bulanan2':
on_login = p.get('on-login', '')
print(f"\n=== Bulanan2 on-login ({len(on_login)} bytes) ===")
print(on_login)
break
api.close()
+118
View File
@@ -0,0 +1,118 @@
import socket, hashlib
class R:
def __init__(self,h,p=8728): self.h=h; self.p=p; self.s=None
def c(self,u,p):
self.s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); self.s.settimeout(10)
self.s.connect((self.h,self.p))
self._ws('/login','=name='+u,'=password='+p)
r=self._rr()
if any(isinstance(x,tuple) and x[0]=='!trap' for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
h=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
self._ws('/login','=name='+u,'=response=00'+h)
self._rr()
return self
def _el(self,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
elif l<0x200000: l|=0xC00000; return bytes([(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
elif l<0x10000000: l|=0xE0000000; return bytes([(l>>24)&0xFF,(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
else: return bytes([0xF0,(l>>24)&0xFF,(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(self,w): self.s.sendall(self._el(len(w))+w.encode('utf-8'))
def _ws(self,c,*ws): self._ww(c); [self._ww(w) for w in ws]; self.s.sendall(b'\x00')
def _rb(self): return self.s.recv(1)
def _rw(self):
f=self._rb()
if f==b'\x00': return None
lb=f[0]
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+self._rb()[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(self._rb()[0]<<8)+self._rb()[0]
elif (lb&0xF0)==0xE0: l=((lb&0x0F)<<24)+(self._rb()[0]<<16)+(self._rb()[0]<<8)+self._rb()[0]
else: l=(self._rb()[0]<<24)+(self._rb()[0]<<16)+(self._rb()[0]<<8)+self._rb()[0]
else: l=lb
d=b''
while len(d)<l:
c=self.s.recv(l-len(d))
if not c: break
d+=c
return d.decode('utf-8',errors='replace')
def _rss(self):
ws=[]
while True:
w=self._rw()
if w is None: break
ws.append(w)
return ws
def _rr(self):
rs=[]
while True:
s=self._rss()
if not s: break
r=s[0]; t=s[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap':
print(f'TRAP: {t}')
rs.append(('!trap',t))
return rs
def cmd(self,c,**a):
ws=[c]
for k,v in a.items():
if k.startswith('?'): ws.append(f'{k}={v}')
else: ws.append(f'={k}={v}')
self._ws(*ws)
return self._rr()
def close(self):
if self.s: self.s.close(); self.s=None
api=R('192.168.100.2').c('mi4','m14')
# Try set with type=regular (RouterOS keyword)
for b in api.cmd('/ip/hotspot/ip-binding/print'):
mac = b.get('mac-address', '')
if 'CA:1A:02:C2:8C:1E' in mac:
bid = b['.id']
print(f'Found: {bid} Type={b.get("type")}')
# Try setting type to empty string
r = api.cmd('/ip/hotspot/ip-binding/set', **{'.id': bid, 'type': ''})
print(f'Set type=\"\" result: {r}')
break
# Check again
print()
for b in api.cmd('/ip/hotspot/ip-binding/print'):
mac = b.get('mac-address', '')
if 'CA:1A:02:C2:8C:1E' in mac:
print(f'After set: MAC={mac} Type="{b.get("type")}"')
# If still bypassed, remove it entirely
if b.get('type') == 'bypassed' or not b.get('type'):
bid = b['.id']
print(f'Still bypassed/empty. Removing binding {bid}...')
r = api.cmd('/ip/hotspot/ip-binding/remove', **{'.id': bid})
print(f'Remove result: {r}')
break
# Verify removal
print()
found = False
for b in api.cmd('/ip/hotspot/ip-binding/print'):
if 'CA:1A:02:C2:8C:1E' in b.get('mac-address', ''):
print(f'STILL EXISTS! MAC={b.get("mac-address")} Type={b.get("type")}')
found = True
if not found:
print('Binding CA:1A:02:C2:8C:1E removed successfully')
api.close()
+70
View File
@@ -0,0 +1,70 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(60)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
print('remove:', api.cmd('/ip/hotspot/user/remove', **{'numbers': '*B94'}))
r = api.cmd('/ip/hotspot/user/print', **{'?name': 'TMR-1'})
print('remaining TMR-1:', len([x for x in r if isinstance(x,dict)]))
api.close()
+79
View File
@@ -0,0 +1,79 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(60)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
name = 'TMR-1'
MARKUP_PREFIX = '%7B%22inline_keyboard%22%3A%5B%5B%7B%22text%22%3A%22%F0%9F%94%84%20Reset%22%2C%22callback_data%22%3A%22reset_'
MARKUP_SUFFIX = '%22%7D%5D%5D%7D'
markup = MARKUP_PREFIX + name + MARKUP_SUFFIX
url = ('https://api.telegram.org/bot5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk/'
'sendMessage?chat_id=-1002554941429&message_thread_id=42&text=' + name + '+expired'
'&reply_markup=' + markup)
print('sending...')
res = api.cmd('/tool/fetch', **{'url': url, 'mode': 'https', 'http-method': 'get', 'keep-result': 'no'})
print('fetch:', [x.get('status') for x in res if isinstance(x,dict)])
api.close()
+129
View File
@@ -0,0 +1,129 @@
<?php
error_reporting(0);
$secret = 'msr-2026-reset';
if (!isset($_GET['key']) || $_GET['key'] !== $secret) { http_response_code(403); die('auth failed'); }
$bot = '5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk';
function tg_api($method, $params = array()) {
global $bot;
$url = "https://api.telegram.org/bot{$bot}/{$method}";
$ch = curl_init();
$opts = array(
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_SSL_VERIFYPEER => false,
);
if (!empty($params)) {
$opts[CURLOPT_POST] = true;
$opts[CURLOPT_POSTFIELDS] = http_build_query($params);
}
curl_setopt_array($ch, $opts);
$r = curl_exec($ch);
curl_close($ch);
return json_decode($r, true);
}
if (isset($_GET['test'])) {
$username = preg_replace('/[^a-zA-Z0-9_.@-]/', '', $_GET['test']);
if (!$username) { die('invalid username'); }
include_once(__DIR__ . '/lib/routeros_api.class.php');
$api = new RouterosAPI();
$api->debug = false;
$api->port = 33206;
if (!$api->connect('remote.vpnmurahjogja.my.id', 'mi4', 'm14')) { die('conn fail'); }
$found = $api->comm('/ip/hotspot/user/print', array('?name' => $username));
if (empty($found)) { $api->disconnect(); die('user not found'); }
$uid = $found[0]['.id'];
$api->comm('/ip/hotspot/user/set', array('.id' => $uid, 'limit-uptime' => '0', 'comment' => ''));
$api->comm('/ip/hotspot/user/reset-counters', array('.id' => $uid));
$sch = $api->comm('/system/scheduler/print', array('?name' => $username));
if (!empty($sch) && isset($sch[0]['.id'])) {
$api->comm('/system/scheduler/remove', array('.id' => $sch[0]['.id']));
}
$api->disconnect();
tg_api('sendMessage', array(
'chat_id' => '-1002554941429',
'message_thread_id' => 42,
'text' => "✅ *{$username}* has been reset\nReady to use again",
'parse_mode' => 'Markdown',
));
echo "reset ok: {$username}";
exit;
}
$updates = tg_api('getUpdates', array('timeout' => 5, 'allowed_updates' => '["callback_query"]'));
if (!$updates || !isset($updates['ok']) || !$updates['ok']) { echo "no updates"; exit; }
$processed = 0;
$last_id = 0;
include_once(__DIR__ . '/lib/routeros_api.class.php');
foreach ($updates['result'] as $u) {
$cb = isset($u['callback_query']) ? $u['callback_query'] : null;
if (!$cb) continue;
$data = isset($cb['data']) ? $cb['data'] : '';
$cb_id = isset($cb['id']) ? $cb['id'] : '';
$last_id = max($last_id, $u['update_id']);
if (substr($data, 0, 6) !== 'reset_') continue;
$username = substr($data, 6);
$username = preg_replace('/[^a-zA-Z0-9_.@-]/', '', $username);
if (!$username) continue;
$api = new RouterosAPI();
$api->debug = false;
$api->port = 33206;
if (!$api->connect('remote.vpnmurahjogja.my.id', 'mi4', 'm14')) {
echo "conn fail for {$username} ";
continue;
}
$found = $api->comm('/ip/hotspot/user/print', array('?name' => $username));
if (empty($found)) {
$api->disconnect();
tg_api('answerCallbackQuery', array(
'callback_query_id' => $cb_id,
'text' => "User {$username} not found",
'show_alert' => true,
));
continue;
}
$uid = $found[0]['.id'];
$api->comm('/ip/hotspot/user/set', array('.id' => $uid, 'limit-uptime' => '0', 'comment' => ''));
$api->comm('/ip/hotspot/user/reset-counters', array('.id' => $uid));
$sch = $api->comm('/system/scheduler/print', array('?name' => $username));
if (!empty($sch) && isset($sch[0]['.id'])) {
$api->comm('/system/scheduler/remove', array('.id' => $sch[0]['.id']));
}
$api->disconnect();
tg_api('answerCallbackQuery', array(
'callback_query_id' => $cb_id,
'text' => "✅ {$username} reset!",
'show_alert' => false,
));
tg_api('sendMessage', array(
'chat_id' => '-1002554941429',
'message_thread_id' => 42,
'text' => "✅ *{$username}* has been reset\nReady to use again",
'parse_mode' => 'Markdown',
));
$processed++;
}
if ($last_id > 0) {
tg_api('getUpdates', array('offset' => $last_id + 1, 'timeout' => 1));
}
echo "processed: {$processed}";
+84
View File
@@ -0,0 +1,84 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(30)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
print('=== ping 8.8.8.8 (internet?) ===')
r = api.cmd('/ping', address='8.8.8.8', count='2', interval='0.3')
got = [x for x in r if isinstance(x,dict) and 'received' in x]
print(' loss=%s' % (got[-1].get('packet-loss','') if got else str(r)[:100]))
print()
print('=== ping msr.s.dimanaaja.biz.id (DNS) ===')
r = api.cmd('/ping', address='msr.s.dimanaaja.biz.id', count='2', interval='0.3')
got = [x for x in r if isinstance(x,dict) and 'received' in x]
print(' loss=%s' % (got[-1].get('packet-loss','') if got else str(r)[:200]))
print()
print('=== fetch https://msr.s.dimanaaja.biz.id/notify-expire.php?key=msr-2026-reset&name=TESTNOTIF ===')
r = api.cmd('/tool/fetch', **{'url': 'https://msr.s.dimanaaja.biz.id/notify-expire.php?key=msr-2026-reset&name=TESTNOTIF', 'http-method': 'get', 'keep-result': 'yes'})
print(' -> %s' % str(r)[:500])
api.close()
+89
View File
@@ -0,0 +1,89 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(30)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
# Add temporary route to Docker host subnets via 192.168.100.1 (the upstream gateway)
for net, gw in [('192.168.35.133/32','192.168.100.1'), ('192.168.9.253/32','192.168.100.1'), ('100.100.31.46/32','192.168.100.1')]:
print(f'--- route {net} via {gw} ---')
r = api.cmd('/ip/route/add', **{'dst-address': net, 'gateway': gw, 'comment': 'tmp-test'})
print(' add:', r if r else 'ok')
import time
time.sleep(2)
# ping test
for ip in ['192.168.35.133','192.168.9.253','100.100.31.46']:
r = api.cmd('/ping', address=ip, count='2')
ok = any(isinstance(x,dict) and x.get('packet-loss')=='0%' for x in r)
print(f'ping {ip}: loss = {[x.get("packet-loss") for x in r if isinstance(x,dict)]}')
# Remove tmp routes
r = api.cmd('/ip/route/print', **{'?comment': 'tmp-test'})
for rw in r:
if isinstance(rw, dict):
api.cmd('/ip/route/remove', **{'=.id': rw.get('.id')})
api.close()
+84
View File
@@ -0,0 +1,84 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(25)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
for ip in ['192.168.9.253', '192.168.35.133', '100.100.31.46', '192.168.100.6']:
r = api.cmd('/ping', address=ip, count='2', interval='0.3')
got = [x for x in r if isinstance(x,dict) and 'received' in x]
if got:
last = got[-1]
loss = last.get('packet-loss','')
print('ping %-18s -> loss=%s' % (ip, loss))
else:
print('ping %-18s -> %s' % (ip, str(r)[:120]))
# Test fetch to host LAN IP on port 80 (traefik)
for url in ['http://192.168.9.253/notify-expire.php?key=msr-2026-reset&name=TESTNOTIF']:
r = api.cmd('/tool/fetch', **{'url': url, 'http-method': 'get', 'keep-result': 'yes'})
print('fetch %s' % url)
print(' -> %s' % str(r)[:400])
api.close()
+80
View File
@@ -0,0 +1,80 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(30)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
# Test 1: router resolve + fetch api.telegram.org
print('=== DNS resolve api.telegram.org ===')
r = api.cmd('/tool/fetch', url='https://api.telegram.org/bot5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk/getMe', mode='https', **{'http-method': 'get', 'keep-result': 'no'})
print(' ', r)
if any(isinstance(x,tuple) and x[0]=='!trap' for x in r):
print(' -> https fetch gagal')
print()
print('=== fetch ke http (no ssl) di api.telegram.org ===')
r = api.cmd('/tool/fetch', url='http://api.telegram.org/bot5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk/getMe', mode='http', **{'http-method': 'get', 'keep-result': 'no'})
print(' ', r)
api.close()
+142
View File
@@ -0,0 +1,142 @@
import socket, hashlib, time
from datetime import datetime, timedelta
class R:
def __init__(self,h,p=8728): self.h=h; self.p=p; self.s=None
def c(self,u,p):
self.s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); self.s.settimeout(10)
self.s.connect((self.h,self.p))
self._ws('/login','=name='+u,'=password='+p)
r=self._rr()
if any(isinstance(x,tuple) and x[0]=='!trap' for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
h=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
self._ws('/login','=name='+u,'=response=00'+h)
self._rr()
return self
def _el(self,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
elif l<0x200000: l|=0xC00000; return bytes([(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
elif l<0x10000000: l|=0xE0000000; return bytes([(l>>24)&0xFF,(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
else: return bytes([0xF0,(l>>24)&0xFF,(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(self,w): self.s.sendall(self._el(len(w))+w.encode('utf-8'))
def _ws(self,c,*ws): self._ww(c); [self._ww(w) for w in ws]; self.s.sendall(b'\x00')
def _rb(self): return self.s.recv(1)
def _rw(self):
f=self._rb()
if f==b'\x00': return None
lb=f[0]
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+self._rb()[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(self._rb()[0]<<8)+self._rb()[0]
elif (lb&0xF0)==0xE0: l=((lb&0x0F)<<24)+(self._rb()[0]<<16)+(self._rb()[0]<<8)+self._rb()[0]
else: l=(self._rb()[0]<<24)+(self._rb()[0]<<16)+(self._rb()[0]<<8)+self._rb()[0]
else: l=lb
d=b''
while len(d)<l:
c=self.s.recv(l-len(d))
if not c: break
d+=c
return d.decode('utf-8',errors='replace')
def _rss(self):
ws=[]
while True:
w=self._rw()
if w is None: break
ws.append(w)
return ws
def _rr(self):
rs=[]
while True:
s=self._rss()
if not s: break
r=s[0]; t=s[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(self,c,**a):
ws=[c]
for k,v in a.items():
if k.startswith('?'): ws.append(f'{k}={v}')
else: ws.append(f'={k}={v}')
self._ws(*ws)
return self._rr()
def close(self):
if self.s: self.s.close(); self.s=None
api=R('192.168.100.2').c('mi4','m14')
# Get router time
r = api.cmd('/system/clock/print')[0]
print(f"Router time: {r['date']} {r['time']}")
# Parse router time
months = {'jan':'jan','feb':'feb','mar':'mar','apr':'apr','may':'may','jun':'jun',
'jul':'jul','aug':'aug','sep':'sep','oct':'oct','nov':'nov','dec':'dec'}
parts = r['date'].split('/')
router_date = f"{parts[0]}/{parts[1]}/{parts[2]}"
router_time = r['time']
# Create comment: 2 minutes in the past
past_comment = f"{router_date} 00:00:00"
print(f"Setting user comment (past): {past_comment}")
# Find next scheduler run
for item in api.cmd('/system/scheduler/print'):
name = item['name']
if 'Bulanan2' in name:
next_run = item.get('next-run', '?')
run_before = int(item.get('run-count', '0'))
print(f"Bulanan2: next-run={next_run} run-count={run_before}")
# Create test user
test_name = "tes1menit"
r = api.cmd('/ip/hotspot/user/add', **{
'name': test_name,
'password': 'x',
'profile': 'Bulanan2',
'comment': past_comment
})
print(f"Created user: {test_name}")
# Wait for scheduler
wait_sec = 180
print(f"\nWaiting {wait_sec}s for scheduler to run...")
for i in range(wait_sec):
time.sleep(1)
if i > 0 and i % 30 == 0:
r = api.cmd('/ip/hotspot/user/print', **{'?name': test_name})
if r:
print(f" [{i}s] User exists. limit-uptime={r[0].get('limit-uptime','')}")
else:
print(f" [{i}s] User REMOVED!")
break
# Final check
print()
r = api.cmd('/ip/hotspot/user/print', **{'?name': test_name})
if r:
u = r[0]
print(f"User still exists. limit-uptime={u.get('limit-uptime')} comment={u.get('comment')}")
if u.get('limit-uptime') == '1s':
print("=> EXPIRED (limit-uptime=1s) ✅")
else:
print("=> NOT EXPIRED yet")
# Cleanup
api.cmd('/ip/hotspot/user/remove', **{'.id': u['.id']})
print("Cleaned up.")
else:
print("User was REMOVED by scheduler. EXPIRY WORKING ✅")
api.close()
+146
View File
@@ -0,0 +1,146 @@
import socket, hashlib, time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(10)
s.connect(('192.168.100.2', 8728))
def el(l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
elif l<0x200000: l|=0xC00000; return bytes([(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
elif l<0x10000000: l|=0xE0000000; return bytes([(l>>24)&0xFF,(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
else: return bytes([0xF0,(l>>24)&0xFF,(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def ww(w): s.sendall(el(len(w))+(w.encode() if isinstance(w,str) else w))
def ws(c,*a): ww(c); [ww(x) for x in a]; s.sendall(b'\x00')
def rb(): return s.recv(1)
def rw():
f=rb()
if f==b'\x00': return None
lb=f[0]
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+rb()[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(rb()[0]<<8)+rb()[0]
elif (lb&0xF0)==0xE0: l=((lb&0x0F)<<24)+(rb()[0]<<16)+(rb()[0]<<8)+rb()[0]
else: l=(rb()[0]<<24)+(rb()[0]<<16)+(rb()[0]<<8)+rb()[0]
else: l=lb
d=b''
while len(d)<l:
c=s.recv(l-len(d))
if not c: break
d+=c
return d.decode('utf-8',errors='replace')
def rss():
ws=[]
while True:
w=rw()
if w is None: break
ws.append(w)
return ws
def rr():
rs=[]
while True:
sent=rss()
if not sent: break
r=sent[0]; t=sent[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(c,**a):
w=[c]
for k,v in a.items():
if k.startswith('?'): w.append(f'{k}={v}')
else: w.append(f'={k}={v}')
ws(*w)
return rr()
ws('/login','=name=mi4','=password=m14')
r=rr()
if any(isinstance(x,tuple) and x[0]=='!trap' for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
resp='00'+hashlib.md5(b'\x00'+b'm14'+bytes.fromhex(ch)).hexdigest()
ws('/login','=name=mi4',f'=response={resp}')
rr()
# Get router time
rt=cmd('/system/clock/print')[0]
date_str=rt['date']
time_str=rt['time']
print(f"Router time: {date_str} {time_str}")
# Parse time, add 1 minute
h=int(time_str[0:2]); m=int(time_str[3:5]); s=int(time_str[6:8])
m+=1
if m>=60: m=0; h+=1
if h>=24: h=0
exp_time=f"{h:02d}:{m:02d}:00"
exp_comment=f"{date_str} {exp_time}"
print(f"Expiry time (1 menit): {exp_comment}")
# Remove old labibah_tes if exists
for u in cmd('/ip/hotspot/user/print',**{'?name':'labibah_tes'}):
cmd('/ip/hotspot/user/remove',**{'.id':u['.id']})
print("Removed old labibah_tes")
# Create new user - active for 1 minute
cmd('/ip/hotspot/user/add',**{
'name':'labibah_tes',
'password':'labibah_tes',
'profile':'Bulanan2',
'mac-address':'CA:1A:02:C2:8C:1E',
'comment':exp_comment,
'limit-uptime':''
})
print(f"\nCreated user: labibah_tes / labibah_tes")
print(f" Expiry comment: {exp_comment}")
print(f" Status: AKTIF (belum expired - bisa login sekarang)")
print(f" Prediksi: setelah jam {exp_time}, scheduler akan expire user ini")
print(f" (Bulanan2 next-run sekitar ~2-3 menit sekali)\n")
# Find Bulanan2 next run
for sitem in cmd('/system/scheduler/print'):
if sitem['name']=='Bulanan2':
print(f"Bulanan2 next-run: {sitem.get('next-run')}")
# Wait and monitor
print(f"\nMemantau setiap 10 detik selama 180 detik...")
for i in range(180):
time.sleep(1)
if i%10==0:
for u in cmd('/ip/hotspot/user/print',**{'?name':'labibah_tes'}):
lu=u.get('limit-uptime','(none)')
co=u.get('comment','')
active_found=False
for a in cmd('/ip/hotspot/active/print',**{'?user':'labibah_tes'}):
active_found=True
status="TERSAMBUNG" if active_found else "offline"
print(f" [{i:3d}s] uptime={lu} status={status}")
break
else:
print(f" [{i:3d}s] User REMOVED!")
break
# Final
print("\n=== HASIL AKHIR ===")
for u in cmd('/ip/hotspot/user/print',**{'?name':'labibah_tes'}):
print(f" User: {u['name']}")
print(f" Comment: {u.get('comment')}")
print(f" limit-uptime: {u.get('limit-uptime','(none)')}")
if u.get('limit-uptime')=='1s': print(" => ✅ EXPIRED!")
elif u.get('limit-uptime')=='(none)': print(" => Masih AKTIF")
cmd('/ip/hotspot/user/remove',**{'.id':u['.id']})
print(" Cleaned up")
break
else:
print(" User sudah dihapus oleh scheduler ✅")
s.close()
+37
View File
@@ -0,0 +1,37 @@
import librouteros
api = librouteros.connect('192.168.100.2', 'mi4', 'm14', port=8728)
# Test 1: Set comment on Bulanan2
items = list(api('/system/scheduler/print'))
for item in items:
if item['name'] == 'Bulanan2':
print(f'Bulanan2 .id = {item[".id"]}')
print(f'Current comment: {item.get("comment")}')
# Set comment
result = list(api('/system/scheduler/set', **{'.id': item['.id'], 'comment': 'TEST_FIX_123'}))
print(f'Set result: {result}')
break
# Re-read
items = list(api('/system/scheduler/print'))
for item in items:
if item['name'] == 'Bulanan2':
print(f'Comment after set: {item.get("comment")}')
# Test 2: Set on-event with simple value
simple_script = ':put "hello world"'
items = list(api('/system/scheduler/print', **{'?name': 'Bulanan2'}))
if items:
item = items[0]
print(f'Setting on-event to simple script...')
result = list(api('/system/scheduler/set', **{'.id': item['.id'], 'on-event': simple_script, 'comment': 'Monitor Profile Bulanan2'}))
print(f'Result: {result}')
# Re-read
items = list(api('/system/scheduler/print', **{'?name': 'Bulanan2'}))
if items:
print(f'On-event after: {items[0].get("on-event")[:100]}')
api.close()
+128
View File
@@ -0,0 +1,128 @@
import socket, hashlib, 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}')
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}")
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
api = RouterOSApi('192.168.100.2')
api.connect('mi4', 'm14')
# Get current time and scheduler next-run
clock = api.cmd('/system/clock/print')[0]
print(f"Time: {clock['date']} {clock['time']}")
# Find Bulanan2's next-run
for item in api.cmd('/system/scheduler/print'):
if item['name'] == 'Bulanan2':
next_run = item.get('next-run', '?')
print(f"Bulanan2 next-run: {next_run}")
run_before = int(item.get('run-count', '0'))
# Create test user
test_name = "e2e_test"
api.cmd('/ip/hotspot/user/add', **{'name': test_name, 'profile': 'Bulanan2', 'password': 'x', 'comment': 'jul/06/2026 00:00:00'})
print(f"\nCreated {test_name}")
# Wait for scheduler to run
wait_sec = 150 # 2.5 min
print(f"Waiting {wait_sec}s for scheduler...")
for i in range(wait_sec):
time.sleep(1)
if i % 30 == 0:
print(f" waited {i}s...")
# Check result
users = list(api.cmd('/ip/hotspot/user/print', **{'?name': test_name}))
for item in api.cmd('/system/scheduler/print'):
if item['name'] == 'Bulanan2':
run_after = int(item.get('run-count', '0'))
print(f"Bulanan2 runs: {run_before} -> {run_after} (delta={run_after-run_before})")
if not users:
print(f"\nResult: {test_name} was REMOVED - EXPIRY SYSTEM IS WORKING!")
else:
user = users[0]
lu = user.get('limit-uptime', '?')
print(f"\nResult: {test_name} still exists (limit-uptime={lu})")
if lu == '1s':
print("limit-uptime=1s - partial: expired but not removed (depends on profile mode)")
# Cleanup
api.cmd('/ip/hotspot/user/remove', **{'.id': user['.id']})
api.close()
+37
View File
@@ -0,0 +1,37 @@
import paramiko, time
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('192.168.100.2', username='mi4', password='m14', look_for_keys=False, allow_agent=False)
# Simple test
simple_code = ':put "hello world" ; :local x 5 ; :put $x'
escaped = simple_code.replace('"', '\\"').replace('$', '\\$').replace('\\', '\\\\')
print(f"Original: {simple_code}")
print(f"Escaped: {escaped}")
chan = client.invoke_shell()
chan.settimeout(5)
time.sleep(1.5)
out = b''
while chan.recv_ready(): out += chan.recv(65535)
# Create test script
chan.send(f'/system script add name=test_esc source="{escaped}"\n')
time.sleep(2)
out = b''
while chan.recv_ready(): out += chan.recv(65535)
print(f"\nCreate result: {out.decode(errors='replace')[:200]}")
# Read it back
stdin, stdout, stderr = client.exec_command(':put [/system script get test_esc source]')
time.sleep(0.5)
result = stdout.read().decode().strip()
print(f"\nStored source: {result}")
# Cleanup
chan.send('/system script remove test_esc\n')
time.sleep(1)
chan.close()
client.close()
+141
View File
@@ -0,0 +1,141 @@
import socket, hashlib, 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
api = RouterOSApi('192.168.100.2')
api.connect('mi4', 'm14')
# 1. Create test user with past expiry
test_name = "test_expiry_verify"
print(f"Creating test user '{test_name}' with past expiry...")
api.cmd('/ip/hotspot/user/add', **{
'name': test_name,
'profile': 'Bulanan2',
'password': 'test123',
'comment': 'jul/06/2026 00:00:00', # yesterday
'limit-uptime': '1h',
})
# Verify
users = list(api.cmd('/ip/hotspot/user/print', **{'?name': test_name}))
if users:
print(f" Created: {users[0].get('name')} comment={users[0].get('comment')}")
# 2. Get scheduler run count before
sched_before = {}
for item in api.cmd('/system/scheduler/print'):
sched_before[item['name']] = item.get('run-count', '0')
print(f"\n DevB2 run count before: {sched_before.get('DevB2', '?')}")
# 3. Wait for scheduler to run (it runs every ~2.5 min)
print("\n Waiting 10 seconds for scheduler to execute...")
time.sleep(10)
# 4. Check if user still exists
users_after = list(api.cmd('/ip/hotspot/user/print', **{'?name': test_name}))
sched_after = {}
for item in api.cmd('/system/scheduler/print'):
sched_after[item['name']] = item.get('run-count', '0')
print(f" DevB2 run count after: {sched_after.get('DevB2', '?')}")
if users_after:
user = users_after[0]
print(f"\n User STILL EXISTS: {user.get('name')}")
print(f" Comment: {user.get('comment')}")
print(f" limit-uptime: {user.get('limit-uptime')}")
# Check if limit-uptime was changed to 1s
if user.get('limit-uptime') == '1s':
print(" EXPIRED: limit-uptime=1s - expiry system is WORKING!")
else:
print(f" NOT YET EXPIRED: limit-uptime={user.get('limit-uptime')}")
print(" (may need to wait for next scheduler cycle)")
else:
print(f"\n User was REMOVED! Expiry system is WORKING!")
# Cleanup
if users_after:
api.cmd('/ip/hotspot/user/remove', **{'.id': users_after[0]['.id']})
print(f"\n Cleaned up test user")
api.close()
+49
View File
@@ -0,0 +1,49 @@
# Test the fix_script logic locally
def fix_script(s):
s = s.replace(':pickk', ':pick')
s = s.replace(':pick', '\x00PICKGUARD\x00')
s = s.replace(':pic', ':pick')
s = s.replace('\x00PICKGUARD\x00', ':pick')
return s
# Test cases
test1 = ':local days [ :pic $d 4 6 ];' # original typo
test2 = ':local days [ :pickk $d 4 6 ];' # current broken state
test3 = ':local days [ :pick $d 4 6 ];' # already correct
test4 = ':local gettime [:pick $comment 12 20];' # correct $comment
test5 = ':local gettime [:pic $comment 12 20];' # original typo $comment
test6 = test1 + ' ' + test4 # mixed
for name, t in [('test1 orig typo', test1), ('test2 double-k', test2),
('test3 correct', test3), ('test4 comment ok', test4),
('test5 comment typo', test5), ('test6 mixed', test6)]:
result = fix_script(t)
pic = result.count(':pic')
pick = result.count(':pick')
pickk = result.count(':pickk')
print(f"{name}: pic={pic} pick={pick} pickk={pickk}")
if result != 'ERROR':
if pic == 0 and pick > 0:
print(f" OK: {result}")
else:
print(f" FAIL: {result}")
# Test with actual long script (simulated)
long_script = ';'.join([
':local dateint do={:local montharray ( "jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec" );:local days [ :pickk $d 4 6 ];:local month [ :pickk $d 0 3 ];:local year [ :pickk $d 7 11 ];:local monthint ([ :find $montharray $month]);:local month ($monthint + 1);:if ( [len $month] = 1) do={:local zero ("0");:return [:tonum ("$year$zero$month$days")];} else={:return [:tonum ("$year$month$days")];}}',
':local timeint do={ :local hours [ :pickk $t 0 2 ]; :local minutes [ :pickk $t 3 5 ]; :return ($hours * 60 + $minutes) ; }',
':local date [ /system clock get date ]',
':local time [ /system clock get time ]',
':local today [$dateint d=$date]',
':local curtime [$timeint t=$time]',
':foreach i in [ /ip hotspot user find where profile="test" ] do={ :local comment [ /ip hotspot user get $i comment]; :local name [ /ip hotspot user get $i name]; :local gettime [:pick $comment 12 20]; :if ([:pick $comment 3] = "/" and [:pick $comment 6] = "/") do={:local expd [$dateint d=$comment] ; :local expt [$timeint t=$gettime] ; :if (($expd < $today and $expt < $curtime)) do={ /ip hotspot user set limit-uptime=1s $i } } }'
])
print(f"\nLong script test:")
print(f" Before: pic={long_script.count(':pic')} pick={long_script.count(':pick')} pickk={long_script.count(':pickk')}")
result = fix_script(long_script)
print(f" After: pic={result.count(':pic')} pick={result.count(':pick')} pickk={result.count(':pickk')}")
if result.count(':pic') == 0:
print(" PASS: No :pic remaining")
else:
print(" FAIL: :pic still present")
+83
View File
@@ -0,0 +1,83 @@
import socket, hashlib
class R:
def __init__(s,h,p=8728): s.h=h; s.p=p; s.s=None
def c(s,u,p):
s.s=socket.socket(); s.s.settimeout(20)
s.s.connect((s.h,s.p))
s._ws('/login','=name='+u,'=password='+p)
r=s._rr()
if any(isinstance(x,tuple) for x in r):
for x in r:
if isinstance(x,dict) and 'ret' in x: ch=x['ret']
if ch and len(ch)==32:
hh=hashlib.md5(b'\x00'+p.encode()+bytes.fromhex(ch)).hexdigest()
s._ws('/login','=name='+u,'=response=00'+hh); s._rr()
return s
def _el(s,l):
if l<0x80: return bytes([l])
elif l<0x4000: l|=0x8000; return bytes([(l>>8)&0xFF,l&0xFF])
else: return bytes([0xC0|((l>>16)&0x0F),(l>>8)&0xFF,l&0xFF]) if l<0x200000 else bytes([0xE0|((l>>24)&0x07),(l>>16)&0xFF,(l>>8)&0xFF,l&0xFF])
def _ww(s,w): s.s.sendall(s._el(len(w))+w.encode())
def _ws(s,c,*ws): s._ww(c); [s._ww(w) for w in ws]; s.s.sendall(b'\x00')
def _rw(s):
f=s.s.recv(1)
if f==b'\x00': return None
lb=f[0]; l=lb
if lb&0x80:
if (lb&0xC0)==0x80: l=((lb&0x3F)<<8)+s.s.recv(1)[0]
elif (lb&0xE0)==0xC0: l=((lb&0x1F)<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
else: l=((lb&0x0F)<<24)+(s.s.recv(1)[0]<<16)+(s.s.recv(1)[0]<<8)+s.s.recv(1)[0]
d=b''
while len(d)<l: d+=s.s.recv(l-len(d))
return d.decode(errors='replace')
def _rss(s):
ws=[]
while True:
w=s._rw()
if w is None: break
ws.append(w)
return ws
def _rr(s):
rs=[]
while True:
s2=s._rss()
if not s2: break
r=s2[0]; t=s2[1:]
if r=='!done': break
elif r=='!re':
d={}
for w in t:
if '=' in w:
p=w.split('=',2)
d[p[1]]=p[2] if len(p)>=3 else ''
else: d[w]=''
rs.append(d)
elif r=='!trap': rs.append(('!trap',t))
return rs
def cmd(s,c,**a):
ws=[c]
for k,v in a.items():
ws.append(('?' if k.startswith('?') else '=')+k+'='+v)
s._ws(*ws)
return s._rr()
def close(s):
if s.s: s.s.close()
api=R('remote.vpnmurahjogja.my.id',33206).c('mi4','m14')
print('=== TEST 1: /tool fetch from router to notify-expire.php ===')
# Use a real test user? Use name=TESTNOTIF
r = api.cmd('/tool/fetch', **{'url': 'http://192.168.100.6/notify-expire.php?key=msr-2026-reset&name=TESTNOTIF', 'http-method': 'get', 'keep-result': 'yes'})
print(' Result: %s' % str(r))
print()
print('=== TEST 2: Can router reach 192.168.100.6? (ping) ===')
r2 = api.cmd('/ping', address='192.168.100.6', count='2')
print(' Result: %s' % str(r2))
print()
print('=== TEST 3: Container reachability (ping container gateway) ===')
r3 = api.cmd('/ping', address='192.168.100.6', count='2', interval='0.5')
print(' Result: %s' % str(r3))
api.close()
+22
View File
@@ -0,0 +1,22 @@
s = ':put "hello" ; :local x 5 ; :put $x'
print('orig:', repr(s))
print('orig raw:', s)
# The original escape logic
escaped = s.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$')
print('escaped repr:', repr(escaped))
print('escaped raw:', escaped)
# What RouterOS expects:
# In RouterOS CLI: /system script add source=":put \"hello\" ; :local x 5 ; :put \$x"
# The CLI parser sees: \", \$, and stores: :put "hello" ; :local x 5 ; :put $x
# Test: what does the CLI actually store?
# Note: the escaped value needs to be:
# - \" for literal double quote
# - \$ for literal dollar sign
# - \\ for literal backslash
correct = s.replace('"', '\\"').replace('$', '\\$')
print('\ncorrect repr:', repr(correct))
print('correct raw:', correct)

Some files were not shown because too many files have changed in this diff Show More