Skip to content

Commit

Permalink
Telegram support and more notificatons (#217)
Browse files Browse the repository at this point in the history
# 1.8.6
- Integrated Telegram push notifications
- Fixed multi miner editor
- Added miner offline/online status push notification
- Added 0/recovered hashrate push notification
  • Loading branch information
Bendr0id authored Dec 10, 2018
1 parent 576e803 commit cc8b117
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 30 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# 1.8.6
- Integrated Telegram push notifications
- Fixed multi miner editor
- Added miner offline/online status push notification
- Added 0/recovered hashrate push notification
# 1.8.5
- Add remote reboot (machine) feature to Dashboard, Server & Miner
- Integrated Pushover push notifications for Offline miners and periodical status notifications on iOS and Android
Expand Down
2 changes: 2 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@
table.button(4).enable(selectedRows > 0);
table.button(5).enable(selectedRows > 0);
table.button(6).enable(selectedRows > 0);
table.button(7).enable(selectedRows > 0);
});

table.on('deselect', function () {
Expand All @@ -376,6 +377,7 @@
table.button(4).enable(selectedRows > 0);
table.button(5).enable(selectedRows > 0);
table.button(6).enable(selectedRows > 0);
table.button(7).enable(selectedRows > 0);
});

table.buttons().container().appendTo('#clientStatusList_wrapper .col-sm-6:eq(0)');
Expand Down
47 changes: 41 additions & 6 deletions src/Options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,13 @@ Options:\n"
--cc-key-file=FILE when tls is turned on, use this to point to the right key file (default: server.key) \n\
--cc-client-log-lines-history=N maximum lines of log history kept per miner (default: 100)\n\
--cc-client-config-folder=FOLDER Folder contains the client config files\n\
--cc-pushover-user-key pushover user for push messages\n\
--cc-pushover-api-token your user key for pushover notifications\n\
--cc-push-miner-offline-info api token/keytoken of the application for pushover notifications\n\
--cc-push-periodic-mining-status send periodic mining status push\n\
--cc-pushover-user-key your user key for pushover notifications\n\
--cc-pushover-api-token api token/keytoken of the application for pushover notifications\n\
--cc-telegram-bot-token your bot token for telegram notifications\n\
--cc-telegram-chat-id your chat-id for telegram notifications\n\
--cc-push-miner-offline-info push notification for offline miners and recover push\n\
--cc-push-miner-zero-hash-info push notification when miner reports 0 hashrate and recover push\n\
--cc-push-periodic-mining-status push periodic status notification (every hour)\n\
--cc-custom-dashboard=FILE loads a custom dashboard and serve it to '/'\n"
# endif
"\
Expand Down Expand Up @@ -195,8 +198,11 @@ static struct option const options[] = {
{ "cc-reboot-cmd", 1, nullptr, 4021 },
{ "cc-pushover-user-key", 1, nullptr, 4022 },
{ "cc-pushover-api-token", 1, nullptr, 4023 },
{ "cc-telegram-bot-token", 1, nullptr, 4027 },
{ "cc-telegram-chat-id", 1, nullptr, 4028 },
{ "cc-push-miner-offline-info", 0, nullptr, 4024 },
{ "cc-push-periodic-mining-status", 0, nullptr, 4025 },
{ "cc-push-miner-zero-hash-info", 0, nullptr, 4026 },
{ "daemonized", 0, nullptr, 4011 },
{ "doublehash-thread-mask", 1, nullptr, 4013 },
{ "multihash-thread-mask", 1, nullptr, 4013 },
Expand Down Expand Up @@ -279,7 +285,10 @@ static struct option const cc_server_options[] = {
{ "client-log-lines-history", 1, nullptr, 4018 },
{ "pushover-user-key", 1, nullptr, 4022 },
{ "pushover-api-token", 1, nullptr, 4023 },
{ "telegram-bot-token", 1, nullptr, 4027 },
{ "telegram-chat-id", 1, nullptr, 4028 },
{ "push-miner-offline-info", 0, nullptr, 4024 },
{ "push-miner-zero-hash-info", 0, nullptr, 4026 },
{ "push-periodic-mining-status", 0, nullptr, 4025 },
{ nullptr, 0, nullptr, 0 }
};
Expand Down Expand Up @@ -354,6 +363,7 @@ Options::Options(int argc, char **argv) :
m_ccUploadConfigOnStartup(true),
m_ccPushOfflineMiners(false),
m_ccPushPeriodicStatus(false),
m_ccPushZeroHashrateMiners(false),
m_fileName(Platform::defaultConfigName()),
m_apiToken(nullptr),
m_apiWorkerId(nullptr),
Expand All @@ -371,6 +381,8 @@ Options::Options(int argc, char **argv) :
m_ccRebootCmd(nullptr),
m_ccPushoverUser(nullptr),
m_ccPushoverToken(nullptr),
m_ccTelegramBotToken(nullptr),
m_ccTelegramChatId(nullptr),
m_algo(ALGO_CRYPTONIGHT),
m_algoVariant(AV0_AUTO),
m_aesni(AESNI_AUTO),
Expand Down Expand Up @@ -572,12 +584,12 @@ bool Options::parseArg(int key, const char *arg)

