mirror of
https://github.com/UltraCoderRU/court_monitor.git
synced 2026-01-28 02:15:12 +00:00
Одиночный проход по всем подпискам из конфига.
This commit is contained in:
commit
0de99a97e8
16 changed files with 629 additions and 0 deletions
118
.clang-format
Normal file
118
.clang-format
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
---
|
||||||
|
BasedOnStyle: WebKit
|
||||||
|
AlignAfterOpenBracket: Align
|
||||||
|
AlignConsecutiveAssignments: false
|
||||||
|
AlignConsecutiveBitFields: false
|
||||||
|
AlignConsecutiveDeclarations: false
|
||||||
|
AlignEscapedNewlines: DontAlign
|
||||||
|
AlignOperands: Align
|
||||||
|
AlignTrailingComments: true
|
||||||
|
AllowAllArgumentsOnNextLine: true
|
||||||
|
AllowAllConstructorInitializersOnNextLine: true
|
||||||
|
AllowAllParametersOfDeclarationOnNextLine: false
|
||||||
|
AllowShortBlocksOnASingleLine: Never
|
||||||
|
AllowShortCaseLabelsOnASingleLine: false
|
||||||
|
AllowShortEnumsOnASingleLine: false
|
||||||
|
AllowShortFunctionsOnASingleLine: InlineOnly
|
||||||
|
AllowShortIfStatementsOnASingleLine: Never
|
||||||
|
AllowShortLambdasOnASingleLine: Inline
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AlwaysBreakAfterReturnType: None
|
||||||
|
AlwaysBreakBeforeMultilineStrings: true
|
||||||
|
AlwaysBreakTemplateDeclarations: Yes
|
||||||
|
BinPackArguments: true
|
||||||
|
BinPackParameters: false
|
||||||
|
BraceWrapping:
|
||||||
|
AfterCaseLabel: true
|
||||||
|
AfterClass: true
|
||||||
|
AfterControlStatement: Always
|
||||||
|
AfterEnum: true
|
||||||
|
AfterFunction: true
|
||||||
|
AfterNamespace: false
|
||||||
|
AfterStruct: true
|
||||||
|
AfterUnion: true
|
||||||
|
AfterExternBlock: false
|
||||||
|
BeforeCatch: true
|
||||||
|
BeforeElse: true
|
||||||
|
BeforeLambdaBody: true
|
||||||
|
BeforeWhile: true
|
||||||
|
IndentBraces: false
|
||||||
|
SplitEmptyFunction: true
|
||||||
|
SplitEmptyRecord: true
|
||||||
|
SplitEmptyNamespace: true
|
||||||
|
BreakBeforeBinaryOperators: None
|
||||||
|
BreakBeforeBraces: Custom
|
||||||
|
BreakBeforeTernaryOperators: true
|
||||||
|
BreakConstructorInitializers: BeforeColon
|
||||||
|
BreakInheritanceList: BeforeColon
|
||||||
|
BreakStringLiterals: true
|
||||||
|
ColumnLimit: 100
|
||||||
|
CompactNamespaces: false
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||||
|
ConstructorInitializerIndentWidth: 4
|
||||||
|
ContinuationIndentWidth: 4
|
||||||
|
Cpp11BracedListStyle: true
|
||||||
|
DeriveLineEnding: true
|
||||||
|
DerivePointerAlignment: false
|
||||||
|
DisableFormat: false
|
||||||
|
FixNamespaceComments: true
|
||||||
|
IncludeBlocks: Regroup
|
||||||
|
IncludeCategories:
|
||||||
|
- Regex: '^.*Private[.]h"'
|
||||||
|
Priority: 0
|
||||||
|
- Regex: '^".*[.]pb[.]h"'
|
||||||
|
Priority: 2
|
||||||
|
- Regex: '^".*[.]h"'
|
||||||
|
Priority: 1
|
||||||
|
- Regex: '^<boost/.*>'
|
||||||
|
Priority: 5
|
||||||
|
- Regex: '^<.*[.](h|hpp|hxx)>'
|
||||||
|
Priority: 3
|
||||||
|
- Regex: '^<queue>'
|
||||||
|
Priority: 6
|
||||||
|
- Regex: '^<Q.*>'
|
||||||
|
Priority: 4
|
||||||
|
- Regex: '^<.*>'
|
||||||
|
Priority: 6
|
||||||
|
IndentCaseBlocks: false
|
||||||
|
IndentCaseLabels: false
|
||||||
|
IndentExternBlock: NoIndent
|
||||||
|
IndentGotoLabels: false
|
||||||
|
IndentPPDirectives: None
|
||||||
|
IndentWidth: 4
|
||||||
|
IndentWrappedFunctionNames: false
|
||||||
|
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||||
|
Language: Cpp
|
||||||
|
MaxEmptyLinesToKeep: 2
|
||||||
|
NamespaceIndentation: None
|
||||||
|
PenaltyExcessCharacter: 10
|
||||||
|
PointerAlignment: Left
|
||||||
|
ReflowComments: true
|
||||||
|
SortIncludes: true
|
||||||
|
SortUsingDeclarations: true
|
||||||
|
SpaceAfterCStyleCast: false
|
||||||
|
SpaceAfterLogicalNot: false
|
||||||
|
SpaceAfterTemplateKeyword: true
|
||||||
|
SpaceBeforeAssignmentOperators: true
|
||||||
|
SpaceBeforeCpp11BracedList: false
|
||||||
|
SpaceBeforeCtorInitializerColon: true
|
||||||
|
SpaceBeforeInheritanceColon: true
|
||||||
|
SpaceBeforeParens: ControlStatements
|
||||||
|
SpaceBeforeRangeBasedForLoopColon: true
|
||||||
|
SpaceBeforeSquareBrackets: false
|
||||||
|
SpaceInEmptyBlock: false
|
||||||
|
SpaceInEmptyParentheses: false
|
||||||
|
SpacesBeforeTrailingComments: 1
|
||||||
|
SpacesInAngles: false
|
||||||
|
SpacesInCStyleCastParentheses: false
|
||||||
|
SpacesInConditionalStatement: false
|
||||||
|
SpacesInContainerLiterals: false
|
||||||
|
SpacesInParentheses: false
|
||||||
|
SpacesInSquareBrackets: false
|
||||||
|
Standard: c++14
|
||||||
|
StatementMacros: ['Q_UNUSED']
|
||||||
|
TabWidth: 4
|
||||||
|
UseCRLF: false
|
||||||
|
UseTab: ForIndentation
|
||||||
|
|
||||||
|
...
|
||||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
.idea
|
||||||
|
cmake-build-*
|
||||||
|
storage.json
|
||||||
12
.gitmodules
vendored
Normal file
12
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[submodule "external/fmt"]
|
||||||
|
path = external/fmt
|
||||||
|
url = https://github.com/fmtlib/fmt.git
|
||||||
|
[submodule "external/nlohmann_json"]
|
||||||
|
path = external/nlohmann_json
|
||||||
|
url = https://github.com/nlohmann/json.git
|
||||||
|
[submodule "external/banana"]
|
||||||
|
path = external/banana
|
||||||
|
url = https://github.com/UltraCoderRU/banana.git
|
||||||
|
[submodule "external/certify"]
|
||||||
|
path = external/certify
|
||||||
|
url = https://github.com/djarek/certify.git
|
||||||
11
Asio.cpp
Normal file
11
Asio.cpp
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#include "Asio.h"
|
||||||
|
|
||||||
|
#include <boost/certify/https_verification.hpp>
|
||||||
|
|
||||||
|
void initSSL()
|
||||||
|
{
|
||||||
|
sslContext.set_verify_mode(boost::asio::ssl::verify_peer |
|
||||||
|
boost::asio::ssl::verify_fail_if_no_peer_cert);
|
||||||
|
sslContext.set_default_verify_paths();
|
||||||
|
boost::certify::enable_native_https_server_verification(sslContext);
|
||||||
|
}
|
||||||
13
Asio.h
Normal file
13
Asio.h
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#ifndef COURT_MONITOR_ASIO_H
|
||||||
|
#define COURT_MONITOR_ASIO_H
|
||||||
|
|
||||||
|
#include <boost/asio/io_context.hpp>
|
||||||
|
#include <boost/asio/ssl/context.hpp>
|
||||||
|
|
||||||
|
static boost::asio::io_context asioContext;
|
||||||
|
|
||||||
|
static boost::asio::ssl::context sslContext(boost::asio::ssl::context::tls_client);
|
||||||
|
|
||||||
|
void initSSL();
|
||||||
|
|
||||||
|
#endif // COURT_MONITOR_ASIO_H
|
||||||
26
CMakeLists.txt
Normal file
26
CMakeLists.txt
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
project(court_monitor)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
|
||||||
|
|
||||||
|
find_package(OpenSSL REQUIRED)
|
||||||
|
|
||||||
|
find_package(Boost COMPONENTS system REQUIRED)
|
||||||
|
|
||||||
|
add_subdirectory(external)
|
||||||
|
|
||||||
|
add_executable(court_monitor
|
||||||
|
Asio.cpp
|
||||||
|
CourtApi.cpp
|
||||||
|
Storage.cpp
|
||||||
|
main.cpp
|
||||||
|
)
|
||||||
|
target_link_libraries(court_monitor
|
||||||
|
banana-beast
|
||||||
|
fmt::fmt
|
||||||
|
nlohmann_json::nlohmann_json
|
||||||
|
certify::core
|
||||||
|
Boost::system
|
||||||
|
${OPENSSL_LIBRARIES}
|
||||||
|
)
|
||||||
107
CourtApi.cpp
Normal file
107
CourtApi.cpp
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
#include "CourtApi.h"
|
||||||
|
|
||||||
|
#include "Asio.h"
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include <boost/asio/ssl/stream.hpp>
|
||||||
|
#include <boost/beast.hpp>
|
||||||
|
#include <boost/certify/extensions.hpp>
|
||||||
|
#include <boost/certify/https_verification.hpp>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
const char* serverDomain = "mirsud.spb.ru";
|
||||||
|
|
||||||
|
using ssl_stream = boost::asio::ssl::stream<boost::beast::tcp_stream>;
|
||||||
|
|
||||||
|
ssl_stream connect(const std::string& hostname)
|
||||||
|
{
|
||||||
|
ssl_stream stream(asioContext, sslContext);
|
||||||
|
|
||||||
|
static boost::asio::ip::tcp::resolver resolver(asioContext);
|
||||||
|
auto const results = resolver.resolve(hostname, "https");
|
||||||
|
|
||||||
|
boost::certify::set_server_hostname(stream, hostname);
|
||||||
|
boost::certify::sni_hostname(stream, hostname);
|
||||||
|
boost::beast::get_lowest_layer(stream).connect(results);
|
||||||
|
stream.handshake(boost::asio::ssl::stream_base::client);
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<int, std::string> get(ssl_stream& stream,
|
||||||
|
const std::string& hostname,
|
||||||
|
const std::string& url,
|
||||||
|
std::optional<std::string> payload = {})
|
||||||
|
{
|
||||||
|
// Создать HTTP-запрос
|
||||||
|
boost::beast::http::request<boost::beast::http::string_body> request;
|
||||||
|
request.method(boost::beast::http::verb::get);
|
||||||
|
request.target(url);
|
||||||
|
request.keep_alive(true);
|
||||||
|
request.set(boost::beast::http::field::host, hostname);
|
||||||
|
|
||||||
|
if (payload)
|
||||||
|
{
|
||||||
|
request.set(boost::beast::http::field::content_type, "application/json");
|
||||||
|
request.body() = std::move(*payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "tx: " << request << std::endl;
|
||||||
|
|
||||||
|
boost::beast::http::write(stream, request);
|
||||||
|
|
||||||
|
boost::beast::http::response<boost::beast::http::string_body> response;
|
||||||
|
boost::beast::flat_buffer buffer;
|
||||||
|
boost::beast::http::read(stream, buffer, response);
|
||||||
|
std::cout << "rx: " << response << std::endl;
|
||||||
|
|
||||||
|
return {response.base().result_int(), response.body()};
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json getResults(ssl_stream& stream, const std::string_view& uuid)
|
||||||
|
{
|
||||||
|
int status;
|
||||||
|
std::string result;
|
||||||
|
std::tie(status, result) =
|
||||||
|
get(stream, serverDomain, fmt::format("/cases/api/results/?id={}", uuid));
|
||||||
|
if (status == 200)
|
||||||
|
{
|
||||||
|
return nlohmann::json::parse(result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw std::runtime_error(
|
||||||
|
fmt::format("failed to retrieve JSON (server returned code {})", status));
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json getCaseDetails(int courtId, const std::string_view& caseNumber)
|
||||||
|
{
|
||||||
|
ssl_stream stream = connect(serverDomain);
|
||||||
|
|
||||||
|
int status;
|
||||||
|
std::string result;
|
||||||
|
std::tie(status, result) =
|
||||||
|
get(stream, serverDomain,
|
||||||
|
fmt::format("/cases/api/detail/?id={}&court_site_id={}", caseNumber, courtId));
|
||||||
|
if (status == 200)
|
||||||
|
{
|
||||||
|
auto uuid = nlohmann::json::parse(result).at("id").get<std::string>();
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
auto results = getResults(stream, uuid);
|
||||||
|
bool finished = results.at("finished").get<bool>();
|
||||||
|
if (finished)
|
||||||
|
return results.at("result");
|
||||||
|
else
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
|
}
|
||||||
|
throw std::runtime_error("failed to get results in time");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw std::runtime_error(
|
||||||
|
fmt::format("failed to retrieve JSON (server returned code {})", status));
|
||||||
|
}
|
||||||
12
CourtApi.h
Normal file
12
CourtApi.h
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
#ifndef COURT_MONITOR_COURT_API_H
|
||||||
|
#define COURT_MONITOR_COURT_API_H
|
||||||
|
|
||||||
|
#include <nlohmann/json_fwd.hpp>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
nlohmann::json findCases(const std::string_view& name);
|
||||||
|
|
||||||
|
nlohmann::json getCaseDetails(int courtId, const std::string_view& caseNumber);
|
||||||
|
|
||||||
|
#endif // COURT_MONITOR_COURT_API_H
|
||||||
84
Storage.cpp
Normal file
84
Storage.cpp
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
#include "Storage.h"
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
std::uint32_t parseTime(const std::string& str)
|
||||||
|
{
|
||||||
|
std::istringstream ss(str);
|
||||||
|
std::tm tm = {};
|
||||||
|
ss >> std::get_time(&tm, "%H:%M:%S");
|
||||||
|
if (ss.fail())
|
||||||
|
throw std::invalid_argument("invalid time string");
|
||||||
|
return tm.tm_hour * 3600 + tm.tm_min * 60 + tm.tm_sec;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string timeToString(std::uint32_t time)
|
||||||
|
{
|
||||||
|
auto hours = time / 3600;
|
||||||
|
auto minutes = (time % 3600) / 60;
|
||||||
|
auto seconds = (time % 3600) % 60;
|
||||||
|
return fmt::format("{:0>2}:{:0>2}:{:0>2}", hours, minutes, seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadStorage(LocalStorage& storage)
|
||||||
|
{
|
||||||
|
std::ifstream ifs("storage.json");
|
||||||
|
if (ifs.is_open())
|
||||||
|
{
|
||||||
|
auto data = json::parse(ifs);
|
||||||
|
storage.token = data.at("token").get<std::string>();
|
||||||
|
storage.checkTime = parseTime(data.at("check_time").get<std::string>());
|
||||||
|
|
||||||
|
for (const auto& subscription : data.at("subscriptions"))
|
||||||
|
{
|
||||||
|
Subscription s;
|
||||||
|
s.userId = subscription.at("user_id");
|
||||||
|
for (const auto& counter : subscription.at("counters"))
|
||||||
|
{
|
||||||
|
Counter c;
|
||||||
|
c.courtId = counter.at("court").get<int>();
|
||||||
|
c.caseNumber = counter.at("case").get<std::string>();
|
||||||
|
c.value = counter.at("value").get<std::size_t>();
|
||||||
|
s.counters.push_back(std::move(c));
|
||||||
|
}
|
||||||
|
storage.subscriptions.push_back(std::move(s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw std::runtime_error("failed to load storage");
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveStorage(const LocalStorage& storage)
|
||||||
|
{
|
||||||
|
json data;
|
||||||
|
data["token"] = storage.token;
|
||||||
|
data["check_time"] = timeToString(storage.checkTime);
|
||||||
|
|
||||||
|
data["subscriptions"] = json::array();
|
||||||
|
for (const auto& subscription : storage.subscriptions)
|
||||||
|
{
|
||||||
|
json jsonSubscription;
|
||||||
|
jsonSubscription["user_id"] = subscription.userId;
|
||||||
|
jsonSubscription["counters"] = json::array();
|
||||||
|
for (const auto& counter : subscription.counters)
|
||||||
|
{
|
||||||
|
json jsonCounter;
|
||||||
|
jsonCounter["court"] = counter.courtId;
|
||||||
|
jsonCounter["case"] = counter.caseNumber;
|
||||||
|
jsonCounter["value"] = counter.value;
|
||||||
|
jsonSubscription["counters"].push_back(std::move(jsonCounter));
|
||||||
|
}
|
||||||
|
data["subscriptions"].push_back(std::move(jsonSubscription));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream ofs("storage.json");
|
||||||
|
if (ofs.is_open())
|
||||||
|
ofs << std::setw(2) << data;
|
||||||
|
else
|
||||||
|
throw std::runtime_error("failed to save storage");
|
||||||
|
}
|
||||||
31
Storage.h
Normal file
31
Storage.h
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#ifndef COURT_MONITOR_STORAGE_H
|
||||||
|
#define COURT_MONITOR_STORAGE_H
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct Counter
|
||||||
|
{
|
||||||
|
int courtId = 0;
|
||||||
|
std::string caseNumber;
|
||||||
|
std::size_t value = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Subscription
|
||||||
|
{
|
||||||
|
int userId = 0;
|
||||||
|
std::vector<Counter> counters;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LocalStorage
|
||||||
|
{
|
||||||
|
std::string token;
|
||||||
|
std::vector<Subscription> subscriptions;
|
||||||
|
std::uint32_t checkTime; // секунды с 00:00
|
||||||
|
};
|
||||||
|
|
||||||
|
void loadStorage(LocalStorage& storage);
|
||||||
|
void saveStorage(const LocalStorage& storage);
|
||||||
|
|
||||||
|
#endif // COURT_MONITOR_STORAGE_H
|
||||||
8
external/CMakeLists.txt
vendored
Normal file
8
external/CMakeLists.txt
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
add_subdirectory(fmt EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
|
add_subdirectory(nlohmann_json EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
|
add_subdirectory(banana EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
|
set(BUILD_TESTING OFF CACHE INTERNAL "")
|
||||||
|
add_subdirectory(certify EXCLUDE_FROM_ALL)
|
||||||
1
external/banana
vendored
Submodule
1
external/banana
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 075ac78bba0950341c9c477ca38a523730857fee
|
||||||
1
external/certify
vendored
Submodule
1
external/certify
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 97f5eebfd99a5d6e99d07e4820240994e4e59787
|
||||||
1
external/fmt
vendored
Submodule
1
external/fmt
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit a33701196adfad74917046096bf5a2aa0ab0bb50
|
||||||
1
external/nlohmann_json
vendored
Submodule
1
external/nlohmann_json
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit bc889afb4c5bf1c0d8ee29ef35eaaf4c8bef8a5d
|
||||||
200
main.cpp
Normal file
200
main.cpp
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
#include "Asio.h"
|
||||||
|
#include "CourtApi.h"
|
||||||
|
#include "Storage.h"
|
||||||
|
|
||||||
|
#include <banana/agent/beast.hpp>
|
||||||
|
#include <banana/api.hpp>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include <boost/asio/system_timer.hpp>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <csignal>
|
||||||
|
#include <ctime>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
struct CaseHistoryItem
|
||||||
|
{
|
||||||
|
std::string date;
|
||||||
|
std::string time;
|
||||||
|
std::string status;
|
||||||
|
std::string publishDate;
|
||||||
|
std::string publishTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<CaseHistoryItem> parseHistory(const nlohmann::json& details)
|
||||||
|
{
|
||||||
|
std::vector<CaseHistoryItem> items;
|
||||||
|
const auto& history = details.at("history");
|
||||||
|
for (const auto& obj : history)
|
||||||
|
{
|
||||||
|
CaseHistoryItem item;
|
||||||
|
item.date = obj.at("date").get<std::string>();
|
||||||
|
item.time = obj.at("time").get<std::string>();
|
||||||
|
item.status = obj.at("status").get<std::string>();
|
||||||
|
item.publishDate = obj.at("publish_date").get<std::string>();
|
||||||
|
item.publishTime = obj.at("publish_time").get<std::string>();
|
||||||
|
items.push_back(std::move(item));
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
void notifyUser(banana::agent::beast_callback& bot,
|
||||||
|
int userId,
|
||||||
|
const std::string& caseNumber,
|
||||||
|
std::string caseUrl,
|
||||||
|
const CaseHistoryItem& item)
|
||||||
|
{
|
||||||
|
caseUrl = fmt::format("https://mirsud.spb.ru{}", caseUrl);
|
||||||
|
std::string message = fmt::format(
|
||||||
|
"Новое событие по делу [№{}]({}):\n"
|
||||||
|
"{}\n"
|
||||||
|
"Дата: {} {}\n",
|
||||||
|
caseNumber, caseUrl, item.status, item.date, item.time);
|
||||||
|
banana::api::send_message(bot, {.chat_id = userId, .text = message, .parse_mode = "markdown"},
|
||||||
|
[](const auto&) {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void processAllSubscriptions(LocalStorage& storage, banana::agent::beast_callback& bot)
|
||||||
|
{
|
||||||
|
for (auto& subscription : storage.subscriptions)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
fmt::print("* Processing subscriptions for user {}\n", subscription.userId);
|
||||||
|
for (auto& counter : subscription.counters)
|
||||||
|
{
|
||||||
|
fmt::print("** Processing case {}\n", counter.caseNumber);
|
||||||
|
auto details = getCaseDetails(counter.courtId, counter.caseNumber);
|
||||||
|
fmt::print("{}\n", details.dump());
|
||||||
|
auto url = details["url"].get<std::string>();
|
||||||
|
|
||||||
|
auto history = parseHistory(details);
|
||||||
|
for (std::size_t i = counter.value; i < history.size(); i++)
|
||||||
|
notifyUser(bot, subscription.userId, counter.caseNumber, url, history[i]);
|
||||||
|
counter.value = history.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
fmt::print(stderr, "{}\n", e.what());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void processUpdate(const banana::api::update_t& update)
|
||||||
|
{
|
||||||
|
if (update.message)
|
||||||
|
{
|
||||||
|
if (update.message->text)
|
||||||
|
{
|
||||||
|
fmt::print("rx: {}\n", *update.message->text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::chrono::system_clock::time_point getNextCheckTime(std::uint32_t checkTime)
|
||||||
|
{
|
||||||
|
std::time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||||
|
std::tm tm = *std::localtime(&now);
|
||||||
|
std::uint32_t secsOfDay = tm.tm_hour * 3600 + tm.tm_min * 60 + tm.tm_sec;
|
||||||
|
auto dayBegin = std::chrono::system_clock::from_time_t(now - secsOfDay);
|
||||||
|
auto t = dayBegin + std::chrono::seconds(checkTime);
|
||||||
|
return (secsOfDay < checkTime) ? t : (t + std::chrono::days(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto asioWork = boost::asio::make_work_guard(asioContext);
|
||||||
|
bool terminate = false;
|
||||||
|
|
||||||
|
void handleSignal(int)
|
||||||
|
{
|
||||||
|
fmt::print("signal!\n");
|
||||||
|
terminate = true;
|
||||||
|
asioWork.reset();
|
||||||
|
asioContext.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t offset = 0;
|
||||||
|
|
||||||
|
void getUpdates(banana::agent::beast_callback& bot);
|
||||||
|
|
||||||
|
void processUpdates(banana::agent::beast_callback bot,
|
||||||
|
banana::expected<banana::array_t<banana::api::update_t>> updates)
|
||||||
|
{
|
||||||
|
if (terminate)
|
||||||
|
{
|
||||||
|
fmt::print("exit\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updates)
|
||||||
|
{
|
||||||
|
for (const auto& update : *updates)
|
||||||
|
{
|
||||||
|
processUpdate(update);
|
||||||
|
offset = update.update_id + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
fmt::print(stderr, "failed to get updates: {}\n", updates.error());
|
||||||
|
|
||||||
|
getUpdates(bot);
|
||||||
|
}
|
||||||
|
|
||||||
|
void getUpdates(banana::agent::beast_callback& bot)
|
||||||
|
{
|
||||||
|
banana::api::get_updates(bot, {.offset = offset, .timeout = 50},
|
||||||
|
[bot](auto updates) { processUpdates(bot, std::move(updates)); });
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
std::signal(SIGTERM, handleSignal);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Загрузить данные из локального хранилища
|
||||||
|
LocalStorage storage;
|
||||||
|
loadStorage(storage);
|
||||||
|
fmt::print("Storage loaded\n");
|
||||||
|
|
||||||
|
// Инициализировать SSL
|
||||||
|
initSSL();
|
||||||
|
|
||||||
|
// Создать бота
|
||||||
|
banana::agent::beast_callback bot(storage.token, asioContext, sslContext);
|
||||||
|
|
||||||
|
// Создать таймер ежедневной проверки
|
||||||
|
boost::asio::system_timer checkTimer(asioContext);
|
||||||
|
std::function<void(const boost::system::error_code&)> onTimer =
|
||||||
|
[&](const boost::system::error_code& error)
|
||||||
|
{
|
||||||
|
if (!error)
|
||||||
|
{
|
||||||
|
processAllSubscriptions(storage, bot);
|
||||||
|
checkTimer.expires_at(getNextCheckTime(storage.checkTime));
|
||||||
|
checkTimer.async_wait(onTimer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
checkTimer.expires_at(getNextCheckTime(storage.checkTime));
|
||||||
|
checkTimer.async_wait(onTimer);
|
||||||
|
|
||||||
|
// Запустить асинхронное получение обновлений
|
||||||
|
getUpdates(bot);
|
||||||
|
|
||||||
|
// Запустить цикл обработки событий
|
||||||
|
asioContext.run();
|
||||||
|
|
||||||
|
// Сохранить данные в локальное хранилище
|
||||||
|
fmt::print("Saving storage\n");
|
||||||
|
saveStorage(storage);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
fmt::print(stderr, "{}\n", e.what());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue