Browse Source

Rewrite for NET Core, added logger, fixed bugs.

Veloe 4 years ago
commit
4dbe729075

+ 12 - 0
EthermineBotTelegramCore/App.config

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  
+    <appSettings>
+        <add key="poolApiUrl" value="https://api.ethermine.org" />
+        <add key="currency" value="ETH" />
+        <add key="dbIp" value="192.168.1.101" />
+        <add key="fullLogPath" value="full.log" />
+        <add key="errorLogPath" value="error.log" />
+    </appSettings>
+    
+</configuration>

+ 17 - 0
EthermineBotTelegramCore/AppSettings.cs

@@ -0,0 +1,17 @@
+
+
+namespace EthermineBotTelegramCore
+{
+    public class AppSettings
+    {
+        public static string poolApiUrl = System.Configuration.ConfigurationManager.AppSettings["poolApiUrl"];
+        public static string dbIp = System.Configuration.ConfigurationManager.AppSettings["dbIp"];
+        public static string currency = System.Configuration.ConfigurationManager.AppSettings["currency"];
+        public static string fullLogPath = System.Configuration.ConfigurationManager.AppSettings["fullLogPath"];
+        public static string errorLogPath = System.Configuration.ConfigurationManager.AppSettings["errorLogPath"];
+        
+        //dont use user and pass variables to read credentials from app.config! this used just for reading them from console
+        public static string user;
+        public static string password;
+    }
+}

+ 16 - 0
EthermineBotTelegramCore/DataUpdater.cs

@@ -0,0 +1,16 @@
+using System;
+using Quartz;
+using System.Threading.Tasks;
+
+namespace EthermineBotTelegramCore
+{
+    public class DataUpdater : IJob
+    {
+        public async Task Execute(IJobExecutionContext context)
+        {
+            await Console.Out.WriteLineAsync("Updating data! Current time: " + DateTime.Now.ToString("f"));
+            await Logger.LogInfo("Updating data!");
+            await Program.UpdateData();
+        }
+    }
+}

+ 100 - 0
EthermineBotTelegramCore/EFDatabase.cs

@@ -0,0 +1,100 @@
+using System.Data.Common;
+using Microsoft.EntityFrameworkCore;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Configuration;
+using EthermineBotTelegramCore;
+using Microsoft.Extensions.DependencyInjection;
+using MySql.EntityFrameworkCore;
+
+namespace EFDatabase
+{
+    //[DbConfigurationType(typeof(MySqlEFConfiguration))]
+    public class botdb : DbContext
+    {
+        public DbSet<users> users { get; set; }
+        public DbSet<miners> miners { get; set; }
+        public DbSet<workers> workers { get; set; }
+        public DbSet<payouts> payouts { get; set; }
+        
+        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+        {
+            //#warning To protect potentially sensitive information in your connection string, 
+            //you should move it out of source code. See http://go.microsoft.com/fwlink/?LinkId=723263 
+            //for guidance on storing connection strings.
+     
+                optionsBuilder.UseMySQL("server=" + AppSettings.dbIp +  ";port=3306;database=botdb;uid=" + AppSettings.user + "; pwd=" + AppSettings.password);
+        }
+        
+        protected override void OnModelCreating(ModelBuilder modelBuilder)
+        {
+            modelBuilder.Entity<miners>()
+                .HasKey(m => new { m.wallet, m.time });
+            modelBuilder.Entity<workers>()
+                .HasKey(w => new { w.wallet, w.time, w.worker });
+            modelBuilder.Entity<payouts>()
+                .HasKey(p => new { p.wallet, p.time, p.worker });
+        }
+        
+    }
+    
+    public class users
+    {
+        [Key]
+        [DatabaseGenerated(DatabaseGeneratedOption.None)]
+        public long chat_id { get; set; }
+        public string wallet { get; set; }
+        public long last_message { get; set; }
+        public long timer { get; set; } //unused
+    }
+
+    public class miners
+    {
+        //[Key, Column(Order = 0)]
+        [DatabaseGenerated(DatabaseGeneratedOption.None)]
+        public string wallet { get; set; }
+        //[Key, Column(Order = 1)]
+        [DatabaseGenerated(DatabaseGeneratedOption.None)]
+        public long time { get; set; }
+        public long reported_hashrate { get; set; }
+        public double current_hashrate { get; set; }
+        public int valid_shares { get; set; }
+        public int stale_shares { get; set; }
+        public int invalid_shares { get; set; }
+        public int workers { get; set; }
+        public long unpaid { get; set; }
+    }
+
+    public class workers
+    {
+        
+        public string wallet { get; set; }
+        //[Key, Column(Order = 0)]
+        [DatabaseGenerated(DatabaseGeneratedOption.None)]
+        public long time { get; set; }
+        //[Key, Column(Order = 1)]
+        [DatabaseGenerated(DatabaseGeneratedOption.None)]
+        public string worker { get; set; }
+        public long reported_hashrate { get; set; }
+        public double current_hashrate { get; set; }
+        public int valid_shares { get; set; }
+        public int stale_shares { get; set; }
+        public int invalid_shares { get; set; }
+        public double worker_unpaid { get; set; }
+    }
+
+    public class payouts
+    {
+        //[Key, Column(Order = 0)]
+        [DatabaseGenerated(DatabaseGeneratedOption.None)]
+        public string wallet { get; set; }
+        //[Key, Column(Order = 1)]
+        [DatabaseGenerated(DatabaseGeneratedOption.None)]
+        public long time { get; set; }
+        public long amount { get; set; }
+        //[Key, Column(Order = 2)]
+        [DatabaseGenerated(DatabaseGeneratedOption.None)]
+        public string worker { get; set; }
+        public double worker_amount { get; set; }
+    }
+}

+ 29 - 0
EthermineBotTelegramCore/JsonCurrentStats.cs

@@ -0,0 +1,29 @@
+namespace JsonCurrentStatsData
+{
+    public class Data
+    {
+        public int time { get; set; }
+        public int lastSeen { get; set; }
+        public long reportedHashrate { get; set; }
+        public double currentHashrate { get; set; }
+        public int validShares { get; set; }
+        public int invalidShares { get; set; }
+        public int staleShares { get; set; }
+        public double averageHashrate { get; set; }
+        public int activeWorkers { get; set; }
+        public long unpaid { get; set; }
+        public object unconfirmed { get; set; }
+        public double coinsPerMin { get; set; }
+        public double usdPerMin { get; set; }
+        public double btcPerMin { get; set; }
+    }
+}
+
+namespace EthermineBotTelegramCore
+{
+    public class JsonCurrentStats
+    {
+        public string status { get; set; }
+        public JsonCurrentStatsData.Data data { get; set; }
+    }
+}

