comletely delete old lws, rewrite vk_sms_checker on python

This commit is contained in:
Evgeny Zinoviev 2024-02-20 00:56:00 +03:00
parent 952e41d594
commit 95ac1f0d67
16 changed files with 151 additions and 1143 deletions

75
bin/vk_sms_checker.py Executable file
View File

@ -0,0 +1,75 @@
#!/usr/bin/env python3
import __py_include
import re
from html import escape
from typing import Optional
from homekit.config import AppConfigUnit, config
from homekit.modem import ModemsConfig, E3372
from homekit.database import MySQLHomeDatabase
from homekit.telegram import send_message
db: Optional[MySQLHomeDatabase] = None
class VkSmsCheckerConfig(AppConfigUnit):
NAME = 'vk_sms_checker'
@classmethod
def schema(cls) -> Optional[dict]:
return {
'modem': {'type': 'string', 'required': True}
}
@staticmethod
def custom_validator(data):
if data['modem'] not in ModemsConfig():
raise ValueError('invalid modem')
def get_last_time() -> int:
cur = db.cursor()
cur.execute("SELECT last_message_time FROM vk_sms LIMIT 1")
return int(cur.fetchone()[0])
def set_last_time(timestamp: int) -> None:
cur = db.cursor()
cur.execute("UPDATE vk_sms SET last_message_time=%s", (timestamp,))
db.commit()
def check_sms():
modem = ModemsConfig()[config.app_config['modem']]
cl = E3372(modem['ip'], legacy_token_auth=modem['legacy_auth'])
messages = cl.sms_list()
messages.reverse()
last_time = get_last_time()
new_last_time = None
results = []
if not messages:
return
for m in messages:
if m['UnixTime'] <= last_time:
continue
new_last_time = m['UnixTime']
if re.match(r'^vk', m['Phone'], flags=re.IGNORECASE) or re.match(r'vk', m['Content'], flags=re.IGNORECASE):
results.append(m)
if results:
for m in results:
text = '<b>'+escape(m['Phone'])+'</b> ('+m['Date']+')'
text += "\n"+escape(m['Content'])
send_message(text=text, chat='vk_sms_checker')
if new_last_time:
set_last_time(new_last_time)
if __name__ == '__main__':
db = MySQLHomeDatabase()
config.load_app(VkSmsCheckerConfig)
check_sms()

View File

