1
0
Fork 0

Compare commits

...

12 Commits

7 changed files with 643 additions and 238 deletions

View File

@ -1,2 +1,50 @@
# parser_from_interneturok
Chat-robot Telegram for parsing homeworks by subject name, grade and number of the week
## Funny story of development
The customer of this project **refused to pay** the second half of the payment for the work and wanted to steal my code.<br>
<br>
I managed to delete all the code from the server, then i changed passwords on all **40 accounts** that were planned to be used in this chat-robot.<br>
Thus **i destroyed the entire mini-business** of this customer.
## Dependencies
1. [PHP 8.4](https://www.php.net/releases/8.4/en.php)
2. [Composer](https://getcomposer.org/) (php package manager)
3. [MINIMAL](https://git.svoboda.works/mirzaev/minimal) (PHP framework)
5. [Zanzara](https://github.com/badfarm/zanzara) (Telegram framework + ReactPHP)
6. [Baza](https://git.svoboda.works/mirzaev/baza) (binary database)
7. [NGINX](https://nginx.org/en/) (web server) *(can be replaced)*
8. [SystemD](https://systemd.io/) (service manager) *(can be replaced)*
<small>You can find other dependencies in the file `/composer.json`</small>
## Installation
### SystemD (or any alternative you like)
You can copy an example of systemd file from here: `/examples/systemd/ parser_from_interneturok-telegram.service`<br><br>
**Execute:** `sudo cp parser_from_interneturok-telegram.service /etc/systemd/system/ parser_from_interneturok-telegram.service && sudo chmod +x /etc/systemd/system/ parser_from_interneturok-telegram.service`<br><br>
*before you execute the command think about **what it does** and whether the **paths** are specified correctly*<br>
*the configuration file is very simple and you can remake it for any alternative to SystemD that you like*
### InternetUrok accounts
Fill in the file: `/mirzaev/parser_from_interneturok/system/storage/accounts.csv`<br><br>
**Format:** "mail password" (**separated by 1 space symbol**)
```CSV
mail password
mail password
mail password
```
### Authorized Telegram accounts
Fill in the file: `/mirzaev/parser_from_interneturok/system/settings/accounts.php`
```php
return [
1053489457, // Arsen Mirzaev Tatyano-Muradovich @redloser
]
```
<small>You can get the telegram account identifier by [@RawDataBot](https://t.me/RawDataBot)</small>
### Chat-robot Telegram token
Fill in the file: `/mirzaev/parser_from_interneturok/system/settings/telegram.php`<br>
<small>You can get the chat-robot telegram token by [@BotFather](https://t.me/BotFather)</small>

View File

@ -5,7 +5,7 @@ Wants=network.target
After=syslog.target network-online.target
[Service]
ExecStart=sudo -u www-data /usr/bin/php /var/www/project/mirzaev/parser_from_interneturok/system/public/telegram.php
ExecStart=sudo -u www-data /usr/bin/php /var/www/parser_from_interneturok/mirzaev/parser_from_interneturok/system/public/telegram.php
PIDFile=/var/run/php/parser_from_interneturok-telegram.pid
RemainAfterExit=no
RuntimeMaxSec=3600s

View File

@ -70,6 +70,17 @@ final class interneturok extends core
*/
public const string USER = 'https://api-gw.interneturok.ru/api/v2/homeschool/current_user';
/**
* TARIFFS
*
* Method: GET
* Authrorization: Bearer
*
* @var const string TARIFFS The user tariffs API URL
*/
public const string TARIFFS = 'https://api-gw.interneturok.ru/api/v2/schedules/users/current/tariffs';
/**
* SCHEDULES
*
@ -163,11 +174,12 @@ final class interneturok extends core
*
* @param subject $subject The subject
* @param int $grade The grade
* @param datetime $date The date
* @param int $waiting Interval for processing requests (seconds)
*
* @return array|false Downloaded homework files
*/
public function parse(subject $subject, int $grade, int $waiting = 3): array|false
public function parse(subject $subject, int $grade, datetime $date, int $week, int $waiting = 3): array|false
{
// Initializing accounts
$accounts = static::accounts();
@ -184,6 +196,7 @@ final class interneturok extends core
// Waiting for processing the request
sleep($waiting);
try {
if ($this->authentication($account)->wait()) {
// Authenticated the account
@ -194,7 +207,7 @@ final class interneturok extends core
sleep($waiting);
// Initializing the user journal
$journal = $this->journal(grade: $grade)->wait();
$journal = $this->journal(grade: $grade, week: $week)->wait();
if (!empty($journal)) {
// Initialized the user journal
@ -202,9 +215,6 @@ final class interneturok extends core
// Writing into the output buffer
echo "Инициализирован журнал\n";
// Initializing the actual date
$date = new datetime();
// Initializing the homeworks database
$model = new homework();
@ -227,7 +237,7 @@ final class interneturok extends core
// Initialized the target subject event (the homework exists)
if (new datetime($event->date) <= $date && new datetime($event->date)->modify('+6 days') >= $date) {
// Found the current event
// Found the target event week
if ($event->subject?->name === $subject->value && $event->subject->grade === $grade) {
// Found the target subject
@ -235,8 +245,8 @@ final class interneturok extends core
// Writing into the output buffer
echo "Найден школьный предмет: $subject->value для $grade класса (с " . new datetime($event->date)->format('d.m') . ' по ' . new datetime($event->date)->modify('+6 days')->format('d.m') . ")\n";
// Initializing the unblock time
$unblock = svoboda::timestamp() - 31536000;
// Initializing the current time
$now = svoboda::timestamp();
foreach ($event->homeworks as $homework) {
// Iterating over scheduled event homeworks
@ -244,19 +254,6 @@ final class interneturok extends core
if ($homework->status === 'checked' && $homework->mark === 5) {
// Homework checked and completed for a grade of 5
// Checking for the block record
$blocked = $model->database->read(
filter: fn(record $record) => $record->identifier === $homework->item_id && $record->created > $unblock,
amount: 1
)[0] ?? null;
if ($blocked instanceof record) {
// The account homework has been downloaded for 1 year
continue 3;
} else {
// The account homework has not been downloaded for 1 year
// Writing into the output buffer
echo "Найдено домашнее задание: $homework->item_id с оценкой $homework->mark\n";
@ -272,32 +269,37 @@ final class interneturok extends core
// Writing into the output buffer
echo "Получено домашнее задание\n";
// Blocking the homework downloading for 1 year
$blocked = $model->create(identifier: $homework->item_id);
// Declaring the homework downloads for 1 last year counter
$year = 0;
if ($blocked !== false) {
// The homework was blocked for 1 year
// Counting the homework downloading for 1 last year
$model->database->read(
filter: function (record $record) use ($homework, $now, &$year) {
if ($record->identifier === $homework->item_id && $now - $record->created < 31536000) {
// Found a downloading for 1 last year
// Writing into the output buffer
echo "Заблокировано на 1 год домашнее задание\n";
// Increasing the homework downloads for 1 last year counter
++$year;
}
// Exit (success)
return $files;
}
}
}
}
}
}
return false;
},
amount: 100
);
// Stopping processing events and starting processing the next account
break 2;
}
}
}
// Writing the homework downloading record
$model->create(identifier: $homework->item_id);
// Stopping processing events and starting processing the next account
break;
// Exit (success)
return ['downloads' => ['year' => $year], 'files' => $files];
}
}
}
}
}
}
}
}
}
} else {
@ -318,6 +320,9 @@ final class interneturok extends core
// Proceed to processing the next account
continue;
}
} catch (exception $exception) {
continue;
}
}
}
}
@ -453,10 +458,11 @@ final class interneturok extends core
* Search for the user homeworks journal
*
* @param int $grade The grade
* @param int $week Number of the week
*
* @return promise|null The journal data (object)
*/
private function journal(int $grade): promise|null
private function journal(int $grade, int $week): promise|null
{
if (!empty($this->token)) {
// Initialized the account authorization token
@ -471,16 +477,38 @@ final class interneturok extends core
);
return $this->browser->sendAsync($request)
->then(function ($response) use ($grade) {
->then(function ($response) use ($grade, $week) {
// Sended the request and received the response
// Initializing the user data
$user = json_decode((string) $response->getBody())?->data?->homeschool?->response?->user;
if ($user->grade + 1 === $grade) {
// Matched the account grade with the grade
if (!empty($user)) {
// Initialized the user data
// Initialiint the user identifier
// Initializing the request to the user data API
$request = new request(
'GET',
static::TARIFFS,
[
'Authorization' => "Bearer $this->token"
]
);
return $this->browser->sendAsync($request)
->then(function ($response) use ($grade, $week, $user) {
// Sended the request and received the response
// Initializing the tariffs data
$tariffs = json_decode((string) $response->getBody())?->data?->tariffs?->response;
foreach ($tariffs as $tariff) {
// Iterating over tariffs
if ($tariff->grade === $grade) {
// Matched the tariff grade with the target grade
// Initializing the user identifier
$identifier = $user?->id;
if (!empty($identifier)) {
@ -496,7 +524,7 @@ final class interneturok extends core
);
return $this->browser->sendAsync($request)
->then(function ($response) use ($grade, $identifier) {
->then(function ($response) use ($grade, $identifier, $week) {
// Sended the request and received the response
// Initializing the user schedules
@ -524,7 +552,7 @@ final class interneturok extends core
);
return $this->browser->sendAsync($request)
->then(function ($response) use ($grade, $identifier, $year) {
->then(function ($response) use ($grade, $identifier, $year, $week) {
// Sended the request and received the response
// Initializing the moscow time
@ -537,25 +565,10 @@ final class interneturok extends core
$now = new datetime($time);
// Declaring the quarter buffer
$quarter = null;
$quarter = static::quarter(number: $week);
foreach ($year->quarters as $value) {
// Iterating over study year quarters
// Implementing the quarter start time and end time
$start = new datetime($value->starts_at);
$end = new datetime($value->ends_at);
if ($now > $start && $now < $end) {
// Found the current quarter
// Writing the current quarter identifier into the quarter buffer
$quarter = $value->quarter;
// Stopping iterating over study year quarters
break;
}
}
if ($quarter !== false) {
// Initialized number of the quarter
// Initializing the request to the user journal API
$request = new request(
@ -582,6 +595,12 @@ final class interneturok extends core
return false;
}
})->wait();
} else {
// Not initialized number of the quarter
// Exit (fail)
return false;
}
} else {
// Not initialized the moscow time
@ -604,8 +623,13 @@ final class interneturok extends core
// Exit (fail)
return false;
}
}
}
// Exit (fail)
return false;
});
} else {
// Not matched the account grade with the grade
// Not initialized the user data
// Exit (fail)
return false;
@ -791,8 +815,19 @@ final class interneturok extends core
if ($attachment->uploader_role === 'student' && $attachment->attachable_type === 'Result::Homework') {
// Found the homework attachment
// Searching for number of the attachment and the file extension
preg_match('/^(\d+).*(\.\w{3,4})$/', $attachment->attach_path, $matches);
try {
// Initializing the name of the attachment file
$name = $matches[1] . $matches[2];
} catch (exception $exception) {
// Initializing the name of the attachment file
$name = uniqid() . '.jpg';
}
// Initializing path to the downloaded file
$path = $storage . DIRECTORY_SEPARATOR . $attachment->attach_path;
$path = $storage . DIRECTORY_SEPARATOR . ($name ?? uniqid() . '.jpg');
// Downloading the file
$this->browser->request('GET', $attachment->attach, ['sink' => $path]);
@ -824,17 +859,17 @@ final class interneturok extends core
// Exit (fail)
return false;
},
function ($response) use ($homework) {
if ($response->getStatusCode() === 402) {
function ($exception) use ($homework) {
if ($exception->getResponse()?->getStatusCode() === 402) {
// Fail (received the "Payment Required" status code)
// Writing into the output buffer
echo "Не удалось скачать домашнее задание $homework потому, что не оплачен тариф\n";
}
// Exit (fail)
return false;
}
}
);
}
@ -900,6 +935,7 @@ final class interneturok extends core
*/
public static function amount(): int
{
try {
// Opening the interneturok accounts file
$file = new spl(INTERNETUROK_ACCOUNTS_FILE, 'r');
@ -911,5 +947,58 @@ final class interneturok extends core
// Exit (success)
return $rows;
} catch (exception $exception) {
// Exit (fail)
return 0;
}
}
/**
* Week
*
* Generate datetime by number of the week (by 6 days)
*
* @param int $number Number of the week
*
* @return datetime The date
*/
public static function week(int $number = 2): datetime
{
// Initializing the date
$date = new datetime('first day of september last year');
// Normalizing number of the week
if ($number < 2) $number = 2;
else if ($number > 37) $number = 37;
// Offsetting number of the week for calculating
$number = $number - 1;
// Calculating days for offsetting the date
$days = 6 * $number;
// Exit (success)
return $date->modify("+$days days +$number days");
}
/**
* Quarter
*
* Generate number of the quarter by number of the week
*
* @param int $number Number of the week
*
* @return int|false Number of the quarter
*/
public static function quarter(int $number = 2): int|false
{
// Exit (success)
return match (true) {
$number >= 2 && $number <= 9 => 1,
$number >= 11 && $number <= 18 => 2,
$number >= 21 && $number <= 30 => 3,
$number >= 32 && $number <= 38 => 4,
default => false
};
}
}

View File

@ -17,6 +17,10 @@ use Zanzara\Context as context,
// Framework for asynchronous PHP
use function React\Async\await;
// Built-in libraries
use Exception as exception,
DateTime as datetime;
/**
* Telegram shcool subjects parser
*
@ -49,7 +53,9 @@ final class parser extends core
// Initializing the parsing target buffer
$target = [
'subject' => $subject,
'grade' => null
'grade' => null,
'date' => null,
'week' => null
];
// Writing to the telegram user buffer
@ -111,7 +117,7 @@ final class parser extends core
/**
* Grade
*
* Write grade to the buffer and request confirmation for starting the parsing process
* Write grade to the buffer and request date
*
* @param context $context Request data from Telegram
* @param int $grade The grade
@ -131,6 +137,207 @@ final class parser extends core
// Initializing the grade
$target['grade'] = $grade;
// Writing to the telegram user buffer
$context->setUserDataItem(static::PROCESS, $target)
->then(function () use ($context, $target) {
// Writed to the telegram user buffer
// Sending the message
$context->sendMessage(
'📅 *Выберите номер недели*',
[
'reply_markup' => [
'inline_keyboard' => [
[
[
'text' => '2',
'callback_data' => 'week_2'
],
[
'text' => '3',
'callback_data' => 'week_3'
],
[
'text' => '4',
'callback_data' => 'week_4'
],
[
'text' => '5',
'callback_data' => 'week_5'
],
[
'text' => '6',
'callback_data' => 'week_6'
],
[
'text' => '7',
'callback_data' => 'week_7'
],
[
'text' => '8',
'callback_data' => 'week_8'
],
[
'text' => '9',
'callback_data' => 'week_9'
]
],
[
[
'text' => '11',
'callback_data' => 'week_11'
],
[
'text' => '12',
'callback_data' => 'week_12'
],
[
'text' => '13',
'callback_data' => 'week_13'
],
[
'text' => '14',
'callback_data' => 'week_14'
],
[
'text' => '15',
'callback_data' => 'week_15'
],
[
'text' => '16',
'callback_data' => 'week_16'
],
[
'text' => '17',
'callback_data' => 'week_17'
],
[
'text' => '18',
'callback_data' => 'week_18'
]
],
[
[
'text' => '21',
'callback_data' => 'week_21'
],
[
'text' => '22',
'callback_data' => 'week_22'
],
[
'text' => '23',
'callback_data' => 'week_23'
],
[
'text' => '24',
'callback_data' => 'week_24'
],
[
'text' => '25',
'callback_data' => 'week_25'
]
],
[
[
'text' => '26',
'callback_data' => 'week_26'
],
[
'text' => '27',
'callback_data' => 'week_27'
],
[
'text' => '28',
'callback_data' => 'week_28'
],
[
'text' => '29',
'callback_data' => 'week_29'
],
[
'text' => '30',
'callback_data' => 'week_30'
]
],
[
[
'text' => '32',
'callback_data' => 'week_32'
],
[
'text' => '33',
'callback_data' => 'week_33'
],
[
'text' => '34',
'callback_data' => 'week_34'
],
[
'text' => '35',
'callback_data' => 'week_35'
],
[
'text' => '36',
'callback_data' => 'week_36'
],
[
'text' => '37',
'callback_data' => 'week_37'
],
[
'text' => '38',
'callback_data' => 'week_38'
]
]
],
'disable_notification' => true,
'remove_keyboard' => true
]
]
);
});
} else {
// Not initialized the parsing process
// Sending the message
$context->sendMessage('⚠️ *Не запущен процесс генерации парсинга*')
->then(function (message $message) use ($context) {
// Sended the message
// Sending the menu with subjects
commands::menu($context);
});
}
});
}
/**
* Date
*
* Write date and request confirmation for starting the parsing process
*
* @param context $context Request data from Telegram
* @param int $week Number of the week
*
* @return void
*/
public static function date(context $context, int $week): void
{
// Reading from the telegram user buffer
$context->getUserDataItem(static::PROCESS)
->then(function (?array $target) use ($context, $week) {
// Readed from the telegram user buffer
if ($target) {
// Initialized the parsing process
// Initializing the week
$target['week'] = $week;
// Initializing the date
$target['date'] = interneturok::week($week);
// Writing to the telegram user buffer
$context->setUserDataItem(static::PROCESS, $target)
->then(function () use ($context, $target) {
@ -142,6 +349,12 @@ final class parser extends core
// Initializing the grade
$grade = $target['grade'];
// Reinigitlizing the date
$date = $target['date']->format('Y\\\.m\\\.d');
// Inigitlizing number the week
$week = $target['week'];
// Sending the message
$context->sendMessage(
<<<TXT
@ -149,6 +362,7 @@ final class parser extends core
*Предмет:* $subject
*Класс:* $grade
*Дата:* $date \($week\)
TXT,
[
'reply_markup' => [
@ -198,33 +412,53 @@ final class parser extends core
if ($target) {
// Initialized the parsing process
// Sending the message
$context->sendMessage('⚡️ *Запущен процесс парсинга домашнего задания*')
->then(function (message $message) use ($context, $target) {
// Sended the message
// Initializing variables for the parsing process
$subject = $target['subject'];
$grade = $target['grade'];
$date = $target['date'];
$week = $target['week'];
// Initializing the parser
$parser = new interneturok();
// Parsing homework files
$files = $parser->parse(subject: $subject, grade: $grade, waiting: 3);
try {
// Parsing homework
$homework = $parser->parse(subject: $subject, grade: $grade, date: $date, week: $week, waiting: 2);
if (!empty($files)) {
// Initialized homework files
if (!empty($homework)) {
// Initialized homework
foreach ($files as $file) {
// Iterating over files in the storage
foreach ($homework['files'] as $file) {
// Iterating over homework files in the storage
// Sending the file
await($context->sendDocument(new file_input($file)));
}
if ($homework['downloads']['year'] <= 100) {
// The homework was downloadsd 100 or less times for 1 last year
// Sending the message
$context->sendMessage('Домашнее задание было скачано *' . $homework['downloads']['year'] . ' раз* за последний год');
} else {
// The homework was downloaded more than 100 times for 1 last year
// Sending the message
$context->sendMessage('Домашнее задание было скачано *более чем 100 раз*) за последний год');
}
// Deleting in the telegram user buffer
$context->deleteUserDataItem(static::PROCESS);
} else {
// Not initialized homework files
// Sending the message
$context->sendMessage('❌ *Не найдены домашние задания')
$context->sendMessage('❌ *Не найдены домашние задания*')
->then(function (message $message) use ($context) {
// Sended the message
@ -232,6 +466,17 @@ final class parser extends core
commands::menu($context);
});
}
} catch (exception $exception) {
// Sending the message
$context->sendMessage('⚠️ *Произошла ошибка при обработке домашних заданий*')
->then(function (message $message) use ($context) {
// Sended the message
// Sending the menu with subjects
commands::menu($context);
});
}
});
} else {
// Not initialized the parsing process

View File

@ -32,9 +32,6 @@ define('VIEWS', INDEX . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'view
// Initializing path to the directory of settings
define('SETTINGS', INDEX . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'settings');
// Initializing system settings
require SETTINGS . DIRECTORY_SEPARATOR . 'system.php';
// Initializing path to the directory of the storage
define('STORAGE', INDEX . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'storage');
@ -83,6 +80,34 @@ for ($i = 4; $i <= 11; ++$i) {
$robot->onCbQueryData(["grade_$i"], fn(context $context) => parser::grade(context: $context, grade: $i));
}
for ($i = 32; $i <= 38; ++$i) {
// Generating buttons from 32 to 38 numbers of weeks
// Initializing numbers of weeks buttons
$robot->onCbQueryData(["week_$i"], fn(context $context) => parser::date(context: $context, week: $i));
}
for ($i = 21; $i <= 30; ++$i) {
// Generating buttons from 21 to 30 numbers of weeks
// Initializing numbers of weeks buttons
$robot->onCbQueryData(["week_$i"], fn(context $context) => parser::date(context: $context, week: $i));
}
for ($i = 11; $i <= 18; ++$i) {
// Generating buttons from 11 to 18 numbers of weeks
// Initializing numbers of weeks buttons
$robot->onCbQueryData(["week_$i"], fn(context $context) => parser::date(context: $context, week: $i));
}
for ($i = 2; $i <= 9; ++$i) {
// Generating buttons from 2 to 9 numbers of weeks
// Initializing numbers of weeks buttons
$robot->onCbQueryData(["week_$i"], fn(context $context) => parser::date(context: $context, week: $i));
}
// Initializing the parse button
$robot->onCbQueryData(['parse'], [parser::class, 'parse']);

View File

@ -5,5 +5,7 @@
*/
return [
1053489457 // Arsen Mirzaev Tatyano-Muradovich @redloser
1053489457, // Arsen Mirzaev Tatyano-Muradovich @redloser
6831961323, // @IU_K1LLER777
1142807069 // @kysakapro
];

View File

@ -1,4 +0,0 @@
<?php
// Initializing default theme for the views templater
define('THEME', 'default');