case 4014: /* --cc-cert-file */
free(m_ccCertFile);
m_ccCertFile = strdup(arg);
m_ccCertFile = strdup(arg);
break;

case 4015: /* --cc-key-file */
free(m_ccKeyFile);
m_ccKeyFile = strdup(arg);
m_ccKeyFile = strdup(arg);
break;

case 4011: /* --daemonized */
Expand Down Expand Up @@ -630,20 +642,39 @@ bool Options::parseArg(int key, const char *arg)
return parseAsmOptimization(arg);

case 4021: /* --cc-reboot-cmd */
free(m_ccRebootCmd);
m_ccRebootCmd = strdup(arg);
break;

case 4022: /* --cc-pushover-user-key */
free(m_ccPushoverUser);
m_ccPushoverUser = strdup(arg);
break;

case 4023: /* --cc-pushover-api-token */
free(m_ccPushoverToken);
m_ccPushoverToken = strdup(arg);
break;

case 4027: /* --cc-telegram-bot-token */
free(m_ccTelegramBotToken);
m_ccTelegramBotToken = strdup(arg);
break;

case 4028: /* --cc-telegram-chat-id */
free(m_ccTelegramChatId);
m_ccTelegramChatId = strdup(arg);
break;

case 4024: /* --cc-push-miner-offline-info */
return parseBoolean(key, false);

case 4025: /* --cc-push-periodic-mining-status */
return parseBoolean(key, false);

case 4026: /* --cc-push-miner-zero-hash-info */
return parseBoolean(key, false);

