init: workspace opencode sync
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
*.log
|
||||
__pycache__/
|
||||
*.pyc.DS_Store
|
||||
node_modules/.env
|
||||
|
||||
@@ -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).
|
||||
@@ -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()
|
||||
@@ -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";
|
||||
@@ -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';
|
||||
@@ -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';
|
||||
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
@@ -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","")}')
|
||||
@@ -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',''))
|
||||
@@ -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'))
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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)}")
|
||||
@@ -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()
|
||||
@@ -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
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
@@ -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}")
|
||||
@@ -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]}")
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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"
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -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"
|
||||
@@ -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}";
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
@@ -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()
|
||||
@@ -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
@@ -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
@@ -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
@@ -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")
|
||||
@@ -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()
|
||||
@@ -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
@@ -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
@@ -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")
|
||||
@@ -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")
|
||||
@@ -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()
|
||||
@@ -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")
|
||||
@@ -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")
|
||||
@@ -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
@@ -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
@@ -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()
|
||||
@@ -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
|
||||
@@ -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"));'
|
||||
@@ -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"));'
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
@@ -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()
|
||||
@@ -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
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
@@ -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()
|
||||
@@ -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
@@ -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}";
|
||||
@@ -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
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
@@ -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()
|
||||
@@ -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
@@ -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()
|
||||
@@ -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")
|
||||
@@ -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()
|
||||
@@ -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
Reference in New Issue
Block a user