+ 26 - 0
EthermineBotTelegramCore/JsonDownloader.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using Newtonsoft.Json;
+
+namespace EthermineBotTelegramCore
+{
+     public class JsonDownloader
+    {
+        public static T _download_serialized_json_data<T>(string url) where T : new() {
+            using (var w = new WebClient()) {
+                var jsonData = string.Empty;
+                // attempt to download JSON data as a string
+                try
+                {
+                    jsonData = w.DownloadString(url);
+                    jsonData = jsonData.Replace("null", "0");
+
+                }
+                catch (Exception) { }
+                // if string with JSON data is not empty, deserialize it to class and return its instance 
+                return !string.IsNullOrEmpty(jsonData) ? JsonConvert.DeserializeObject<T>(jsonData) : new T();
+            }
+        }
+    }
+}

+ 23 - 0
EthermineBotTelegramCore/JsonNetworkStats.cs

@@ -0,0 +1,23 @@
+namespace JsonNetworkStatsData
+{
+    // Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse); 
+    public class Data
+    {
+        public int time { get; set; }
+        public double blockTime { get; set; }
+        public long difficulty { get; set; }
+        public long hashrate { get; set; }
+        public double usd { get; set; }
+        public double btc { get; set; }
+    }
+}
+
+
+namespace EthermineBotTelegramCore
+{
+    public class JsonNetworkStats
+        {
+            public string status { get; set; }
+            public JsonNetworkStatsData.Data data { get; set; }
+        }
+}

+ 22 - 0
EthermineBotTelegramCore/JsonPayouts.cs

@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+
+namespace JsonPayoutsData
+{
+    public class Datа
+    {
+        public int start { get; set; }
+        public int end { get; set; }
+        public long amount { get; set; }
+        public string txHash { get; set; }
+        public int paidOn { get; set; }
+    }
+}
+
+namespace EthermineBotTelegramCore
+{
+    public class JsonPayouts
+        {
+            public string status { get; set; }
+            public List<JsonPayoutsData.Datа> data { get; set; }
+        }
+}

+ 27 - 0
EthermineBotTelegramCore/JsonWorker.cs

@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+
+namespace JsonWorkerData
+{
+    public class Data
+    {
+        public string worker { get; set; }
+        public long time { get; set; }
+        public long lastSeen { get; set; }
+        public long reportedHashrate { get; set; }
+        public double currentHashrate { get; set; }
+        public int validShares { get; set; }
+        public int invalidShares { get; set; }
+        public int staleShares { get; set; }
+        public double averageHashrate { get; set; }
+    }
+}
+
+namespace  EthermineBotTelegramCore
+{
+    public class JsonWorker
+    {
+        public string status { get; set; }
+        public List<JsonWorkerData.Data> data { get; set; }
+    }
+ 
+}

+ 85 - 0
EthermineBotTelegramCore/Logger.cs

@@ -0,0 +1,85 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EthermineBotTelegramCore
+{
+    public static class Logger
+    {
+        private static string fullLogPath = AppSettings.fullLogPath;
+        private static string errorLogPath = AppSettings.errorLogPath;
+        
+        static FileStream fullLog = new FileStream(fullLogPath, FileMode.OpenOrCreate, 
+                                                                FileAccess.Write,
+                                                                FileShare.None, 4096, 
+                                                                FileOptions.Asynchronous | FileOptions.SequentialScan);
+        static FileStream errLog = new FileStream(errorLogPath, FileMode.OpenOrCreate, 
+                                                                FileAccess.Write,
+                                                                FileShare.None, 4096, 
+                                                                FileOptions.Asynchronous | FileOptions.SequentialScan);
+
+        static Logger()
+        {
+            fullLog.Seek(0,SeekOrigin.End);
+            errLog.Seek(0,SeekOrigin.End);
+        }
+        public async static Task LogInfo(string message)
+        {
+            message =  $"{DateTime.Now.ToLocalTime().ToString("[MM-dd-yyyy HH:mm:ss]")} INFO {message} \n";
+
+            byte[] logLine = Encoding.Unicode.GetBytes(message);
+            
+            await fullLog.WriteAsync(logLine, 0, logLine.Length);
+            
+            await fullLog.FlushAsync();
+        }
+        
+        public async static Task LogWarn(string message)
+        {
+            message =  $"{DateTime.Now.ToLocalTime().ToString("[MM-dd-yyyy HH:mm:ss]")} WARNING {message} \n";
+
+            byte[] logLine = Encoding.Unicode.GetBytes(message);
+            
+            await fullLog.WriteAsync(logLine, 0, logLine.Length);
+            
+            await fullLog.FlushAsync();
+        }
+
+        public async static Task LogError(string message)
+        {
+            message =  $"{DateTime.Now.ToLocalTime().ToString("[MM-dd-yyyy HH:mm:ss]")} ERROR {message} \n";
+
+            byte[] logLine = Encoding.Unicode.GetBytes(message);
+            
+            await fullLog.WriteAsync(logLine, 0, logLine.Length);
+            await errLog.WriteAsync(logLine, 0, logLine.Length);
+            
+            await fullLog.FlushAsync();
+            await errLog.FlushAsync();
+        }
+
+        public async static Task LogDebug(string message)
+        {
+            message =  $"{DateTime.Now.ToLocalTime().ToString("[MM-dd-yyyy HH:mm:ss]")} DEBUG {message} \n";
+
+            byte[] logLine = Encoding.Unicode.GetBytes(message);
+            
+            await fullLog.WriteAsync(logLine, 0, logLine.Length);
+            
+            await fullLog.FlushAsync();
+        }
+
+        public static void Stop()
+        {
+            fullLog.Close();
+            errLog.Close();
+        }
+
+        public async static Task Save()
+        {
+            await fullLog.FlushAsync();
+            await errLog.FlushAsync();
+        }
+    }
+}

+ 592 - 0
EthermineBotTelegramCore/Program.cs