case 't': /* --threads */
if (strncmp(arg, "all", 3) == 0) {
m_threads = Cpu::threads();
Expand Down Expand Up @@ -888,6 +919,10 @@ bool Options::parseBoolean(int key, bool enable)
m_ccPushOfflineMiners = enable;
break;

case 4026: /* --cc-push-miner-zero-hash-info */
m_ccPushZeroHashrateMiners = enable;
break;

case 4025: /* --cc-push-periodic-mining-status */
m_ccPushPeriodicStatus = enable;
break;
Expand Down
8 changes: 8 additions & 0 deletions src/Options.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ class Options
inline bool ccUploadConfigOnStartup() const { return m_ccUploadConfigOnStartup; }
inline bool ccPushOfflineMiners() const { return m_ccPushOfflineMiners; }
inline bool ccPushPeriodicStatus() const { return m_ccPushPeriodicStatus; }
inline bool ccPushZeroHashrateMiners() const { return m_ccPushZeroHashrateMiners; }
inline bool ccUsePushover() const { return ccPushoverUser() && ccPushoverToken(); }
inline bool ccUseTelegram() const { return ccTelegramBotToken() && ccTelegramChatId(); }
inline const char *fileName() const { return m_fileName; }
inline const char *apiToken() const { return m_apiToken; }
inline const char *apiWorkerId() const { return m_apiWorkerId; }
Expand All @@ -94,6 +97,8 @@ class Options
inline const char *ccRebootCmd() const { return (m_ccRebootCmd != nullptr && strlen(m_ccRebootCmd) > 0) ? m_ccRebootCmd : nullptr; }
inline const char *ccPushoverUser() const { return (m_ccPushoverUser != nullptr && strlen(m_ccPushoverUser) > 0) ? m_ccPushoverUser : nullptr; }
inline const char *ccPushoverToken() const { return (m_ccPushoverToken != nullptr && strlen(m_ccPushoverToken) > 0) ? m_ccPushoverToken : nullptr; }
inline const char *ccTelegramBotToken() const { return (m_ccTelegramBotToken != nullptr && strlen(m_ccTelegramBotToken) > 0) ? m_ccTelegramBotToken : nullptr; }
inline const char *ccTelegramChatId() const { return (m_ccTelegramChatId != nullptr && strlen(m_ccTelegramChatId) > 0) ? m_ccTelegramChatId : nullptr; }
inline const std::vector<Url*> &pools() const { return m_pools; }
inline Algo algo() const { return m_algo; }
inline PowVariant powVariant() const { return m_powVariant; }
Expand Down Expand Up @@ -159,6 +164,7 @@ class Options
bool m_ccUploadConfigOnStartup;
bool m_ccPushOfflineMiners;
bool m_ccPushPeriodicStatus;
bool m_ccPushZeroHashrateMiners;
const char* m_fileName;
char *m_apiToken;
char *m_apiWorkerId;
Expand All @@ -176,6 +182,8 @@ class Options
char *m_ccRebootCmd;
char *m_ccPushoverUser;
char *m_ccPushoverToken;
char *m_ccTelegramBotToken;
char *m_ccTelegramChatId;
Algo m_algo;
AlgoVariant m_algoVariant;
AesNi m_aesni;
Expand Down
113 changes: 95 additions & 18 deletions src/cc/Service.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,18 @@ std::map<std::string, ControlCommand> Service::m_clientCommand;
std::map<std::string, ClientStatus> Service::m_clientStatus;
std::map<std::string, std::list<std::string>> Service::m_clientLog;

std::list<std::string> Service::m_offlineNotified;
std::list<std::string> Service::m_zeroHashNotified;

uint64_t Service::m_currentServerTime = 0;
uint64_t Service::m_lastOfflineCheckTime = 0;
uint64_t Service::m_lastStatusUpdateTime = 0;

bool Service::start()
{
uv_mutex_init(&m_mutex);

#ifndef XMRIG_NO_TLS
if (Options::i()->ccPushoverToken() && Options::i()->ccPushoverUser())
if (Options::i()->ccUsePushover() || Options::i()->ccUseTelegram())
{
uv_timer_init(uv_default_loop(), &m_timer);
uv_timer_start(&m_timer, Service::onPushTimer,
Expand Down Expand Up @@ -447,7 +449,10 @@ void Service::onPushTimer(uv_timer_t* handle)

if (Options::i()->ccPushOfflineMiners()) {
sendMinerOfflinePush(now);
m_lastOfflineCheckTime = now;
}

if (Options::i()->ccPushZeroHashrateMiners()) {
sendMinerZeroHashratePush(now);
}

if (Options::i()->ccPushPeriodicStatus()) {
Expand All @@ -460,18 +465,63 @@ void Service::onPushTimer(uv_timer_t* handle)

void Service::sendMinerOfflinePush(uint64_t now)
{
uint64_t offlineThreshold = now - OFFLINE_TRESHOLD_IN_MS;

for (auto clientStatus : m_clientStatus) {
uint64_t lastStatus = clientStatus.second.getLastStatusUpdate() * 1000;
uint64_t offlineThreshold = now - OFFLINE_TRESHOLD_IN_MS;

if (lastStatus < offlineThreshold) {
offlineThreshold = now - (OFFLINE_TRESHOLD_IN_MS + (now - m_lastOfflineCheckTime));
if (lastStatus > offlineThreshold) {
if (std::find(m_offlineNotified.begin(), m_offlineNotified.end(), clientStatus.first) == m_offlineNotified.end()) {
std::stringstream message;
message << "Miner: " << clientStatus.first << " just went offline!";

LOG_WARN("Send miner went offline push", clientStatus.first.c_str());
triggerPush(APP_NAME " Offline Monitor", message.str());
LOG_WARN("Send miner %s went offline push", clientStatus.first.c_str());
triggerPush(APP_NAME " Onlinestatus Monitor", message.str());

m_offlineNotified.push_back(clientStatus.first);
}
} else {
if (std::find(m_offlineNotified.begin(), m_offlineNotified.end(), clientStatus.first) != m_offlineNotified.end()) {
std::stringstream message;
message << "Miner: " << clientStatus.first << " is back online!";

LOG_WARN("Send miner %s back online push", clientStatus.first.c_str());
triggerPush(APP_NAME " Onlinestatus Monitor", message.str());

m_offlineNotified.remove(clientStatus.first);
}
}
}
}

void Service::sendMinerZeroHashratePush(uint64_t now)
{
uint64_t offlineThreshold = now - OFFLINE_TRESHOLD_IN_MS;

for (auto clientStatus : m_clientStatus) {
if (offlineThreshold < clientStatus.second.getLastStatusUpdate() * 1000) {
if (clientStatus.second.getHashrateShort() == 0 && clientStatus.second.getHashrateMedium() == 0) {
if (std::find(m_zeroHashNotified.begin(), m_zeroHashNotified.end(), clientStatus.first) == m_zeroHashNotified.end()) {
std::stringstream message;
message << "Miner: " << clientStatus.first << " reported 0 h/s for the last minute!";

LOG_WARN("Send miner %s 0 hashrate push", clientStatus.first.c_str());
triggerPush(APP_NAME " Hashrate Monitor", message.str());

m_zeroHashNotified.push_back(clientStatus.first);
}
} else if (clientStatus.second.getHashrateMedium() > 0) {
if (std::find(m_zeroHashNotified.begin(), m_zeroHashNotified.end(), clientStatus.first) != m_zeroHashNotified.end()) {
std::stringstream message;
message << "Miner: " << clientStatus.first << " hashrate recovered. Reported "
<< clientStatus.second.getHashrateMedium()
<< " h/s for the last minute!";

LOG_WARN("Send miner %s hashrate recovered push", clientStatus.first.c_str());
triggerPush(APP_NAME " Hashrate Monitor", message.str());

m_zeroHashNotified.remove(clientStatus.first);
}
}
}
}
Expand All @@ -493,16 +543,16 @@ void Service::sendServerStatusPush(uint64_t now)
for (auto clientStatus : m_clientStatus) {
if (offlineThreshold < clientStatus.second.getLastStatusUpdate() * 1000) {
onlineMiner++;

hashrateMedium += clientStatus.second.getHashrateMedium();
hashrateLong += clientStatus.second.getHashrateLong();

sharesGood += clientStatus.second.getSharesGood();
sharesTotal += clientStatus.second.getSharesTotal();
avgTime += clientStatus.second.getAvgTime();
} else {
offlineMiner++;
}

hashrateMedium += clientStatus.second.getHashrateMedium();
hashrateLong += clientStatus.second.getHashrateLong();

sharesGood += clientStatus.second.getSharesGood();
sharesTotal += clientStatus.second.getSharesTotal();
avgTime += clientStatus.second.getAvgTime();
}

if (!m_clientStatus.empty()) {
Expand All @@ -521,7 +571,17 @@ void Service::sendServerStatusPush(uint64_t now)

void Service::triggerPush(const std::string& title, const std::string& message)
{
#ifndef XMRIG_NO_TLS
if (Options::i()->ccUsePushover()) {
sendViaPushover(title, message);
}

if (Options::i()->ccUseTelegram()) {
sendViaTelegram(title, message);
}
}

void Service::sendViaPushover(const std::string &title, const std::string &message)
{
std::shared_ptr<httplib::Client> cli = std::make_shared<httplib::SSLClient>("api.pushover.net", 443);

httplib::Params params;
Expand All @@ -532,7 +592,24 @@ void Service::triggerPush(const std::string& title, const std::string& message)

auto res = cli->Post("/1/messages.json", params);
if (res) {
LOG_WARN("Push response: %s", res->body.c_str());
LOG_WARN("Pushover response: %s", res->body.c_str());
}
}

void Service::sendViaTelegram(const std::string &title, const std::string &message)
{
std::shared_ptr<httplib::Client> cli = std::make_shared<httplib::SSLClient>("api.telegram.org", 443);

std::string text = "<b>" + title + "</b>\n\n" + message;
std::string path = std::string("/bot") + Options::i()->ccTelegramBotToken() + std::string("/sendMessage");

httplib::Params params;
params.emplace("chat_id", Options::i()->ccTelegramChatId());
params.emplace("text", httplib::detail::encode_url(text));
params.emplace("parse_mode", "HTML");

auto res = cli->Post(path.c_str(), params);
if (res) {
LOG_WARN("Telegram response: %s", res->body.c_str());
}
#endif
}
9 changes: 8 additions & 1 deletion src/cc/Service.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,26 @@ class Service
static void onPushTimer(uv_timer_t* handle);
static void sendServerStatusPush(uint64_t now);
static void sendMinerOfflinePush(uint64_t now);
static void sendMinerZeroHashratePush(uint64_t now);
static void triggerPush(const std::string& title, const std::string& message);

private:
static uint64_t m_currentServerTime;
static uint64_t m_lastOfflineCheckTime;
static uint64_t m_lastStatusUpdateTime;

static std::map<std::string, ClientStatus> m_clientStatus;
static std::map<std::string, ControlCommand> m_clientCommand;
static std::map<std::string, std::list<std::string>> m_clientLog;

static std::list<std::string> m_offlineNotified;
static std::list<std::string> m_zeroHashNotified;

static uv_mutex_t m_mutex;
static uv_timer_t m_timer;

static void sendViaPushover(const std::string &title, const std::string &message);

static void sendViaTelegram(const std::string &title, const std::string &message);
};

#endif /* __SERVICE_H__ */
Loading

0 comments on commit cc8b117

Please sign in to comment.