init: workspace opencode sync

This commit is contained in:
2026-08-15 00:28:08 +07:00
commit 2451170708
122 changed files with 10549 additions and 0 deletions
+129
View File
@@ -0,0 +1,129 @@
# Telegram Expiry Notifier + Reset Button — Deploy ke Server
## Overview
Mengganti direct call Telegram API dari RouterOS dengan PHP intermediary agar bisa kirim inline button "Reset" untuk user hotspot yang expired.
### Arsitektur
```
RouterOS scheduler (Family)
↓ call HTTP GET
↓
notify-expire.php (server)
↓ POST ke Telegram API
↓
⚠️ User X expired [🔄 Reset] (ke topik Notif Expire)
↓ admin klik "Reset"
↓ callback_query = "reset_X" ke Bot API
↓
tg-cb-poll scheduler (router, tiap 60s)
↓ call HTTP GET
↓
telegram-cb.php (server)
↓ polling getUpdates → proses callback
↓ RouterOS API: set limit-uptime=0, comment="", reset-counters
↓
✅ User X has been reset (ke topik Notif Expire)
```
### Files
| File | Fungsi |
|------|--------|
| `notify-expire.php` | Kirim notif + inline button ke Telegram |
| `telegram-cb.php` | Polling callback query + eksekusi reset |
| `setup.sh` | Setup script untuk server (install PHP + deploy) |
### Prerequisites (Server)
- Linux server (tested on Ubuntu/Debian)
- PHP 7.4+ dengan `curl` extension
- Akses keluar ke `api.telegram.org` (port 443)
- Akses ke RouterOS API (`192.168.100.2:8728`)
- `curl` binary di PATH (untuk internal call dari PHP ke Telegram)
### Setup Cepat
```bash
# 1. Copy files ke server
scp notify-expire.php player@somewhere:/home/player/telegram-notif/
scp telegram-cb.php player@somewhere:/home/player/telegram-notif/
scp setup.sh player@somewhere:/home/player/telegram-notif/
# 2. SSH ke server
ssh player@somewhere
# 3. Run setup
cd ~/telegram-notif
chmod +x setup.sh
./setup.sh
# 4. Start PHP built-in server (background)
nohup php -S 0.0.0.0:8080 -t /home/player/telegram-notif > /tmp/telegram-notif.log 2>&1 &
```
### Update RouterOS Scheduler
#### A. Family scheduler — notifikasi
Ganti on-event:
```bash
# OLD
/tool fetch url=("https://api.telegram.org/bot" . $bot . "/sendMessage?chat_id=" . $chat . "&text=" . $msg) http-method=get keep-result=no
# NEW — arahkan ke IP Tailscale server
/tool fetch url=("http://100.100.31.46:8080/notify-expire.php?key=msr-2026-reset&name=" . $name) http-method=get keep-result=no
```
#### B. Tambah scheduler polling callback
```bash
/system scheduler add name=tg-cb-poll interval=00:01:00 start-time=startup on-event=":delay 10s;/tool fetch url=\"http://100.100.31.46:8080/telegram-cb.php?key=msr-2026-reset\" http-method=get keep-result=no" disabled=no
```
### Secret Key
`msr-2026-reset` — shared secret di URL parameter `key`. Diganti sesuai kebutuhan.
### Bot Config
- Token: `5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk`
- Group: `-1002554941429` (laporkan)
- Topic ID: `42` (Notif Expire)
### RouterOS Credentials (untuk callback reset)
- IP: `192.168.100.2`
- User: `mi4`
- Password: `m14`
### Troubleshooting
**Logs**:
```bash
tail -f /tmp/telegram-notif.log
```
**Test notifikasi**:
```bash
curl "http://localhost:8080/notify-expire.php?key=msr-2026-reset&name=test_user"
```
**Test reset langsung**:
```bash
curl "http://localhost:8080/telegram-cb.php?key=msr-2026-reset&test=test_user"
```
**PHP curl extension**:
```bash
sudo apt install php-curl
```
### File Reference
Source files di `C:\Users\Admin\dev\Mikhmon Server\mikhmon\`:
- `notify-expire.php`
- `process/telegram-cb.php`
- `lib/routeros_api.class.php` (dependency untuk koneksi ke RouterOS)
+44
View File
@@ -0,0 +1,44 @@
# Telegram Expiry Notifier — Deploy ke somewhere
# Jalankan script ini. Masukkan password SSH player@somewhere ketika diminta.
$server = "player@somewhere"
$remoteDir = "/home/player/telegram-notif"
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
Write-Host "=== Deploy Telegram Notifier ke $server ===" -ForegroundColor Cyan
Write-Host ""
# 1. Buat remote directory via SSH
Write-Host "[1/4] Membuat remote directory..." -ForegroundColor Yellow
Start-Process -Wait -WindowStyle Normal -FilePath "ssh" -ArgumentList "-t $server mkdir -p $remoteDir"
Write-Host ""
# 2. Copy files via SCP
Write-Host "[2/4] Copy files via SCP..." -ForegroundColor Yellow
Start-Process -Wait -WindowStyle Normal -FilePath "scp" -ArgumentList "$scriptDir\notify-expire.php $scriptDir\telegram-cb.php $scriptDir\routeros_api.class.php $scriptDir\setup.sh ${server}:${remoteDir}/"
Write-Host ""
# 3. Run setup via SSH
Write-Host "[3/4] Run remote setup..." -ForegroundColor Yellow
Write-Host " (akan menginstall PHP + membuat systemd service)" -ForegroundColor Gray
Start-Process -Wait -WindowStyle Normal -FilePath "ssh" -ArgumentList "-t $server cd $remoteDir && chmod +x setup.sh && ./setup.sh"
Write-Host ""
# 4. Test
Write-Host "[4/4] Test..." -ForegroundColor Yellow
Start-Process -Wait -WindowStyle Normal -FilePath "ssh" -ArgumentList "-t $server curl -s 'http://localhost:8080/notify-expire.php?key=msr-2026-reset&name=test_deploy'"
Write-Host ""
Write-Host "=== Done ===" -ForegroundColor Green
Write-Host ""
Write-Host "Update RouterOS scheduler:"
Write-Host " Family on-event:"
Write-Host ' /tool fetch url=("http://100.100.31.46:8080/notify-expire.php?key=msr-2026-reset&name=" . $name) http-method=get keep-result=no'
Write-Host ""
Write-Host " Tambah scheduler:"
Write-Host ' /system scheduler add name=tg-cb-poll interval=00:01:00 start-time=startup on-event=":delay 10s;/tool fetch url=\"http://100.100.31.46:8080/telegram-cb.php?key=msr-2026-reset\" http-method=get keep-result=no" disabled=no'
Write-Host ""
Write-Host "Remote management:"
Write-Host " ssh $server"
Write-Host " sudo systemctl status telegram-notif"
Write-Host " sudo journalctl -u telegram-notif -f"
+49
View File
@@ -0,0 +1,49 @@
<?php
error_reporting(0);
$secret = 'msr-2026-reset';
if (!isset($_GET['key']) || $_GET['key'] !== $secret) { http_response_code(403); die('auth failed'); }
$user = preg_replace('/[^a-zA-Z0-9_.@-]/', '', $_GET['name']);
if (!$user) { http_response_code(400); die('missing name'); }
$bot = '5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk';
$chat = '-1002554941429';
$thread = 42;
$text = "⚠️ User *{$user}* expired";
$markup = json_encode(array(
'inline_keyboard' => array(array(array(
'text' => '🔄 Reset',
'callback_data' => "reset_{$user}"
)))
));
$data = http_build_query(array(
'chat_id' => $chat,
'message_thread_id' => $thread,
'text' => $text,
'parse_mode' => 'Markdown',
'reply_markup' => $markup,
));
function tg_post($url, $data) {
$ctx = stream_context_create(array('http' => array(
'method' => 'POST',
'header' => 'Content-Type: application/x-www-form-urlencoded',
'content' => $data,
'timeout' => 10,
)));
return @file_get_contents($url, false, $ctx);
}
$r = tg_post("https://api.telegram.org/bot{$bot}/sendMessage", $data);
if ($r === false) { http_response_code(500); echo "fail: http error"; exit; }
$result = json_decode($r, true);
if (isset($result['ok']) && $result['ok']) {
echo "sent: " . $result['result']['message_id'];
} else {
http_response_code(500);
echo "fail: " . (isset($result['description']) ? $result['description'] : 'no response');
}
+647
View File
@@ -0,0 +1,647 @@
<?php
/*****************************
*
* RouterOS PHP API class v1.6
* Author: Denis Basta
* Contributors:
* Nick Barnes
* Ben Menking (ben [at] infotechsc [dot] com)
* Jeremy Jefferson (http://jeremyj.com)
* Cristian Deluxe (djcristiandeluxe [at] gmail [dot] com)
* Mikhail Moskalev (mmv.rus [at] gmail [dot] com)
*
* http://www.mikrotik.com
* http://wiki.mikrotik.com/wiki/API_PHP_class
*
******************************/
class RouterosAPI
{
var $debug = false; // Show debug information
var $connected = false; // Connection state
var $port = 8728; // Port to connect to (default 8729 for ssl)
var $ssl = false; // Connect using SSL (must enable api-ssl in IP/Services)
var $timeout = 3; // Connection attempt timeout and data read timeout
var $attempts = 5; // Connection attempt count
var $delay = 3; // Delay between connection attempts in seconds
var $socket; // Variable for storing socket resource
var $error_no; // Variable for storing connection error number, if any
var $error_str; // Variable for storing connection error text, if any
/* Check, can be var used in foreach */
public function isIterable($var)
{
return $var !== null
&& (is_array($var)
|| $var instanceof Traversable
|| $var instanceof Iterator
|| $var instanceof IteratorAggregate
);
}
/**
* Print text for debug purposes
*
* @param string $text Text to print
*
* @return void
*/
public function debug($text)
{
if ($this->debug) {
echo $text . "\n";
}
}
/**
*
*
* @param string $length
*
* @return void
*/
public function encodeLength($length)
{
if ($length < 0x80) {
$length = chr($length);
} elseif ($length < 0x4000) {
$length |= 0x8000;
$length = chr(($length >> 8) & 0xFF) . chr($length & 0xFF);
} elseif ($length < 0x200000) {
$length |= 0xC00000;
$length = chr(($length >> 16) & 0xFF) . chr(($length >> 8) & 0xFF) . chr($length & 0xFF);
} elseif ($length < 0x10000000) {
$length |= 0xE0000000;
$length = chr(($length >> 24) & 0xFF) . chr(($length >> 16) & 0xFF) . chr(($length >> 8) & 0xFF) . chr($length & 0xFF);
} elseif ($length >= 0x10000000) {
$length = chr(0xF0) . chr(($length >> 24) & 0xFF) . chr(($length >> 16) & 0xFF) . chr(($length >> 8) & 0xFF) . chr($length & 0xFF);
}
return $length;
}
/**
* Login to RouterOS
*
* @param string $ip Hostname (IP or domain) of the RouterOS server
* @param string $login The RouterOS username
* @param string $password The RouterOS password
*
* @return boolean If we are connected or not
*/
public function connect($ip, $login, $password)
{
for ($ATTEMPT = 1; $ATTEMPT <= $this->attempts; $ATTEMPT++) {
$this->connected = false;
$PROTOCOL = ($this->ssl ? 'ssl://' : '' );
$context = stream_context_create(array('ssl' => array('ciphers' => 'ADH:ALL', 'verify_peer' => false, 'verify_peer_name' => false)));
$this->debug('Connection attempt #' . $ATTEMPT . ' to ' . $PROTOCOL . $ip . ':' . $this->port . '...');
$this->socket = @stream_socket_client($PROTOCOL . $ip.':'. $this->port, $this->error_no, $this->error_str, $this->timeout, STREAM_CLIENT_CONNECT,$context);
if ($this->socket) {
socket_set_timeout($this->socket, $this->timeout);
$this->write('/login', false);
$this->write('=name=' . $login, false);
$this->write('=password=' . $password);
$RESPONSE = $this->read(false);
if (isset($RESPONSE[0])) {
if ($RESPONSE[0] == '!done') {
if (!isset($RESPONSE[1])) {
// Login method post-v6.43
$this->connected = true;
break;
} else {
// Login method pre-v6.43
$MATCHES = array();
if (preg_match_all('/[^=]+/i', $RESPONSE[1], $MATCHES)) {
if ($MATCHES[0][0] == 'ret' && strlen($MATCHES[0][1]) == 32) {
$this->write('/login', false);
$this->write('=name=' . $login, false);
$this->write('=response=00' . md5(chr(0) . $password . pack('H*', $MATCHES[0][1])));
$RESPONSE = $this->read(false);
if (isset($RESPONSE[0]) && $RESPONSE[0] == '!done') {
$this->connected = true;
break;
}
}
}
}
}
}
fclose($this->socket);
}
sleep($this->delay);
}
if ($this->connected) {
$this->debug('Connected...');
} else {
$this->debug('Error...');
}
return $this->connected;
}
/**
* Disconnect from RouterOS
*
* @return void
*/
public function disconnect()
{
// let's make sure this socket is still valid. it may have been closed by something else
if( is_resource($this->socket) ) {
fclose($this->socket);
}
$this->connected = false;
$this->debug('Disconnected...');
}
/**
* Parse response from Router OS
*
* @param array $response Response data
*
* @return array Array with parsed data
*/
public function parseResponse($response)
{
if (is_array($response)) {
$PARSED = array();
$CURRENT = null;
$singlevalue = null;
foreach ($response as $x) {
if (in_array($x, array('!fatal','!re','!trap'))) {
if ($x == '!re') {
$CURRENT =& $PARSED[];
} else {
$CURRENT =& $PARSED[$x][];
}
} elseif ($x != '!done') {
$MATCHES = array();
if (preg_match_all('/[^=]+/i', $x, $MATCHES)) {
if ($MATCHES[0][0] == 'ret') {
$singlevalue = $MATCHES[0][1];
}
$CURRENT[$MATCHES[0][0]] = (isset($MATCHES[0][1]) ? $MATCHES[0][1] : '');
}
}
}
if (empty($PARSED) && !is_null($singlevalue)) {
$PARSED = $singlevalue;
}
return $PARSED;
} else {
return array();
}
}
/**
* Parse response from Router OS
*
* @param array $response Response data
*
* @return array Array with parsed data
*/
public function parseResponse4Smarty($response)
{
if (is_array($response)) {
$PARSED = array();
$CURRENT = null;
$singlevalue = null;
foreach ($response as $x) {
if (in_array($x, array('!fatal','!re','!trap'))) {
if ($x == '!re') {
$CURRENT =& $PARSED[];
} else {
$CURRENT =& $PARSED[$x][];
}
} elseif ($x != '!done') {
$MATCHES = array();
if (preg_match_all('/[^=]+/i', $x, $MATCHES)) {
if ($MATCHES[0][0] == 'ret') {
$singlevalue = $MATCHES[0][1];
}
$CURRENT[$MATCHES[0][0]] = (isset($MATCHES[0][1]) ? $MATCHES[0][1] : '');
}
}
}
foreach ($PARSED as $key => $value) {
$PARSED[$key] = $this->arrayChangeKeyName($value);
}
return $PARSED;
if (empty($PARSED) && !is_null($singlevalue)) {
$PARSED = $singlevalue;
}
} else {
return array();
}
}
/**
* Change "-" and "/" from array key to "_"
*
* @param array $array Input array
*
* @return array Array with changed key names
*/
public function arrayChangeKeyName(&$array)
{
if (is_array($array)) {
foreach ($array as $k => $v) {
$tmp = str_replace("-", "_", $k);
$tmp = str_replace("/", "_", $tmp);
if ($tmp) {
$array_new[$tmp] = $v;
} else {
$array_new[$k] = $v;
}
}
return $array_new;
} else {
return $array;
}
}
/**
* Read data from Router OS
*
* @param boolean $parse Parse the data? default: true
*
* @return array Array with parsed or unparsed data
*/
public function read($parse = true)
{
$RESPONSE = array();
$receiveddone = false;
while (true) {
// Read the first byte of input which gives us some or all of the length
// of the remaining reply.
$BYTE = ord(fread($this->socket, 1));
$LENGTH = 0;
// If the first bit is set then we need to remove the first four bits, shift left 8
// and then read another byte in.
// We repeat this for the second and third bits.
// If the fourth bit is set, we need to remove anything left in the first byte
// and then read in yet another byte.
if ($BYTE & 128) {
if (($BYTE & 192) == 128) {
$LENGTH = (($BYTE & 63) << 8) + ord(fread($this->socket, 1));
} else {
if (($BYTE & 224) == 192) {
$LENGTH = (($BYTE & 31) << 8) + ord(fread($this->socket, 1));
$LENGTH = ($LENGTH << 8) + ord(fread($this->socket, 1));
} else {
if (($BYTE & 240) == 224) {
$LENGTH = (($BYTE & 15) << 8) + ord(fread($this->socket, 1));
$LENGTH = ($LENGTH << 8) + ord(fread($this->socket, 1));
$LENGTH = ($LENGTH << 8) + ord(fread($this->socket, 1));
} else {
$LENGTH = ord(fread($this->socket, 1));
$LENGTH = ($LENGTH << 8) + ord(fread($this->socket, 1));
$LENGTH = ($LENGTH << 8) + ord(fread($this->socket, 1));
$LENGTH = ($LENGTH << 8) + ord(fread($this->socket, 1));
}
}
}
} else {
$LENGTH = $BYTE;
}
$_ = "";
// If we have got more characters to read, read them in.
if ($LENGTH > 0) {
$_ = "";
$retlen = 0;
while ($retlen < $LENGTH) {
$toread = $LENGTH - $retlen;
$_ .= fread($this->socket, $toread);
$retlen = strlen($_);
}
$RESPONSE[] = $_;
$this->debug('>>> [' . $retlen . '/' . $LENGTH . '] bytes read.');
}
// If we get a !done, make a note of it.
if ($_ == "!done") {
$receiveddone = true;
}
$STATUS = socket_get_status($this->socket);
if ($LENGTH > 0) {
$this->debug('>>> [' . $LENGTH . ', ' . $STATUS['unread_bytes'] . ']' . $_);
}
if ((!$this->connected && !$STATUS['unread_bytes']) || ($this->connected && !$STATUS['unread_bytes'] && $receiveddone)) {
break;
}
}
if ($parse) {
$RESPONSE = $this->parseResponse($RESPONSE);
}
return $RESPONSE;
}
/**
* Write (send) data to Router OS
*
* @param string $command A string with the command to send
* @param mixed $param2 If we set an integer, the command will send this data as a "tag"
* If we set it to boolean true, the funcion will send the comand and finish
* If we set it to boolean false, the funcion will send the comand and wait for next command
* Default: true
*
* @return boolean Return false if no command especified
*/
public function write($command, $param2 = true)
{
if ($command) {
$data = explode("\n", $command);
foreach ($data as $com) {
$com = trim($com);
fwrite($this->socket, $this->encodeLength(strlen($com)) . $com);
$this->debug('<<< [' . strlen($com) . '] ' . $com);
}
if (gettype($param2) == 'integer') {
fwrite($this->socket, $this->encodeLength(strlen('.tag=' . $param2)) . '.tag=' . $param2 . chr(0));
$this->debug('<<< [' . strlen('.tag=' . $param2) . '] .tag=' . $param2);
} elseif (gettype($param2) == 'boolean') {
fwrite($this->socket, ($param2 ? chr(0) : ''));
}
return true;
} else {
return false;
}
}
/**
* Write (send) data to Router OS
*
* @param string $com A string with the command to send
* @param array $arr An array with arguments or queries
*
* @return array Array with parsed
*/
public function comm($com, $arr = array())
{
$count = count($arr);
$this->write($com, !$arr);
$i = 0;
if ($this->isIterable($arr)) {
foreach ($arr as $k => $v) {
switch ($k[0]) {
case "?":
$el = "$k=$v";
break;
case "~":
$el = "$k~$v";
break;
default:
$el = "=$k=$v";
break;
}
$last = ($i++ == $count - 1);
$this->write($el, $last);
}
}
return $this->read();
}
/**
* Standard destructor
*
* @return void
*/
public function __destruct()
{
$this->disconnect();
}
}
// encrypt decript
function encrypt($string, $key=128) {
$result = '';
for($i=0, $k= strlen($string); $i<$k; $i++) {
$char = substr($string, $i, 1);
$keychar = substr($key, ($i % strlen($key))-1, 1);
$char = chr(ord($char)+ord($keychar));
$result .= $char;
}
return base64_encode($result);
}
function decrypt($string, $key=128) {
$result = '';
$string = base64_decode($string);
for($i=0, $k=strlen($string); $i< $k ; $i++) {
$char = substr($string, $i, 1);
$keychar = substr($key, ($i % strlen($key))-1, 1);
$char = chr(ord($char)-ord($keychar));
$result .= $char;
}
return $result;
}
// Reformat date time MikroTik
// by Laksamadi Guko
function formatInterval($dtm){
$val_convert = $dtm;
$new_format = str_replace("s", "", str_replace("m", "m ", str_replace("h", "h ", str_replace("d", "d ", str_replace("w", "w ", $val_convert)))));
return $new_format;
}
function formatDTM($dtm){
if(substr($dtm, 1,1) == "d" || substr($dtm, 2,1) == "d"){
$day = explode("d",$dtm)[0]."d";
$day = str_replace("d", "d ", str_replace("w", "w ", $day));
$dtm = explode("d",$dtm)[1];
}elseif(substr($dtm, 1,1) == "w" && substr($dtm, 3,1) == "d" || substr($dtm, 2,1) == "w" && substr($dtm, 4,1) == "d"){
$day = explode("d",$dtm)[0]."d";
$day = str_replace("d", "d ", str_replace("w", "w ", $day));
$dtm = explode("d",$dtm)[1];
}elseif (substr($dtm, 1,1) == "w" || substr($dtm, 2,1) == "w" ) {
$day = explode("w",$dtm)[0]."w";
$day = str_replace("d", "d ", str_replace("w", "w ", $day));
$dtm = explode("w",$dtm)[1];
}
// secs
if(strlen($dtm) == "2" && substr($dtm, -1) == "s"){
$format = $day." 00:00:0".substr($dtm, 0,-1);
}elseif(strlen($dtm) == "3" && substr($dtm, -1) == "s"){
$format = $day." 00:00:".substr($dtm, 0,-1);
//minutes
}elseif(strlen($dtm) == "2" && substr($dtm, -1) == "m"){
$format = $day." 00:0".substr($dtm, 0,-1).":00";
}elseif(strlen($dtm) == "3" && substr($dtm, -1) == "m"){
$format = $day." 00:".substr($dtm, 0,-1).":00";
//hours
}elseif(strlen($dtm) == "2" && substr($dtm, -1) == "h"){
$format = $day." 0".substr($dtm, 0,-1).":00:00";
}elseif(strlen($dtm) == "3" && substr($dtm, -1) == "h"){
$format = $day." ".substr($dtm, 0,-1).":00:00";
//minutes -secs
}elseif(strlen($dtm) == "4" && substr($dtm, -1) == "s" && substr($dtm,1,-2) == "m"){
$format = $day." "."00:0".substr($dtm, 0,1).":0".substr($dtm, 2,-1);
}elseif(strlen($dtm) == "5" && substr($dtm, -1) == "s" && substr($dtm,1,-3) == "m"){
$format = $day." "."00:0".substr($dtm, 0,1).":".substr($dtm, 2,-1);
}elseif(strlen($dtm) == "5" && substr($dtm, -1) == "s" && substr($dtm,2,-2) == "m"){
$format = $day." "."00:".substr($dtm, 0,2).":0".substr($dtm, 3,-1);
}elseif(strlen($dtm) == "6" && substr($dtm, -1) == "s" && substr($dtm,2,-3) == "m"){
$format = $day." "."00:".substr($dtm, 0,2).":".substr($dtm, 3,-1);
//hours -secs
}elseif(strlen($dtm) == "4" && substr($dtm, -1) == "s" && substr($dtm,1,-2) == "h"){
$format = $day." 0".substr($dtm, 0,1).":00:0".substr($dtm, 2,-1);
}elseif(strlen($dtm) == "5" && substr($dtm, -1) == "s" && substr($dtm,1,-3) == "h"){
$format = $day." 0".substr($dtm, 0,1).":00:".substr($dtm, 2,-1);
}elseif(strlen($dtm) == "5" && substr($dtm, -1) == "s" && substr($dtm,2,-2) == "h"){
$format = $day." ".substr($dtm, 0,2).":00:0".substr($dtm, 3,-1);
}elseif(strlen($dtm) == "6" && substr($dtm, -1) == "s" && substr($dtm,2,-3) == "h"){
$format = $day." ".substr($dtm, 0,2).":00:".substr($dtm, 3,-1);
//hours -secs
}elseif(strlen($dtm) == "4" && substr($dtm, -1) == "m" && substr($dtm,1,-2) == "h"){
$format = $day." 0".substr($dtm, 0,1).":0".substr($dtm, 2,-1).":00";
}elseif(strlen($dtm) == "5" && substr($dtm, -1) == "m" && substr($dtm,1,-3) == "h"){
$format = $day." 0".substr($dtm, 0,1).":".substr($dtm, 2,-1).":00";
}elseif(strlen($dtm) == "5" && substr($dtm, -1) == "m" && substr($dtm,2,-2) == "h"){
$format = $day." ".substr($dtm, 0,2).":0".substr($dtm, 3,-1).":00";
}elseif(strlen($dtm) == "6" && substr($dtm, -1) == "m" && substr($dtm,2,-3) == "h"){
$format = $day." ".substr($dtm, 0,2).":".substr($dtm, 3,-1).":00";
//hours minutes secs
}elseif(strlen($dtm) == "6" && substr($dtm, -1) == "s" && substr($dtm,3,-2) == "m" && substr($dtm,1,-4) == "h"){
$format = $day." 0".substr($dtm, 0,1).":0".substr($dtm, 2,-3).":0".substr($dtm, 4,-1);
}elseif(strlen($dtm) == "7" && substr($dtm, -1) == "s" && substr($dtm,3,-3) == "m" && substr($dtm,1,-5) == "h"){
$format = $day." 0".substr($dtm, 0,1).":0".substr($dtm, 2,-4).":".substr($dtm, 4,-1);
}elseif(strlen($dtm) == "7" && substr($dtm, -1) == "s" && substr($dtm,4,-2) == "m" && substr($dtm,1,-5) == "h"){
$format = $day." 0".substr($dtm, 0,1).":".substr($dtm, 2,-3).":0".substr($dtm, 5,-1);
}elseif(strlen($dtm) == "8" && substr($dtm, -1) == "s" && substr($dtm,4,-3) == "m" && substr($dtm,1,-6) == "h"){
$format = $day." 0".substr($dtm, 0,1).":".substr($dtm, 2,-4).":".substr($dtm, 5,-1);
}elseif(strlen($dtm) == "7" && substr($dtm, -1) == "s" && substr($dtm,4,-2) == "m" && substr($dtm,2,-4) == "h"){
$format = $day." ".substr($dtm, 0,2).":0".substr($dtm, 3,-3).":0".substr($dtm, 5,-1);
}elseif(strlen($dtm) == "8" && substr($dtm, -1) == "s" && substr($dtm,4,-3) == "m" && substr($dtm,2,-5) == "h"){
$format = $day." ".substr($dtm, 0,2).":0".substr($dtm, 3,-4).":".substr($dtm, 5,-1);
}elseif(strlen($dtm) == "8" && substr($dtm, -1) == "s" && substr($dtm,5,-2) == "m" && substr($dtm,2,-5) == "h"){
$format = $day." ".substr($dtm, 0,2).":".substr($dtm, 3,-3).":0".substr($dtm, 6,-1);
}elseif(strlen($dtm) == "9" && substr($dtm, -1) == "s" && substr($dtm,5,-3) == "m" && substr($dtm,2,-6) == "h"){
$format = $day." ".substr($dtm, 0,2).":".substr($dtm, 3,-4).":".substr($dtm, 6,-1);
}else{
$format = $dtm;
}
return $format;
}
function randN($length) {
$chars = "23456789";
$charArray = str_split($chars);
$charCount = strlen($chars);
$result = "";
for($i=1;$i<=$length;$i++)
{
$randChar = rand(0,$charCount-1);
$result .= $charArray[$randChar];
}
return $result;
}
function randUC($length) {
$chars = "ABCDEFGHJKLMNPRSTUVWXYZ";
$charArray = str_split($chars);
$charCount = strlen($chars);
$result = "";
for($i=1;$i<=$length;$i++)
{
$randChar = rand(0,$charCount-1);
$result .= $charArray[$randChar];
}
return $result;
}
function randLC($length) {
$chars = "abcdefghijkmnprstuvwxyz";
$charArray = str_split($chars);
$charCount = strlen($chars);
$result = "";
for($i=1;$i<=$length;$i++)
{
$randChar = rand(0,$charCount-1);
$result .= $charArray[$randChar];
}
return $result;
}
function randULC($length) {
$chars = "ABCDEFGHJKLMNPRSTUVWXYZabcdefghijkmnprstuvwxyz";
$charArray = str_split($chars);
$charCount = strlen($chars);
$result = "";
for($i=1;$i<=$length;$i++)
{
$randChar = rand(0,$charCount-1);
$result .= $charArray[$randChar];
}
return $result;
}
function randNLC($length) {
$chars = "23456789abcdefghijkmnprstuvwxyz";
$charArray = str_split($chars);
$charCount = strlen($chars);
$result = "";
for($i=1;$i<=$length;$i++)
{
$randChar = rand(0,$charCount-1);
$result .= $charArray[$randChar];
}
return $result;
}
function randNUC($length) {
$chars = "23456789ABCDEFGHJKLMNPRSTUVWXYZ";
$charArray = str_split($chars);
$charCount = strlen($chars);
$result = "";
for($i=1;$i<=$length;$i++)
{
$randChar = rand(0,$charCount-1);
$result .= $charArray[$randChar];
}
return $result;
}
function randNULC($length) {
$chars = "23456789ABCDEFGHJKLMNPRSTUVWXYZabcdefghijkmnprstuvwxyz";
$charArray = str_split($chars);
$charCount = strlen($chars);
$result = "";
for($i=1;$i<=$length;$i++)
{
$randChar = rand(0,$charCount-1);
$result .= $charArray[$randChar];
}
return $result;
}
?>
+50
View File
@@ -0,0 +1,50 @@
#!/bin/bash
set -e
echo "=== Telegram Expiry Notifier — Setup ==="
DIR="/home/player/telegram-notif"
PORT=8080
SERVICE="telegram-notif"
# 1. Install PHP + curl
echo "[1/4] Installing PHP + php-curl..."
sudo apt update -qq
sudo apt install -y -qq php-cli php-curl curl 2>/dev/null
# 2. Create directory
echo "[2/4] Creating directory..."
mkdir -p "$DIR"
# 3. Copy files (run this script from the same dir as the PHP files)
echo "[3/4] Copying files..."
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cp "$SCRIPT_DIR/notify-expire.php" "$DIR/"
cp "$SCRIPT_DIR/telegram-cb.php" "$DIR/"
# 4. Create systemd service
echo "[4/4] Creating systemd service..."
sudo tee /etc/systemd/system/$SERVICE.service > /dev/null <<EOF
[Unit]
Description=Telegram Expiry Notifier
After=network.target
[Service]
ExecStart=/usr/bin/php -S 0.0.0.0:$PORT -t $DIR
WorkingDirectory=$DIR
Restart=always
RestartSec=5
User=player
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable $SERVICE
sudo systemctl restart $SERVICE
echo "=== Done ==="
echo "Service: sudo systemctl status $SERVICE"
echo "Test: curl http://localhost:$PORT/notify-expire.php?key=msr-2026-reset&name=test"
echo "Logs: sudo journalctl -u $SERVICE -f"
+127
View File
@@ -0,0 +1,127 @@
<?php
error_reporting(0);
$secret = 'msr-2026-reset';
if (!isset($_GET['key']) || $_GET['key'] !== $secret) { http_response_code(403); die('auth failed'); }
$bot = '5115036608:AAGeyXVq5eNvdAkaCQHU9NfH16uAAiSKiuk';
function tg_api($method, $params = array()) {
global $bot;
$url = "https://api.telegram.org/bot{$bot}/{$method}";
if (!empty($params)) {
$data = http_build_query($params);
$ctx = stream_context_create(array('http' => array(
'method' => 'POST',
'header' => 'Content-Type: application/x-www-form-urlencoded',
'content' => $data,
'timeout' => 15,
)));
$r = @file_get_contents($url, false, $ctx);
} else {
$r = @file_get_contents($url, false, stream_context_create(array('http' => array('timeout' => 5))));
}
return $r ? json_decode($r, true) : null;
}
if (isset($_GET['test'])) {
$username = preg_replace('/[^a-zA-Z0-9_.@-]/', '', $_GET['test']);
if (!$username) { die('invalid username'); }
include_once(dirname(__DIR__) . '/lib/routeros_api.class.php');
$api = new RouterosAPI();
$api->debug = false;
$api->port = 33206;
if (!$api->connect('remote.vpnmurahjogja.my.id', 'mi4', 'm14')) { die('conn fail'); }
$found = $api->comm('/ip/hotspot/user/print', array('?name' => $username));
if (empty($found)) { $api->disconnect(); die('user not found'); }
$uid = $found[0]['.id'];
$api->comm('/ip/hotspot/user/set', array('.id' => $uid, 'limit-uptime' => '0', 'comment' => ''));
$api->comm('/ip/hotspot/user/reset-counters', array('.id' => $uid));
$sch = $api->comm('/system/scheduler/print', array('?name' => $username));
if (!empty($sch) && isset($sch[0]['.id'])) {
$api->comm('/system/scheduler/remove', array('.id' => $sch[0]['.id']));
}
$api->disconnect();
tg_api('sendMessage', array(
'chat_id' => '-1002554941429',
'message_thread_id' => 42,
'text' => "✅ *{$username}* has been reset\nReady to use again",
'parse_mode' => 'Markdown',
));
echo "reset ok: {$username}";
exit;
}
$updates = tg_api('getUpdates', array('timeout' => 5, 'allowed_updates' => '["callback_query"]'));
if (!$updates || !isset($updates['ok']) || !$updates['ok']) { echo "no updates"; exit; }
$processed = 0;
$last_id = 0;
include_once(dirname(__DIR__) . '/lib/routeros_api.class.php');
foreach ($updates['result'] as $u) {
$cb = isset($u['callback_query']) ? $u['callback_query'] : null;
if (!$cb) continue;
$data = isset($cb['data']) ? $cb['data'] : '';
$cb_id = isset($cb['id']) ? $cb['id'] : '';
$last_id = max($last_id, $u['update_id']);
if (substr($data, 0, 6) !== 'reset_') continue;
$username = substr($data, 6);
$username = preg_replace('/[^a-zA-Z0-9_.@-]/', '', $username);
if (!$username) continue;
$api = new RouterosAPI();
$api->debug = false;
$api->port = 33206;
if (!$api->connect('remote.vpnmurahjogja.my.id', 'mi4', 'm14')) {
echo "conn fail for {$username} ";
continue;
}
$found = $api->comm('/ip/hotspot/user/print', array('?name' => $username));
if (empty($found)) {
$api->disconnect();
tg_api('answerCallbackQuery', array(
'callback_query_id' => $cb_id,
'text' => "User {$username} not found",
'show_alert' => true,
));
continue;
}
$uid = $found[0]['.id'];
$api->comm('/ip/hotspot/user/set', array('.id' => $uid, 'limit-uptime' => '0', 'comment' => ''));
$api->comm('/ip/hotspot/user/reset-counters', array('.id' => $uid));
$sch = $api->comm('/system/scheduler/print', array('?name' => $username));
if (!empty($sch) && isset($sch[0]['.id'])) {
$api->comm('/system/scheduler/remove', array('.id' => $sch[0]['.id']));
}
$api->disconnect();
tg_api('answerCallbackQuery', array(
'callback_query_id' => $cb_id,
'text' => "✅ {$username} reset!",
'show_alert' => false,
));
tg_api('sendMessage', array(
'chat_id' => '-1002554941429',
'message_thread_id' => 42,
'text' => "✅ *{$username}* has been reset\nReady to use again",
'parse_mode' => 'Markdown',
));
$processed++;
}
if ($last_id > 0) {
tg_api('getUpdates', array('offset' => $last_id + 1, 'timeout' => 1));
}
echo "processed: {$processed}";