@@ -0,0 +1,592 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Telegram.Bot;
+using Telegram.Bot.Args;
+using Telegram.Bot.Types;
+using Telegram.Bot.Types.Enums;
+using System.Net.Http;
+using System.Threading;
+using EFDatabase;
+using Microsoft.EntityFrameworkCore;
+using Quartz;
+using Quartz.Impl;
+
+namespace EthermineBotTelegramCore
+{
+    partial class Program
+    {
+        static ITelegramBotClient botClient;
+        static botdb Botdb;
+
+        private static async Task Main(string[] args)
+        {
+            botClient = new TelegramBotClient("1785154817:AAGhXD9yQVn9HPdWTcmGJUBeZ8nA50SzHbY");
+            var me = botClient.GetMeAsync().Result;
+            {
+                string message = $"Hello, World! I am user {me.Id} and my name is {me.FirstName}.";
+                Console.WriteLine(message);
+                await Logger.LogInfo(message);
+            }
+            
+            //enter credentials for DB
+            if (args.Length >= 2)
+            {
+                AppSettings.user = args[0];
+                AppSettings.password = args[1];
+            }
+            else
+            {
+                Console.Out.Write("User:");
+                AppSettings.user = Console.ReadLine();
+                Console.Out.Write("Password:");
+                AppSettings.password = Console.ReadLine();
+            }
+            
+            //init DB
+            Botdb = new botdb();
+
+            if (Botdb.Database.CanConnect())
+            {
+                // Grab the Scheduler instance from the Factory
+                StdSchedulerFactory factory = new StdSchedulerFactory();
+                IScheduler scheduler = await factory.GetScheduler();
+
+                // and start it off
+                await scheduler.Start();
+
+                // define the job and tie it to our HelloJob class
+                IJobDetail job = JobBuilder.Create<DataUpdater>()
+                    .WithIdentity("job1", "group1")
+                    .UsingJobData("connectionString", "server=" + AppSettings.dbIp +  ";port=3306;database=botdb;uid=" + AppSettings.user + "; pwd=" + AppSettings.password)
+                    .Build();
+                // Trigger the job to run now, and then repeat it
+                ITrigger trigger = TriggerBuilder.Create()
+                    .WithIdentity("trigger1", "group1")
+                    .StartNow()
+                    .WithSimpleSchedule(x => x
+                        .WithIntervalInSeconds(240)
+                        .RepeatForever())
+                    .Build();
+                // Tell quartz to schedule the job using our trigger
+                await scheduler.ScheduleJob(job, trigger);
+
+                {
+                    string message = "Scheduler started!";
+                    Console.WriteLine(message); 
+                    await Logger.LogInfo(message);
+                }
+                
+                Thread.Sleep(5000);
+                
+                //start receive messages
+                botClient.OnMessage += BotOnMessage;
+                botClient.StartReceiving();
+
+                Console.WriteLine("Press any key to exit");
+                Console.ReadKey();
+                
+                Logger.Stop();
+                await scheduler.Shutdown();
+                botClient.StopReceiving();
+            }
+            else
+            {
+                string message = "Don't connected! Check auth data and restart!";
+                Console.WriteLine(message);
+                await Logger.LogError(message);
+                Logger.Stop();
+            }
+
+        }
+
+        private static async void BotOnMessage(object sender, MessageEventArgs e) {
+            if (e.Message.Text != null)
+            {
+                Console.WriteLine($"Received a text message in chat {e.Message.Chat.Id}.");
+
+                char[] delimeters = { ' ', '@' };
+                
+                var message = e.Message;
+                    if (message == null || message.Type != MessageType.Text)
+                        return;
+                    string command = message.Text.Split(delimeters).First();
+                    switch (command)
+                    {
+                        // add user into database
+                        case "/start":
+                            await Logger.LogInfo(
+                                $"Received a text message {command} in chat {e.Message.Chat.Id}.");
+                            AddUser(e.Message.Chat);
+                            break;
+                    
+                        // add user into database
+                        case "/stop":
+                            await Logger.LogInfo(
+                                $"Received a text message {command} in chat {e.Message.Chat.Id}.");
+                            DeleteUser(e.Message.Chat);
+                            break;
+                        
+                        // connect wallet to user
+                        case "/setwallet":
+                            await Logger.LogInfo(
+                                $"Received a text message {command} in chat {e.Message.Chat.Id}.");
+                            SetWallet(e);
+                            break;
+
+                        // get actual data from ethermine
+                        case "/actual":
+                            await Logger.LogInfo(
+                                $"Received a text message {command} in chat {e.Message.Chat.Id}.");
+                            if (message.Text.Split(' ').Length > 1)
+                                GetActualData(e);
+                            else
+                                GetActualDataFromDatabase(e.Message.Chat);
+                            break;
+                        
+                        // get actual data from ethermine
+                        case "/rate":
+                            await Logger.LogInfo(
+                                $"Received a text message {message.Text.Split(delimeters).First()} in chat {e.Message.Chat.Id}.");
+                            GetActualRate(e);
+                            break;
+                        
+                        // send help
+                        case "/help":
+                            await Logger.LogInfo(
+                                $"Received a text message {message.Text.Split(delimeters).First()} in chat {e.Message.Chat.Id}.");
+                            await SendHelp(e.Message.Chat);
+                            break;
+                        
+                        // send last payout
+                        case "/lastpayout":
+                            await Logger.LogInfo(
+                                $"Received a text message {message.Text.Split(delimeters).First()} in chat {e.Message.Chat.Id}.");
+                            await GetLastPayout(e.Message.Chat);
+                            break;
+
+                        default:
+                            if (e.Message.Chat.Id > 0)
+                                await botClient.SendTextMessageAsync(
+                                    chatId: e.Message.Chat,
+                                    text:   "Incorrect message");
+                            break;
+                    }
+            }
+        }
+        //send reauest to pool api and gets back json than parse it and send message with data from it
+        private static async void GetActualData(MessageEventArgs e)
+        {
+            try
+            {
+                var url = AppSettings.poolApiUrl + "/miner/" + e.Message.Text.Split(' ')[1] + "/currentStats";
+                var currnentStats = JsonDownloader._download_serialized_json_data<JsonCurrentStats>(url);
+                await botClient.SendTextMessageAsync(
+                    chatId: e.Message.Chat,
+                    text: "Updated: " + DateTimeOffset.FromUnixTimeSeconds(currnentStats.data.time).LocalDateTime
+                              .ToString("f") + "\n"
+                          + "Reported Hashrate: " + Math.Round(currnentStats.data.reportedHashrate / 1000000D, 3) +
+                          " MH/s\n"
+                          + "Current Hashrate: " + Math.Round(currnentStats.data.currentHashrate / 1000000D, 3) +
+                          " MH/s\n"
+                          + "Valid Shares: " + currnentStats.data.validShares + "\n"
+                          + "Stale Shares: " + currnentStats.data.staleShares + "\n"
+                          + "Workers: " + currnentStats.data.activeWorkers + "\n"
+                          + "Unpaid Balance: " + Math.Round(currnentStats.data.unpaid / Math.Pow(10, 18), 5) + AppSettings.currency + "\n");
+            }
+            catch (Exception exception)
+            {
+                await Console.Out.WriteLineAsync("An exception occurred while executing /actual command. See log file to get full info.");
+                await Logger.LogError("An exception occurred while executing /actual command.");
+                await Logger.LogError(exception.Message);
+                await Logger.LogError(exception.Source);
+                await Logger.LogError(exception.StackTrace);
+                
+                await botClient.SendTextMessageAsync(
+                    chatId: e.Message.Chat,
+                    text:   "An exception occurred while executing this command.");
+            }
+        }
+        //get last data from tables miners and workers from DB and send message to user
+        private static async void GetActualDataFromDatabase(Chat e)
+        {
+            try
+            {
+                var wallet = Botdb.users
+                    .Where(u => u.chat_id == e.Id)
+                    .Select(u => u.wallet)
+                    .FirstOrDefault();
+                if (wallet != null)
+                {
+                    var lastMinerRecord = Botdb.miners
+                        .Where(w => w.wallet == wallet)
+                        .OrderByDescending(w => w.time)
+                        .FirstOrDefault();
+                    
+                    if (lastMinerRecord != null)
+                    {
+                        //var lastWorkerRecord = new workers();
+                        long lastTime = Botdb.workers
+                            .Where(w => w.wallet == wallet)
+                            .Max(w => w.time);
+                        
+                        var lastWorkerRecord = Botdb.workers
+                            .Where(w => w.wallet == wallet && w.time == lastTime)
+                            .OrderByDescending(w => w.time)
+                            .ToList();
+                        
+                        if (lastWorkerRecord.First() != null)
+                        {
+                            await botClient.SendTextMessageAsync(
+                                chatId: e,
+                                text: $"Miner {lastMinerRecord.wallet} stats:\n" +
+                                      "Updated: " + DateTimeOffset.FromUnixTimeSeconds(lastMinerRecord.time)
+                                          .LocalDateTime
+                                          .ToString("f") + "\n"
+                                      + "Reported Hashrate: " + Math.Round(lastMinerRecord.reported_hashrate / 1000000D, 3) + " MH/s\n"
+                                      + "Current Hashrate: " + Math.Round(lastMinerRecord.current_hashrate / 1000000D, 3) + " MH/s\n"
+                                      + "Valid Shares: " + lastMinerRecord.valid_shares + "\n"
+                                      + "Stale Shares: " + lastMinerRecord.stale_shares + "\n"
+                                      + "Workers: " + lastMinerRecord.workers + "\n"
+                                      + "Unpaid Balance: " + Math.Round(lastMinerRecord.unpaid / Math.Pow(10, 18), 5) + AppSettings.currency +
+                                      "\n");
+                            for (int i = 0; i < lastWorkerRecord.Count; i++)
+                            {
+                                Thread.Sleep(1000);
+                                if (i!=0 && i%20 == 0)                           //TODO SOMETIHNG BETTER THAN USUAL TIMER
+                                    Thread.Sleep(30000);
+                                try
+                                {
+                                    await botClient.SendTextMessageAsync(
+                                        chatId: e,
+                                        text: $"Worker {lastWorkerRecord[i].worker} stats:\n" +
+                                              "Updated: " + DateTimeOffset.FromUnixTimeSeconds(lastWorkerRecord[i].time)
+                                                  .LocalDateTime
+                                                  .ToString("f") + "\n"
+                                              + "Reported Hashrate: " + Math.Round(lastWorkerRecord[i].reported_hashrate / 1000000D, 3) + " MH/s\n"
+                                              + "Current Hashrate: " + Math.Round(lastWorkerRecord[i].current_hashrate / 1000000D, 3) + " MH/s\n"
+                                              + "Valid Shares: " + lastWorkerRecord[i].valid_shares + "\n"
+                                              + "Stale Shares: " + lastWorkerRecord[i].stale_shares + "\n"
+                                              + "Unpaid Balance: " + Math.Round(lastWorkerRecord[i].worker_unpaid / Math.Pow(10, 18), 5) + AppSettings.currency +
+                                              "\n");
+                                }
+                                catch (HttpRequestException exception)
+                                {
+                                    await Console.Out.WriteLineAsync("An exception occurred on sending messages while executing /actual command.");
+                                    await Logger.LogError(exception.Message);
+                                    await Logger.LogError(exception.Source);
+                                    await Logger.LogError(exception.StackTrace);
+                                }
+                            }
+                        }
+                        else
+                        {
+                            await botClient.SendTextMessageAsync(
+                                chatId: e,
+                                text:   "No workers data for now!");
+                        }
+                    }
+                    else
+                    {
+                        await botClient.SendTextMessageAsync(
+                            chatId: e,
+                            text:   "No miner data for now!");
+                    }
+                }
+                else
+                {
+                    await botClient.SendTextMessageAsync(
+                        chatId: e,
+                        text:   "Set wallet at first! Use /setwallet");
+                }
+            }
+            catch (Exception exception)
+            {
+                await Console.Out.WriteLineAsync("An exception occurred while executing /actual command. See log file to get full info.");
+                await Logger.LogError("An exception occurred while executing /actual command.");
+                await Logger.LogError(exception.Message);
+                await Logger.LogError(exception.Source);
+                await Logger.LogError(exception.StackTrace);
+                
+                await botClient.SendTextMessageAsync(
+                    chatId: e,
+                    text:   "An exception occurred while executing this command.");
+            }
+        }
+        
+        private static async void GetActualRate(MessageEventArgs e)
+        {
+            try
+            {
+                var url = AppSettings.poolApiUrl + "/networkStats";
+                var networkStats = JsonDownloader._download_serialized_json_data<JsonNetworkStats>(url);
+                await botClient.SendTextMessageAsync(
+                    chatId: e.Message.Chat,
+                    text: "ETH: " + Math.Round(networkStats.data.usd, 2) + "\n" 
+                          + "BTC: " + Math.Round(networkStats.data.usd / networkStats.data.btc, 2)
+                );
+            }
+            catch (Exception exception)
+            {
+                await Console.Out.WriteLineAsync("An exception occurred while executing /rate command. See log file to get full info.");
+                await Logger.LogError("An exception occurred while executing /rate command.");
+                await Logger.LogError(exception.Message);
+                await Logger.LogError(exception.Source);
+                await Logger.LogError(exception.StackTrace);
+                
+                await botClient.SendTextMessageAsync(
+                    chatId: e.Message.Chat,
+                    text:   "An exception occurred while executing this command.");
+            }
+        }
+
+        private static async void AddUser(Chat e)
+        {
+            try
+            {
+                if (Botdb.users.Where(users => users.chat_id == e.Id).FirstOrDefault() == null)
+                {
+                    var newuser = new users();
+                    newuser.chat_id = e.Id;
+                    Botdb.users.Add(newuser);
+                    Botdb.SaveChanges();
+                    await botClient.SendTextMessageAsync(
+                        chatId: e,
+                        text:   "Added new user to database. Now you can connect your miner wallet using command /setwallet <address>");
+                }
+                else
+                {
+                    await botClient.SendTextMessageAsync(
+                        chatId: e,
+                        text:   "Already registered");
+                }
+            }
+            catch (Exception exception)
+            {
+                await Console.Out.WriteLineAsync("An exception occurred while executing /start command. See log file to get full info.");
+                await Logger.LogError("An exception occurred while executing /start command.");
+                await Logger.LogError(exception.Message);
+                await Logger.LogError(exception.Source);
+                await Logger.LogError(exception.StackTrace);
+                
+                await botClient.SendTextMessageAsync(
+                    chatId: e,
+                    text:   "An exception occurred while executing this command.");
+            }
+        }
+
+        private static async void SetWallet(MessageEventArgs e)
+        {
+            try
+            {
+                if (Botdb.users.Where(u => u.chat_id == e.Message.Chat.Id).FirstOrDefault() != null)
+                {
+                    if (e.Message.Text.Split(' ')[1].Length > 2)
+                    {
+                        if (e.Message.Text.Split(' ')[1].Contains("0x"))
+                        {
+                            Botdb.users.Where(u => u.chat_id == e.Message.Chat.Id).FirstOrDefault().wallet = 
+                                e.Message.Text.Split(' ')[1];
+                        }
+                        else
+                        {
+                            Botdb.users.Where(u => u.chat_id == e.Message.Chat.Id).FirstOrDefault().wallet = 
+                                "0x" + e.Message.Text.Split(' ')[1];
+                        }
+
+                        await Botdb.SaveChangesAsync();
+                        await botClient.SendTextMessageAsync(
+                            chatId: e.Message.Chat,
+                            text:   $"Wallet {"0x" + e.Message.Text.Split(' ')[1]} set!");
+                    }
+                    else
+                    {
+                        await botClient.SendTextMessageAsync(
+                            chatId: e.Message.Chat,
+                            text:   "Too short!");
+                    }
+                    //Botdb.SaveChanges();
+                }
+                else
+                {
+                    await botClient.SendTextMessageAsync(
+                        chatId: e.Message.Chat,
+                        text:   "You not registered! Type /start first!");
+                }
+            }
+            catch (Exception exception)
+            {
+                await Console.Out.WriteLineAsync("An exception occurred while executing /setwallet command. See log file to get full info.");
+                await Logger.LogError("An exception occurred while executing /setwallet command.");
+                await Logger.LogError(exception.Message);
+                await Logger.LogError(exception.Source);
+                await Logger.LogError(exception.StackTrace);
+                
+                await botClient.SendTextMessageAsync(
+                    chatId: e.Message.Chat,
+                    text:   "An exception occurred while executing this command.");
+            }
+        }
+	
+        static async void DeleteUser(Chat e)
+        {
+            try
+            {
+                if (Botdb.users.Where(users => users.chat_id == e.Id).FirstOrDefault() != null)
+                {
+                    var deletableWallet = Botdb.users.Where(u => u.chat_id == e.Id).First().wallet;
+                    if (Botdb.users.Where(u => u.wallet == deletableWallet).Count() > 1)
+                    {
+                        Botdb.users.RemoveRange(Botdb.users.Where(u=> u.chat_id == e.Id));
+                    }
+                    else
+                    {
+                        Botdb.workers.RemoveRange(Botdb.workers.Where(w=> w.wallet == deletableWallet));
+                        Botdb.miners.RemoveRange(Botdb.miners.Where(m=> m.wallet == deletableWallet));
+                        Botdb.payouts.RemoveRange(Botdb.payouts.Where(p=> p.wallet == deletableWallet));
+                        Botdb.users.RemoveRange(Botdb.users.Where(u=> u.chat_id == e.Id));
+                    }
+                    
+                    await Botdb.SaveChangesAsync();
+                    await botClient.SendTextMessageAsync(
+                        chatId: e,
+                        text:   "Done!");
+                }
+                else
+                {
+                    await botClient.SendTextMessageAsync(
+                        chatId: e,
+                        text:   "Already deleted");
+                }
+            }
+            catch (Exception exception)
+            {
+                await Console.Out.WriteLineAsync("An exception occurred while executing /delete command. See log file to get full info.");
+                await Logger.LogError("An exception occurred while executing /delete command.");
+                await Logger.LogError(exception.Message);
+                await Logger.LogError(exception.Source);
+                await Logger.LogError(exception.StackTrace);
+                
+                await botClient.SendTextMessageAsync(
+                    chatId: e,
+                    text:   "An exception occurred while executing this command.");
+            }
+        }
+        //gets data from pool API in json, calculates unpaid for each worker and adds it to DB
+        //public static async Task UpdateData()
+
+        static async Task SendHelp(Chat e)
+        {
+            try
+            {
+                await botClient.SendTextMessageAsync(
+                    chatId: e,
+                    text:   $"This is bot tracks your miner stats and calculates unpaid per worker.\n"
+                            +    $"How to start:\n"
+                            +    $"1) \"/start\" - it will add you to database\n"
+                            +    $"2) \"/setwallet <wallet>\" - it will set wallet for tracking, bot will start to add info about your miner and workers to database\n"
+                            +    $"3) \"/actual\" - it will send you up to date data about your worker\n"
+                            +    $"4) \"/lastpayout\" - it will send you last payout data with calculated worker unpaid for that period if avaliable\n"
+                            +    $"5) \"/stop\" - it will clear all data about you from database\n"
+                            +    $"Additional commands:\n"
+                            +    $"\"/rate\" - get actual ETH and BTC rate from ethermine\n"
+                            +    $"\"/actual <wallet>\" - get up to date data about any wallet without unpaid per worker\n");
+            }
+            catch (Exception exception)
+            {
+                await Console.Out.WriteLineAsync("An exception occurred while executing /help command. See log file to get full info.");
+                await Logger.LogError(exception.Message);
+                await Logger.LogError(exception.Source);
+                await Logger.LogError(exception.StackTrace);
+                
+                await botClient.SendTextMessageAsync(
+                    chatId: e,
+                    text:   "An exception occurred while executing this command.");
+            }
+            
+        }
+
+        static async Task GetLastPayout(Chat e)
+        {
+            try
+            {
+                var wallet = Botdb.users
+                                        .Where(u => u.chat_id == e.Id)
+                                        .Select(u => u.wallet)
+                                        .FirstOrDefault();
+                if (wallet != null)
+                {
+                    //TODO rewrite this
+                    long payoutTime;
+                    try
+                    {
+                        payoutTime = Botdb.payouts
+                                        .Where(p => p.wallet == wallet)
+                                        .OrderByDescending(p => p.time)
+                                        .Select(p => p.time)
+                                        .MaxAsync().Result;
+                    }
+                    catch (Exception exception)
+                    {
+                        string message = $"No payouts data for {wallet}! Time is set to 0!";
+                        await Console.Out.WriteLineAsync(message);
+                        await Logger.LogWarn(message);
+                        //await Logger.LogError(exception.Message);
+                        //await Logger.LogError(exception.Source);
+                        //await Logger.LogError(exception.StackTrace);
+                        payoutTime = 0;
+                    }
+                    
+                    var dbPayoutRecords = Botdb.payouts
+                                                                    .Where(p => p.wallet == wallet && p.time == payoutTime)
+                                                                    .OrderByDescending(p => p.time);
+                    if (dbPayoutRecords.FirstOrDefault() != null)
+                    {
+                        string message = "Payout date: " +
+                                         DateTimeOffset.FromUnixTimeSeconds(dbPayoutRecords.First().time).LocalDateTime
+                                             .ToString("f") + "\n" + "Amount: " +
+                                         Math.Round(dbPayoutRecords.First().amount / 1000000000000000000D, 5) + AppSettings.currency + "\n";
+                        foreach (var payoutRecord in dbPayoutRecords)
+                        {
+                            message +=
+                                $"Worker {payoutRecord.worker} paid: {Math.Round(payoutRecord.worker_amount / 1000000000000000000D, 5)} {AppSettings.currency}\n";
+                        }
+
+                        message += "Data source: Bot database \n";
+                        await botClient.SendTextMessageAsync(
+                            chatId: e,
+                            text: message);
+                    }
+                    else
+                    {
+                        var url = AppSettings.poolApiUrl + "/miner/" + wallet + "/payouts";
+                        var payouts = JsonDownloader._download_serialized_json_data<JsonPayouts>(url);
+                        
+                        await botClient.SendTextMessageAsync(
+                            chatId: e,
+                            text: "Payout date: " + DateTimeOffset.FromUnixTimeSeconds(payouts.data[0].paidOn).LocalDateTime.ToString("f") + "\n"
+                                  + "Amount: " + Math.Round(payouts.data[0].amount / 1000000000000000000D, 5) + AppSettings.currency + "\n"
+                                  + "Data source: Ethermine API \n");
+                    }
+                }
+                else
+                {
+                    await botClient.SendTextMessageAsync(
+                        chatId: e,
+                        text: "Set wallet at first! Use /setwallet");
+                }
+            }
+            catch (Exception exception)
+            {
+                await Console.Out.WriteLineAsync("An exception occurred while executing /lastpayout command. See log file to get full info.");
+                await Logger.LogError(exception.Message);
+                await Logger.LogError(exception.Source);
+                await Logger.LogError(exception.StackTrace);
+                
+                await botClient.SendTextMessageAsync(
+                    chatId: e,
+                    text:   "An exception occurred while executing this command.");
+            }
+        }
+    }
+}