@ -6,6 +6,7 @@ __all__ = [
'get_clickhouse', 'get_clickhouse',
'SimpleState', 'SimpleState',
'MySQLHomeDatabase',
'SensorsDatabase', 'SensorsDatabase',
'InverterDatabase', 'InverterDatabase',
'BotsDatabase' 'BotsDatabase'
@ -14,12 +15,13 @@ __all__ = [
def __getattr__(name: str): def __getattr__(name: str):
if name in __all__: if name in __all__:
if name.endswith('Database'): ln = name.lower()
file = name[:-8].lower() if 'mysql' in ln:
elif 'mysql' in name:
file = 'mysql' file = 'mysql'
elif 'clickhouse' in name: elif 'clickhouse' in ln:
file = 'clickhouse' file = 'clickhouse'
elif name.endswith('Database'):
file = name[:-8].lower()
else: else:
file = 'simple_state' file = 'simple_state'

View File

@ -1,6 +1,6 @@
from .mysql import ( from .mysql import (
get_mysql as get_mysql, mysql_now as mysql_now,
mysql_now as mysql_now MySQLHomeDatabase as MySQLHomeDatabase
) )
from .clickhouse import get_clickhouse as get_clickhouse from .clickhouse import get_clickhouse as get_clickhouse

View File

@ -1,6 +1,6 @@
import pytz import pytz
from .mysql import mysql_now, MySQLDatabase, datetime_fmt from .mysql import mysql_now, MySQLDatabase, datetime_fmt, MySQLCredsConfig, MySQLCloudCredsConfig
from ..api.types import ( from ..api.types import (
SoundSensorLocation SoundSensorLocation
) )
@ -26,6 +26,9 @@ class OpenwrtLogRecord:
class BotsDatabase(MySQLDatabase): class BotsDatabase(MySQLDatabase):
def creds(self) -> MySQLCredsConfig:
return MySQLCloudCredsConfig()
def add_openwrt_logs(self, def add_openwrt_logs(self,
lines: List[Tuple[datetime, str]], lines: List[Tuple[datetime, str]],
access_point: int): access_point: int):

View File

@ -1,47 +1,74 @@
import time import time
import logging import logging
from abc import ABC, abstractmethod
from mysql.connector import connect, MySQLConnection, Error from mysql.connector import connect, MySQLConnection, Error
from typing import Optional from typing import Optional
from ..config import config from ..config import ConfigUnit
link: Optional[MySQLConnection] = None
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
datetime_fmt = '%Y-%m-%d %H:%M:%S' datetime_fmt = '%Y-%m-%d %H:%M:%S'
def get_mysql() -> MySQLConnection: class MySQLCredsConfig(ConfigUnit, ABC):
global link @classmethod
def schema(cls) -> Optional[dict]:
schema = {}
for k in ('host', 'database', 'user', 'password'):
schema[k] = dict(type='string', required=True)
return schema
if link is not None:
return link
link = connect( class MySQLHomeCredsConfig(MySQLCredsConfig):
host=config['mysql']['host'], NAME = 'mysql_home_creds'
user=config['mysql']['user'],
password=config['mysql']['password'],
database=config['mysql']['database'], class MySQLCloudCredsConfig(MySQLCredsConfig):
) NAME = 'mysql_cloud_creds'
link.time_zone = '+01:00'
return link
def mysql_now() -> str: def mysql_now() -> str:
return time.strftime('%Y-%m-%d %H:%M:%S') return time.strftime('%Y-%m-%d %H:%M:%S')
class MySQLDatabase: class MySQLDatabase(ABC):
def __init__(self): _enable_pings: bool
self.db = get_mysql() _link: MySQLConnection
_time_zone: Optional[str]
@abstractmethod
def creds(self) -> MySQLCredsConfig:
pass
def __init__(self, enable_pings=False, time_zone='+01:00'):
self._enable_pings = enable_pings
self._time_zone = time_zone
self._connect()
def _connect(self):
c = self.creds()
self._link = connect(
host=c['host'],
user=c['user'],
password=c['password'],
database=c['database'],
)
if self._time_zone:
self._link.time_zone = self._time_zone
def cursor(self, **kwargs): def cursor(self, **kwargs):
try: if self._enable_pings:
self.db.ping(reconnect=True, attempts=2) try:
except Error as e: self._link.ping(reconnect=True, attempts=2)
logger.exception(e) except Error as e:
self.db = get_mysql() logger.exception(e)
return self.db.cursor(**kwargs) self._connect()
return self._link.cursor(**kwargs)
def commit(self): def commit(self):
self.db.commit() self._link.commit()
class MySQLHomeDatabase(MySQLDatabase):
def creds(self) -> MySQLCredsConfig:
return MySQLHomeCredsConfig()

View File

@ -1,6 +1,7 @@
import requests import requests
import xml.etree.ElementTree as ElementTree import xml.etree.ElementTree as ElementTree
from datetime import datetime
from ..util import Addr from ..util import Addr
from enum import Enum from enum import Enum
from ..http import HTTPMethod from ..http import HTTPMethod
@ -21,14 +22,14 @@ class Error(Enum):
ERROR_WRONG_TOKEN = 125001 ERROR_WRONG_TOKEN = 125001
ERROR_WRONG_SESSION = 125002 ERROR_WRONG_SESSION = 125002
ERROR_WRONG_SESSION_TOKEN = 125003 ERROR_WRONG_SESSION_TOKEN = 125003
class WifiStatus(Enum): class WifiStatus(Enum):
WIFI_CONNECTING = '900' WIFI_CONNECTING = '900'
WIFI_CONNECTED = '901' WIFI_CONNECTED = '901'
WIFI_DISCONNECTED = '902' WIFI_DISCONNECTED = '902'
WIFI_DISCONNECTING = '903' WIFI_DISCONNECTING = '903'
class Cradle(Enum): class Cradle(Enum):
CRADLE_CONNECTING = '900' CRADLE_CONNECTING = '900'
@ -38,8 +39,8 @@ class Cradle(Enum):
CRADLE_CONNECTFAILED = '904' CRADLE_CONNECTFAILED = '904'
CRADLE_CONNECTSTATUSNULL = '905' CRADLE_CONNECTSTATUSNULL = '905'
CRANDLE_CONNECTSTATUSERRO = '906' CRANDLE_CONNECTSTATUSERRO = '906'
class MacroEVDOLevel(Enum): class MacroEVDOLevel(Enum):
MACRO_EVDO_LEVEL_ZERO = '0' MACRO_EVDO_LEVEL_ZERO = '0'
MACRO_EVDO_LEVEL_ONE = '1' MACRO_EVDO_LEVEL_ONE = '1'
@ -47,8 +48,8 @@ class MacroEVDOLevel(Enum):
MACRO_EVDO_LEVEL_THREE = '3' MACRO_EVDO_LEVEL_THREE = '3'
MACRO_EVDO_LEVEL_FOUR = '4' MACRO_EVDO_LEVEL_FOUR = '4'
MACRO_EVDO_LEVEL_FIVE = '5' MACRO_EVDO_LEVEL_FIVE = '5'
class MacroNetWorkType(Enum): class MacroNetWorkType(Enum):
MACRO_NET_WORK_TYPE_NOSERVICE = 0 MACRO_NET_WORK_TYPE_NOSERVICE = 0
MACRO_NET_WORK_TYPE_GSM = 1 MACRO_NET_WORK_TYPE_GSM = 1
@ -127,7 +128,7 @@ class E3372:
_get_raw_data: bool _get_raw_data: bool
_headers: dict[str, str] _headers: dict[str, str]
_authorized: bool _authorized: bool
def __init__(self, def __init__(self,
addr: Addr, addr: Addr,
need_auth: bool = True, need_auth: bool = True,
@ -174,7 +175,7 @@ class E3372:
def sms_count(self): def sms_count(self):
self.auth() self.auth()
return self.request('sms/sms-count') return self.request('sms/sms-count')
def sms_send(self, phone: str, text: str): def sms_send(self, phone: str, text: str):
self.auth() self.auth()
return self.request('sms/send-sms', HTTPMethod.POST, { return self.request('sms/send-sms', HTTPMethod.POST, {
@ -204,6 +205,7 @@ class E3372:
messages = [] messages = []
for message_elem in root.find('Messages').findall('Message'): for message_elem in root.find('Messages').findall('Message'):
message_dict = {child.tag: child.text for child in message_elem} message_dict = {child.tag: child.text for child in message_elem}
message_dict['UnixTime'] = int(datetime.strptime(message_dict['Date'], '%Y-%m-%d %H:%M:%S').timestamp())
messages.append(message_dict) messages.append(message_dict)
return messages return messages

View File

@ -11,7 +11,7 @@ _logger = logging.getLogger(__name__)
def send_message(text: str, def send_message(text: str,
chat: str, chat: str,
parse_mode: str = 'HTML', parse_mode: str = 'HTML',
disable_web_page_preview: bool = False,): disable_web_page_preview: bool = False):
data, token = _send_telegram_data(text, chat, parse_mode, disable_web_page_preview) data, token = _send_telegram_data(text, chat, parse_mode, disable_web_page_preview)
req = requests.post('https://api.telegram.org/bot%s/sendMessage' % token, data=data) req = requests.post('https://api.telegram.org/bot%s/sendMessage' % token, data=data)
return req.json() return req.json()

View File

@ -1,37 +0,0 @@
<?php
class TelegramBotClient {
protected string $token;
public function __construct(string $token) {
$this->token = $token;
}
public function sendMessage(int $chat_id, string $text): bool {
$ch = curl_init();
$url = 'https://api.telegram.org/bot'.$this->token.'/sendMessage';
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_POSTFIELDS, [
'chat_id' => $chat_id,
'text' => $text,
'parse_mode' => 'html',
'disable_web_page_preview' => 1
]);
$body = curl_exec($ch);
curl_close($ch);
$resp = jsonDecode($body);
if (!$resp['ok']) {
debugError(__METHOD__ . ': ' . $body);
return false;
}
return true;
}
}

View File

@ -1,16 +0,0 @@
{
"name": "ch1p/localwebsite.homekit",
"type": "project",
"require": {
"twig/twig": "^3.3",
"ext-mbstring": "*",
"ext-sockets": "*",
"ext-simplexml": "*",
"ext-curl": "*",
"ext-json": "*",
"ext-gmp": "*",
"ext-sqlite3": "*",
"giggsey/libphonenumber-for-php": "^8.12"
},
"license": "MIT"
}

View File

@ -1,341 +0,0 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "aad58d1c2f9900517de6f62599845b12",
"packages": [
{
"name": "giggsey/libphonenumber-for-php",
"version": "8.12.51",
"source": {
"type": "git",
"url": "https://github.com/giggsey/libphonenumber-for-php.git",
"reference": "a42d89a46797083a95aa48393485fdac22fcac94"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/a42d89a46797083a95aa48393485fdac22fcac94",
"reference": "a42d89a46797083a95aa48393485fdac22fcac94",
"shasum": ""
},
"require": {
"giggsey/locale": "^1.7|^2.0",
"php": ">=5.3.2",
"symfony/polyfill-mbstring": "^1.17"
},
"require-dev": {
"pear/pear-core-minimal": "^1.9",
"pear/pear_exception": "^1.0",
"pear/versioncontrol_git": "^0.5",
"phing/phing": "^2.7",
"php-coveralls/php-coveralls": "^1.0|^2.0",
"symfony/console": "^2.8|^3.0|^v4.4|^v5.2",
"symfony/phpunit-bridge": "^4.2 || ^5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "8.x-dev"
}
},
"autoload": {
"psr-4": {
"libphonenumber\\": "src/"
},
"exclude-from-classmap": [
"/src/data/",
"/src/carrier/data/",
"/src/geocoding/data/",
"/src/timezone/data/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Joshua Gigg",
"email": "giggsey@gmail.com",
"homepage": "https://giggsey.com/"
}
],
"description": "PHP Port of Google's libphonenumber",
"homepage": "https://github.com/giggsey/libphonenumber-for-php",
"keywords": [
"geocoding",
"geolocation",
"libphonenumber",
"mobile",
"phonenumber",
"validation"
],
"support": {
"irc": "irc://irc.appliedirc.com/lobby",
"issues": "https://github.com/giggsey/libphonenumber-for-php/issues",
"source": "https://github.com/giggsey/libphonenumber-for-php"
},
"time": "2022-07-11T08:12:34+00:00"
},
{
"name": "giggsey/locale",
"version": "2.2",
"source": {
"type": "git",
"url": "https://github.com/giggsey/Locale.git",
"reference": "9c1dca769253f6a3e81f9a5c167f53b6a54ab635"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/giggsey/Locale/zipball/9c1dca769253f6a3e81f9a5c167f53b6a54ab635",
"reference": "9c1dca769253f6a3e81f9a5c167f53b6a54ab635",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"require-dev": {
"ext-json": "*",
"pear/pear-core-minimal": "^1.9",
"pear/pear_exception": "^1.0",
"pear/versioncontrol_git": "^0.5",
"phing/phing": "^2.7",
"php-coveralls/php-coveralls": "^2.0",
"phpunit/phpunit": "^8.5|^9.5",
"symfony/console": "^5.0",
"symfony/filesystem": "^5.0",
"symfony/finder": "^5.0",
"symfony/process": "^5.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Giggsey\\Locale\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Joshua Gigg",
"email": "giggsey@gmail.com",
"homepage": "https://giggsey.com/"
}
],
"description": "Locale functions required by libphonenumber-for-php",
"support": {
"issues": "https://github.com/giggsey/Locale/issues",
"source": "https://github.com/giggsey/Locale/tree/2.2"
},
"time": "2022-04-06T07:33:59+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.23.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce",
"reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"time": "2021-02-19T12:13:01+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.23.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2df51500adbaebdc4c38dea4c89a2e131c45c8a1",
"reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"time": "2021-05-27T09:27:20+00:00"
},
{
"name": "twig/twig",
"version": "v3.3.2",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "21578f00e83d4a82ecfa3d50752b609f13de6790"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/21578f00e83d4a82ecfa3d50752b609f13de6790",
"reference": "21578f00e83d4a82ecfa3d50752b609f13de6790",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3"
},
"require-dev": {
"psr/container": "^1.0",
"symfony/phpunit-bridge": "^4.4.9|^5.0.9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.3-dev"
}
},
"autoload": {
"psr-4": {
"Twig\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com",
"homepage": "http://fabien.potencier.org",
"role": "Lead Developer"
},
{
"name": "Twig Team",
"role": "Contributors"
},
{
"name": "Armin Ronacher",
"email": "armin.ronacher@active-4.com",
"role": "Project Founder"
}
],
"description": "Twig, the flexible, fast, and secure template language for PHP",
"homepage": "https://twig.symfony.com",
"keywords": [
"templating"
],
"time": "2021-05-16T12:14:13+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"ext-mbstring": "*",
"ext-sockets": "*",
"ext-simplexml": "*",
"ext-curl": "*",
"ext-json": "*",
"ext-gmp": "*",
"ext-sqlite3": "*"
},
"platform-dev": [],
"plugin-api-version": "2.0.0"
}

View File

@ -1,95 +0,0 @@
<?php
return [
'group' => 'www-data',
'files_mode' => 0664,
'dirs_mode' => 0775,
'is_dev' => true,
'static_public_path' => '/assets',
'openwrt_ip' => '192.168.1.1',
'inverterd_host' => '192.168.1.2',
'inverterd_port' => 8305,
'pump_host' => '192.168.1.2',
'pump_port' => 8307,
'temphumd_servers' => [
// fill here, example:
'hall' => ['192.168.1.3', 8306, 'Big Hall'/*, optional: config::TEMPHUMD_NO_HUM */],
],
// modem names (array keys) must match ipset names and
// routing table names on the openwrt router
//
// the order of the keys in the array must be the same
// as the order in which fwmark iptables rules are applied
'modems' => [
'modem-example' => [
'ip' => '1.2.3.4',
'label' => 'Modem Name',
'short_label' => 'Mname',
'legacy_token_auth' => false,
],
],
// 'routing_smallhome_ip' => 'fill_me',
// 'routing_default' => 'fill_me',
'debug_backtrace' => true,
'debug_file' => '.debug.log',
'twig_cache' => true,
'templates' => [
'web' => [
'root' => 'templates-web',
'cache' => 'cache/templates-web',
],
],
'static' => [
'app.css' => 12,
'app.js' => 7,
'polyfills.js' => 1,
'modem.js' => 2,
'inverter.js' => 2,
'h265webjs-dist/h265webjs-v20221106.js' => 3,
'h265webjs-dist/h265webjs-v20221106-reminified.js' => 1,
'h265webjs-dist/missile.js' => 1,
],
'cam_hls_access_key' => '',
'cam_hls_proto' => 'http', // bool|callable
'cam_hls_host' => '192.168.1.1', // bool|callable
'cam_list' => [
'low' => [
// fill me with names
],
'high' => [
// fill me with names
],
'labels' => [
// assoc array
],
],
'vk_sms_checker' => [
'telegram_token' => '',
'telegram_chat_id' => '',
'modem_name' => '', // reference to the 'modems' array
],
'database_path' => getenv('HOME').'/.config/homekit.localwebsite.sqlite3',
'auth_cookie_host' => '',
'auth_need' => false, // bool|callable
'auth_pw_salt' => '',
'grafana_sensors_url' => '',
'grafana_inverter_url' => '',
'ipcam_server_api_addr' => '',
'dhcp_hostname_overrides' => [],
];

View File

@ -1,44 +0,0 @@
#!/usr/bin/env php
<?php
// this scripts pulls recent inbox from e3372 modem,
// looks for new messages from vk and re-sends them
// to the telegram group
require_once __DIR__.'/../init.php';
global $config;
$cfg = $config['modems'][$config['vk_sms_checker']['modem_name']];
$e3372 = new E3372($cfg['ip'], $cfg['legacy_token_auth']);
$db = getDB();
$last_processed = $db->querySingle("SELECT last_message_time FROM vk_processed");
$new_last_processed = 0;
$messages = $e3372->getSMSList();
$messages = array_reverse($messages);
$results = [];
if (!empty($messages)) {
foreach ($messages as $m) {
if ($m['timestamp'] <= $last_processed)
continue;
$new_last_processed = $m['timestamp'];
if (preg_match('/^vk/i', $m['phone']) || preg_match('/vk/i', $m['content']))
$results[] = $m;
}
}
if (!empty($results)) {
$t = new TelegramBotClient($config['vk_sms_checker']['telegram_token']);
foreach ($results as $m) {
$text = '<b>'.htmlescape($m['phone']).'</b> ('.$m['date'].')';
$text .= "\n".htmlescape($m['content']);
$t->sendMessage($config['vk_sms_checker']['telegram_chat_id'], $text);
}
}
if ($new_last_processed != 0)
$db->exec("UPDATE vk_processed SET last_message_time=?", $new_last_processed);

View File

@ -1,131 +0,0 @@
<?php
class database {
const SCHEMA_VERSION = 2;
protected SQLite3 $link;
public function __construct(string $db_path) {
$will_create = !file_exists($db_path);
$this->link = new SQLite3($db_path);
if ($will_create)
setperm($db_path);
$this->link->enableExceptions(true);
$this->upgradeSchema();
}
protected function upgradeSchema() {
$cur = $this->getSchemaVersion();
if ($cur == self::SCHEMA_VERSION)
return;
if ($cur < 1) {
$this->link->exec("CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT,
password TEXT
)");
}
if ($cur < 2) {
$this->link->exec("CREATE TABLE vk_processed (
last_message_time INTEGER
)");
$this->link->exec("INSERT INTO vk_processed (last_message_time) VALUES (0)");
}
$this->syncSchemaVersion();
}
protected function getSchemaVersion() {
return $this->link->query("PRAGMA user_version")->fetchArray()[0];
}
protected function syncSchemaVersion() {
$this->link->exec("PRAGMA user_version=".self::SCHEMA_VERSION);
}
protected function prepareQuery(string $sql): string {
if (func_num_args() > 1) {
$mark_count = substr_count($sql, '?');
$positions = array();
$last_pos = -1;
for ($i = 0; $i < $mark_count; $i++) {
$last_pos = strpos($sql, '?', $last_pos + 1);
$positions[] = $last_pos;
}
for ($i = $mark_count - 1; $i >= 0; $i--) {
$arg_val = func_get_arg($i + 1);
if (is_null($arg_val)) {
$v = 'NULL';
} else {
$v = '\''.$this->link->escapeString($arg_val) . '\'';
}
$sql = substr_replace($sql, $v, $positions[$i], 1);
}
}
return $sql;
}
public function query(string $sql, ...$params): SQLite3Result {
return $this->link->query($this->prepareQuery($sql, ...$params));
}
public function exec(string $sql, ...$params) {
return $this->link->exec($this->prepareQuery($sql, ...$params));
}
public function querySingle(string $sql, ...$params) {
return $this->link->querySingle($this->prepareQuery($sql, ...$params));
}
public function querySingleRow(string $sql, ...$params) {
return $this->link->querySingle($this->prepareQuery($sql, ...$params), true);
}
protected function performInsert(string $command, string $table, array $fields): SQLite3Result {
$names = [];
$values = [];
$count = 0;
foreach ($fields as $k => $v) {
$names[] = $k;
$values[] = $v;
$count++;
}
$sql = "{$command} INTO `{$table}` (`" . implode('`, `', $names) . "`) VALUES (" . implode(', ', array_fill(0, $count, '?')) . ")";
array_unshift($values, $sql);
return call_user_func_array([$this, 'query'], $values);
}
public function insert(string $table, array $fields): SQLite3Result {
return $this->performInsert('INSERT', $table, $fields);
}
public function replace(string $table, array $fields): SQLite3Result {
return $this->performInsert('REPLACE', $table, $fields);
}
public function insertId(): int {
return $this->link->lastInsertRowID();
}
public function update($table, $rows, ...$cond): SQLite3Result {
$fields = [];
$args = [];
foreach ($rows as $row_name => $row_value) {
$fields[] = "`{$row_name}`=?";
$args[] = $row_value;
}
$sql = "UPDATE `$table` SET " . implode(', ', $fields);
if (!empty($cond)) {
$sql .= " WHERE " . $cond[0];
if (count($cond) > 1)
$args = array_merge($args, array_slice($cond, 1));
}
return $this->query($sql, ...$args);
}
}

View File

@ -1,300 +0,0 @@
<?php
function param($key) {
global $RouterInput;
$val = null;
if (isset($RouterInput[$key])) {
$val = $RouterInput[$key];
} else if (isset($_POST[$key])) {
$val = $_POST[$key];
} else if (isset($_GET[$key])) {
$val = $_GET[$key];
}
if (is_array($val)) {
$val = implode($val);
}
return $val;
}
function str_replace_once(string $needle, string $replace, string $haystack): string {
$pos = strpos($haystack, $needle);
if ($pos !== false) {
$haystack = substr_replace($haystack, $replace, $pos, strlen($needle));
}
return $haystack;
}
function htmlescape($s) {
if (is_array($s)) {
foreach ($s as $k => $v) {
$s[$k] = htmlescape($v);
}
return $s;
}
return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}
function jsonEncode($obj) {
return json_encode($obj, JSON_UNESCAPED_UNICODE);
}
function jsonDecode($json) {
return json_decode($json, true);
}
function startsWith(string $haystack, string $needle): bool {
return $needle === "" || strpos($haystack, $needle) === 0;
}
function endsWith(string $haystack, string $needle): bool {
return $needle === "" || substr($haystack, -strlen($needle)) === $needle;
}
function exectime($format = null) {
$time = round(microtime(true) - START_TIME, 4);
if (!is_null($format)) {
$time = sprintf($format, $time);
}
return $time;
}
function stransi($s) {
static $colors = [
'black' => 0,
'red' => 1,
'green' => 2,
'yellow' => 3,
'blue' => 4,
'magenta' => 5,
'cyan' => 6,
'white' => 7
];
static $valid_styles = ['bold', 'fgbright', 'bgbright'];
$s = preg_replace_callback('/<(?:e ([a-z, =]+)|\/e)>/', function($match) use ($colors, $valid_styles) {
if (empty($match[1])) {
return "\033[0m";
} else {
$codes = [];
$args = preg_split('/ +/', $match[1]);
$fg = null;
$bg = null;
$styles = [];
foreach ($args as $arg) {
list($argname, $argvalue) = explode('=', $arg);
$err = false;
if ($argname == 'fg' || $argname == 'bg') {
if (isset($colors[$argvalue])) {
$$argname = $colors[$argvalue];
} else {
$err = true;
}
} else if ($argname == 'style') {
$argstyles = array_filter(explode(',', $argvalue));
foreach ($argstyles as $style) {
if (!in_array($style, $valid_styles)) {
$err = true;
break;
}
}
if (!$err) {
foreach ($argstyles as $style) {
$styles[$style] = true;
}
}
} else {
$err = true;
}
if ($err) {
trigger_error(__FUNCTION__.": unrecognized argument {$arg}", E_USER_WARNING);
}
}
if (!is_null($fg)) {
$codes[] = $fg + (isset($styles['fgbright']) ? 90 : 30);
}
if (!is_null($bg)) {
$codes[] = $bg + (isset($styles['bgbright']) ? 100 : 40);
}
if (isset($styles['bold'])) {
$codes[] = 1;
}
return !empty($codes) ? "\033[".implode(';', $codes)."m" : '';
}
}, $s);
return $s;
}
function strgen($len = 10): string {
$buf = '';
for ($i = 0; $i < $len; $i++) {
$j = mt_rand(0, 61);
if ($j >= 36) {
$j += 13;
} else if ($j >= 10) {
$j += 7;
}
$buf .= chr(48 + $j);
}
return $buf;
}
function setperm($file, $is_dir = null) {
global $config;
// chgrp
$gid = filegroup($file);
$gname = posix_getgrgid($gid);
if (!is_array($gname)) {
debugError(__FUNCTION__.": posix_getgrgid() failed on $gid", $gname);
} else {
$gname = $gname['name'];
}
if ($gname != $config['group']) {
if (!chgrp($file, $config['group'])) {
debugError(__FUNCTION__.": chgrp() failed on $file");
}
}
// chmod
$perms = fileperms($file);
$need_perms = is_dir($file) ? $config['dirs_mode'] : $config['files_mode'];
if (($perms & $need_perms) !== $need_perms) {
if (!chmod($file, $need_perms)) {
debugError(__FUNCTION__.": chmod() failed on $file");
}
}
}
function redirect($url, $preserve_utm = true, $no_ajax = false) {
if (PHP_SAPI != 'cli' && $_SERVER['REQUEST_METHOD'] == 'GET' && $preserve_utm) {
$proxy_params = ['utm_source', 'utm_medium', 'utm_content', 'utm_campaign'];
$params = [];
foreach ($proxy_params as $p) {
if (!empty($_GET[$p])) {
$params[$p] = (string)$_GET[$p];
}
}
if (!empty($params)) {
if (($anchor_pos = strpos($url, '#')) !== false) {
$anchor = substr($url, $anchor_pos+1);
$url = substr($url, 0, $anchor_pos);
}
$url .= (strpos($url, '?') === false ? '?' : '&').http_build_query($params);
if ($anchor_pos !== false) {
$url .= '#'.$anchor;
}
}
}
header('Location: ' . $url);
exit;
}
function is_xhr_request(): bool {
return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest';
}
function secondsToTime(int $n): string {
$parts = [];
if ($n >= 86400) {
$days = floor($n / 86400);
$n %= 86400;
$parts[] = "{$days}д";
}
if ($n >= 3600) {
$hours = floor($n / 3600);
$n %= 3600;
$parts[] = "{$hours}ч";
}
if ($n >= 60) {
$minutes = floor($n / 60);
$n %= 60;
$parts[] = "{$minutes}мин";
}
if ($n)
$parts[] = "{$n}сек";
return implode(' ', $parts);
}
function bytesToUnitsLabel(GMP $b): string {
$ks = array('B', 'Kb', 'Mb', 'Gb', 'Tb');
foreach ($ks as $i => $k) {
if (gmp_cmp($b, gmp_pow(1024, $i + 1)) < 0) {
if ($i == 0)
return gmp_strval($b) . ' ' . $k;
$n = gmp_intval(gmp_div_q($b, gmp_pow(1024, $i)));
return round($n, 2).' '.$k;
}
}
return gmp_strval($b);
}
function pwhash(string $s): string {
return hash('sha256', config::get('auth_pw_salt').'|'.$s);
}
$ShutdownFunctions = [];
function append_shutdown_function(callable $f) {
global $ShutdownFunctions;
$ShutdownFunctions[] = $f;
}
function prepend_shutdown_function(callable $f) {
global $ShutdownFunctions;
array_unshift($ShutdownFunctions, $f);
}
function getDB(): database {
static $link = null;
if (is_null($link))
$link = new database(config::get('database_path'));
return $link;
}
function to_camel_case(string $input, string $separator = '_'): string {
return lcfirst(str_replace($separator, '', ucwords($input, $separator)));
}
function from_camel_case(string $s): string {
$buf = '';
$len = strlen($s);
for ($i = 0; $i < $len; $i++) {
if (!ctype_upper($s[$i])) {
$buf .= $s[$i];
} else {
$buf .= '_'.strtolower($s[$i]);
}
}
return $buf;
}
function unsetcookie(string $name) {
global $config;
setcookie($name, null, -1, '/', $config['auth_cookie_host']);
}
function setcookie_safe(...$args) {
global $config;
if (!headers_sent()) {
if (count($args) == 2)
setcookie($args[0], $args[1], time()+86400*365, '/', $config['auth_cookie_host']);
else
setcookie(...$args);
}
}

View File

@ -1,71 +0,0 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
mb_internal_encoding('UTF-8');
mb_regex_encoding('UTF-8');
register_shutdown_function(function() {
global $ShutdownFunctions;
if (!empty($ShutdownFunctions)) {
foreach ($ShutdownFunctions as $f)
$f();
}
});
spl_autoload_register(function($class) {
if (endsWith($class, 'Handler'))
$path = ROOT.'/handlers/'.$class.'.php';
// engine classes
else if (in_array($class, ['request_handler', 'router', 'model', 'debug', 'database']))
$path = ROOT.'/engine/'.$class.'.php';
else if ($class == 'Lang')
$path = ROOT.'/engine/lang.php';
else if (endsWith($class, '_tpl'))
$path = ROOT.'/engine/tpl.php';
// other classes
else
$path = ROOT.'/classes/'.$class.'.php';
if (strpos($path, '\\') !== false)
$path = str_replace('\\', '/', $path);
if (is_file($path))
require_once $path;
});
define('ROOT', __DIR__);
define('START_TIME', microtime(true));
set_include_path(get_include_path().PATH_SEPARATOR.ROOT);
require_once ROOT.'/functions.php';
$config = require ROOT.'/config.php';
if (!is_file(ROOT.'/config.local.php'))
die('config.local.php not found');
$config = array_merge($config, require_once ROOT.'/config.local.php');
// it's better to start logging as early as possible
$debug = debug::getInstance(
function($errno, $errfile, $errlne, $errstr) {
// it's not our fault that some vendor package uses something that's deprecated
// so let's not spam our logs
if ($errno == E_USER_DEPRECATED && startsWith($errfile, ROOT.'/vendor/'))
return false;
return true;
}
);
$debug->setMessagesStoreType(debug::STORE_FILE);
$debug->setErrorsStoreType(debug::STORE_FILE);
$debug->enable();
unset($debug);
// composer
require_once ROOT.'/vendor/autoload.php';

View File

@ -1,66 +0,0 @@
#!/usr/bin/env php
<?php
require_once __DIR__.'/init.php';
function read_stdin(?string $prompt = null, bool $multiline = true) {
if (!is_null($prompt))
echo $prompt;
if (!$multiline)
return trim(fgets(STDIN));
$fp = fopen('php://stdin', 'r');
$data = stream_get_contents($fp);
fclose($fp);
return $data;
}
function usage() {
global $argv;
echo <<<EOF
usage: {$argv[0]} COMMAND
Supported commands:
add-user
change-password
EOF;
exit(1);
}
if (empty($argv[1]))
usage();
switch ($argv[1]) {
case 'add-user':
$username = read_stdin('enter username: ', false);
$password = read_stdin('enter password: ', false);
if (users::exists($username)) {
fwrite(STDERR, "user already exists\n");
exit(1);
}
$id = users::add($username, $password);
echo "added user, id = $id\n";
break;
case 'change-password':
$id = (int)read_stdin('enter ID: ', false);
if (!$id)
die("invalid id\n");
$password = read_stdin('enter new password: ', false);
if (!$password)
die("invalid password\n");
users::setPassword($id, $password);
break;
default:
fwrite(STDERR, "invalid command\n");
exit(1);
}