+ 283 - 0
EthermineBotTelegramCore/UpdateData.cs

@@ -0,0 +1,283 @@
+using System;
+using System.Linq;
+using EFDatabase;
+using Microsoft.EntityFrameworkCore;
+using System.Threading.Tasks;
+
+namespace EthermineBotTelegramCore
+{
+    partial class Program
+    {
+        public static async Task UpdateData()
+        {
+            var walletList = Botdb.users.Where(u=>u.wallet != null).Select(u=> u.wallet).Distinct().ToList();
+            foreach (string wallet in walletList)
+            {
+                try
+                {
+                    var url = AppSettings.poolApiUrl + "/miner/" + wallet + "/currentStats";
+                    var currentStats = JsonDownloader._download_serialized_json_data<JsonCurrentStats>(url);
+                    
+                    if (currentStats.status == "OK")
+                    {
+                        {
+                            string message = $"Create new record for {wallet}, time {currentStats.data.time}";
+                            await Console.Out.WriteLineAsync(message);
+                            await Logger.LogInfo(message);
+                        }
+                        var newMinerRecord = new miners();
+                        newMinerRecord.wallet = wallet;
+                        newMinerRecord.time = currentStats.data.time;
+                        newMinerRecord.reported_hashrate = currentStats.data.reportedHashrate;
+                        newMinerRecord.current_hashrate = currentStats.data.currentHashrate;
+                        newMinerRecord.valid_shares = currentStats.data.validShares;
+                        newMinerRecord.stale_shares = currentStats.data.staleShares;
+                        newMinerRecord.invalid_shares = currentStats.data.invalidShares;
+                        newMinerRecord.workers = currentStats.data.activeWorkers;
+                        newMinerRecord.unpaid = currentStats.data.unpaid;
+                        await Logger.LogInfo($"New record creating complete {newMinerRecord.wallet}");
+                        long lastTime = 0;
+                        if (Botdb.miners.Where(m => m.wallet == newMinerRecord.wallet).FirstOrDefault() != null) 
+                            lastTime = Botdb.miners.Where(m => m.wallet == newMinerRecord.wallet).Max(m => m.time);
+                        var lastMinerRecord = Botdb.miners.Where(m => m.wallet == newMinerRecord.wallet && m.time == lastTime).FirstOrDefault();
+                        if (lastMinerRecord == null || lastMinerRecord.time !=
+                            newMinerRecord.time)
+                        {
+                            Botdb.miners.Add(newMinerRecord);
+                            //await Botdb.SaveChangesAsync();
+                            {
+                                string message = $"Added new row for {newMinerRecord.wallet}";
+                                await Logger.LogInfo(message);
+                                await Console.Out.WriteLineAsync(message);
+                            }
+
+                        if (lastMinerRecord != null && newMinerRecord.unpaid < lastMinerRecord.unpaid)
+                            {
+                                foreach (var chat_id in Botdb.users.Where(user => user.wallet == newMinerRecord.wallet).Select(u=> u.chat_id))
+                                {
+                                    await botClient.SendTextMessageAsync(
+                                        chatId: chat_id,
+                                    text: "Payout detected!");
+                                }
+                            }
+
+                            url = AppSettings.poolApiUrl + "/miner/" + wallet + "/workers";
+                            var currentWorker = JsonDownloader._download_serialized_json_data<JsonWorker>(url);
+                            
+                            if (currentWorker.status == "OK")
+                            {
+                                for (int i = 0; i < currentWorker.data.Count(); i++)
+                                {
+                                    await Logger.LogInfo($"Create new record for {currentWorker.data[i].worker}, time {currentWorker.data[i].time}");
+                                    var newWorkerRecord = new workers();
+                                    newWorkerRecord.wallet = newMinerRecord.wallet;
+                                    newWorkerRecord.time = currentWorker.data[i].time;
+                                    newWorkerRecord.worker = currentWorker.data[i].worker;
+                                    newWorkerRecord.reported_hashrate = currentWorker.data[i].reportedHashrate;
+                                    newWorkerRecord.current_hashrate = currentWorker.data[i].currentHashrate;
+                                    newWorkerRecord.valid_shares = currentWorker.data[i].validShares;
+                                    newWorkerRecord.stale_shares = currentWorker.data[i].staleShares;
+                                    newWorkerRecord.invalid_shares = currentWorker.data[i].invalidShares;
+
+                                    var lastWorkerRecord = Botdb.workers.Where(w => w.worker == newWorkerRecord.worker)
+                                        .OrderByDescending(w => w.time)
+                                        .FirstOrDefault();
+                                    if (lastWorkerRecord != null)
+                                    {
+                                        //await Logger.LogInfo($"lastWorkerRecord time = {lastWorkerRecord.time}");
+                                        lastMinerRecord = Botdb.miners.Where(w => w.wallet == newMinerRecord.wallet)
+                                            .OrderByDescending(w => w.time)
+                                            .FirstOrDefault();
+                                        if (lastMinerRecord != null)
+                                        {
+                                            //await Logger.LogInfo($"lastMinerRecord time = {lastMinerRecord.time}");
+                                            //check for payout
+                                            if (newMinerRecord.unpaid < lastMinerRecord.unpaid)
+                                            {
+                                                url = AppSettings.poolApiUrl + "/miner/" + wallet + "/payouts";
+                                                var payouts = JsonDownloader._download_serialized_json_data<JsonPayouts>(url);
+                                                
+                                                await Logger.LogInfo($"Last payout time = {payouts.data[0].paidOn}");
+
+                                                var newPayoutRecord = new payouts();
+                                                newPayoutRecord.wallet = wallet;
+                                                newPayoutRecord.time = payouts.data[0].paidOn;
+                                                newPayoutRecord.amount = payouts.data[0].amount;
+                                                newPayoutRecord.worker = newWorkerRecord.worker;
+                                                //calculating payout for worker
+                                                newPayoutRecord.worker_amount = lastWorkerRecord.worker_unpaid +
+                                                    (payouts.data[0].amount -
+                                                     lastMinerRecord.unpaid) *
+                                                    (newWorkerRecord.reported_hashrate /
+                                                     ((double) newMinerRecord.reported_hashrate));
+                                                if (Double.IsNaN(newWorkerRecord.worker_unpaid) ||
+                                                    Double.IsInfinity(newWorkerRecord.worker_unpaid))
+                                                    newPayoutRecord.worker_amount = lastWorkerRecord.worker_unpaid;
+                                                //calculating unpaid for worker
+                                                newWorkerRecord.worker_unpaid = 0 +
+                                                    (newMinerRecord.unpaid) *
+                                                    (newWorkerRecord.reported_hashrate /
+                                                     ((double) newMinerRecord.reported_hashrate));
+                                                if (Double.IsNaN(newWorkerRecord.worker_unpaid) ||
+                                                    Double.IsInfinity(newWorkerRecord.worker_unpaid))
+                                                    newWorkerRecord.worker_unpaid = 0;
+                                                
+                                                Botdb.payouts.Add(newPayoutRecord);
+                                                string message = $"Add newPayoutRecord, time = {newPayoutRecord.time}";
+                                                await Logger.LogInfo(message);
+                                                await Console.Out.WriteLineAsync(message);
+                                                
+                                                //removing old records
+                                                Botdb.workers.RemoveRange(Botdb.workers.Where(w=> w.wallet == newPayoutRecord.wallet && w.time < newPayoutRecord.time));
+                                                Botdb.miners.RemoveRange(Botdb.miners.Where(m=> m.wallet == newPayoutRecord.wallet && m.time < newPayoutRecord.time));
+                                                Botdb.payouts.RemoveRange(Botdb.payouts.Where(p=> p.wallet == newPayoutRecord.wallet));
+                                            }
+                                            else
+                                            {
+                                                //no check that last balance and prev balance are the same
+                                                //don't sure > or >=
+                                                //TODO rewrite this
+                                                long max;
+                                                try
+                                                {
+                                                    max = Botdb.payouts.Where(p => p.wallet == lastWorkerRecord.wallet).Select(p => p.time).MaxAsync().Result;
+                                                }
+                                                catch (Exception exception)
+                                                {
+                                                    string message = $"No payouts data for {lastWorkerRecord.wallet}! Time is set to 0!";
+                                                    await Console.Out.WriteLineAsync(message); 
+                                                    await Logger.LogWarn(message);
+                                                    //await Logger.LogError(exception.Message);
+                                                    //await Logger.LogError(exception.Source);
+                                                    //await Logger.LogError(exception.StackTrace);
+                                                    max = 0;
+                                                }
+
+                                                //await Console.Out.WriteLineAsync(max..ToString());
+                                                if (lastWorkerRecord.time > max)
+                                                {
+                                                    newWorkerRecord.worker_unpaid = lastWorkerRecord.worker_unpaid +
+                                                        (newMinerRecord.unpaid -
+                                                         lastMinerRecord.unpaid) *
+                                                        (newWorkerRecord.reported_hashrate /
+                                                         ((double) newMinerRecord.reported_hashrate));
+                                                    if (Double.IsNaN(newWorkerRecord.worker_unpaid) ||
+                                                        Double.IsInfinity(newWorkerRecord.worker_unpaid))
+                                                        newWorkerRecord.worker_unpaid = lastWorkerRecord.worker_unpaid;
+                                                    //await Logger.LogInfo($"newWorkerRecord unpaid change = {newMinerRecord.unpaid - lastMinerRecord.unpaid}");
+                                                }
+                                                else
+                                                {
+                                                    newWorkerRecord.worker_unpaid = 
+                                                        (newMinerRecord.unpaid -
+                                                         lastMinerRecord.unpaid) *
+                                                        (newWorkerRecord.reported_hashrate /
+                                                         ((double) newMinerRecord.reported_hashrate));
+                                                    if (Double.IsNaN(newWorkerRecord.worker_unpaid) ||
+                                                        Double.IsInfinity(newWorkerRecord.worker_unpaid))
+                                                        newWorkerRecord.worker_unpaid = lastWorkerRecord.worker_unpaid;
+                                                    //await Logger.LogInfo($"newWorkerRecord unpaid change = {newMinerRecord.unpaid - lastMinerRecord.unpaid}");
+                                                    
+                                                    foreach (var chat_id in Botdb.users.Where(u=> u.wallet == newMinerRecord.wallet).Select(u=> u.wallet))
+                                                    {
+                                                        await botClient.SendTextMessageAsync(
+                                                            chatId: chat_id,
+                                                            text:   $"Debug info: Your worker {newWorkerRecord.worker} hasn't been zeroed on payout!\n");
+                                                        await Logger.LogDebug($"Worker {newWorkerRecord.worker} on address {newWorkerRecord.wallet} hasn't been zeroed on payout!");
+                                                    }
+                                                }
+                                            }
+                                        }
+                                        else
+                                        {
+                                            newWorkerRecord.worker_unpaid = 0;
+                                        }
+                                    }
+                                    else
+                                    {
+                                        newWorkerRecord.worker_unpaid = 0;
+                                    }
+                                    //await Logger.LogInfo($"newWorkerRecord worker unpaid = {newWorkerRecord.worker_unpaid}");
+                                    //this if does nothing
+                                    if (newWorkerRecord.reported_hashrate != 0)
+                                    {
+                                        if (lastWorkerRecord == null || newWorkerRecord.time != lastWorkerRecord.time)
+                                        {
+                                            Botdb.workers.Add(newWorkerRecord);
+                                            //await Botdb.SaveChangesAsync();
+                                        }
+                                        //Botdb.SaveChanges();
+                                        else
+                                        {
+                                            Botdb.workers.Add(newWorkerRecord);
+                                        }
+
+                                        string message = $"Added new row for {newWorkerRecord.worker}";
+                                        await Logger.LogInfo(message);
+                                        await Console.Out.WriteLineAsync(message);
+                                    }
+                                }
+                            }
+                        }
+                        else
+                        {
+                            string message =
+                                $"Row with wallet: {newMinerRecord.wallet} and time: {newMinerRecord.time} already exists!";
+                            await Console.Out.WriteLineAsync(message);
+                            await Logger.LogInfo(message);
+                        }
+                        
+                        try
+                        {
+                            await Botdb.SaveChangesAsync();
+                            string message = $"Saved data for {newMinerRecord.wallet}";
+                            await Console.Out.WriteLineAsync(message);
+                            await Logger.LogInfo(message);
+                        }
+                        catch (Exception exception)
+                        {
+                            foreach (var entity in Botdb.payouts.Local)
+                            {
+                                var entry = Botdb.Entry(entity);
+
+                                if (entry.State == EntityState.Added)
+                                    entry.State = EntityState.Detached;                    
+                            }
+                            foreach (var entity in Botdb.workers.Local)
+                            {
+                                var entry = Botdb.Entry(entity);
+
+                                if (entry.State == EntityState.Added)
+                                    entry.State = EntityState.Detached;                    
+                            }
+                            foreach (var entity in Botdb.miners.Local)
+                            {
+                                var entry = Botdb.Entry(entity);
+
+                                if (entry.State == EntityState.Added)
+                                    entry.State = EntityState.Detached;                    
+                            }
+                            await Console.Out.WriteLineAsync("An exception occurred while updating data. See log file to get full info.");
+                            await Logger.LogError(exception.Message);
+                            await Logger.LogError(exception.Source);
+                            await Logger.LogError(exception.StackTrace);
+                        }
+                    }
+                    else
+                    {
+                        await Console.Out.WriteLineAsync($"Error response from pool for {wallet}!");
+                        await Logger.LogError($"Error response from pool for {wallet}!");
+                    }
+                }
+                catch (Exception exception)
+                {
+                    await Console.Out.WriteLineAsync("An exception occurred while updating data. See log file to get full info.");
+                    await Logger.LogError(exception.Message);
+                    await Logger.LogError(exception.Source);
+                    await Logger.LogError(exception.StackTrace);
+                }
+            }
+        }
+    }
+    
+}

+ 26 - 0
EthermineBotTelegramCore/packages.config

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="BouncyCastle" version="1.8.5" targetFramework="net472" />
+  <package id="EntityFramework" version="6.4.4" targetFramework="net472" />
+  <package id="Google.Protobuf" version="3.14.0" targetFramework="net472" />
+  <package id="K4os.Compression.LZ4" version="1.1.11" targetFramework="net472" />
+  <package id="K4os.Compression.LZ4.Streams" version="1.1.11" targetFramework="net472" />
+  <package id="K4os.Hash.xxHash" version="1.0.6" targetFramework="net472" />
+  <package id="Microsoft.Extensions.Configuration.Abstractions" version="2.1.1" targetFramework="net472" />
+  <package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="2.1.1" targetFramework="net472" />
+  <package id="Microsoft.Extensions.Options" version="2.1.1" targetFramework="net472" />
+  <package id="Microsoft.Extensions.Primitives" version="2.1.1" targetFramework="net472" />
+  <package id="Quartz" version="3.3.2" targetFramework="net472" />
+  <package id="Quartz.Extensions.DependencyInjection" version="3.3.2" targetFramework="net472" />
+  <package id="System.Diagnostics.DiagnosticSource" version="4.7.1" targetFramework="net472" />
+  <package id="System.Memory" version="4.5.4" targetFramework="net472" />
+  <package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net472" />
+  <package id="System.Runtime.CompilerServices.Unsafe" version="4.5.3" targetFramework="net472" />
+  <package id="Microsoft.Extensions.Logging.Abstractions" version="2.1.1" targetFramework="net472" />
+  <package id="MySql.Data" version="8.0.24" targetFramework="net472" />
+  <package id="MySql.Data.EntityFramework" version="8.0.24" targetFramework="net472" />
+  <package id="Newtonsoft.Json" version="11.0.2" targetFramework="net472" />
+  <package id="System.Buffers" version="4.5.1" targetFramework="net472" />
+  <package id="System.Net.Requests" version="4.3.0" targetFramework="net472" />
+  <package id="Telegram.Bot" version="15.7.1" targetFramework="net472" />
+</packages>