mirror of
https://github.com/Jetsparrow/jetherald.git
synced 2026-01-20 23:56:08 +03:00
Initial release
This commit is contained in:
commit
a8485edcf9
45
.gitignore
vendored
Normal file
45
.gitignore
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Autosave files
|
||||||
|
*~
|
||||||
|
|
||||||
|
# build
|
||||||
|
[Oo]bj/
|
||||||
|
[Bb]in/
|
||||||
|
packages/
|
||||||
|
TestResults/
|
||||||
|
|
||||||
|
# globs
|
||||||
|
Makefile.in
|
||||||
|
*.DS_Store
|
||||||
|
*.sln.cache
|
||||||
|
*.suo
|
||||||
|
*.cache
|
||||||
|
*.pidb
|
||||||
|
*.userprefs
|
||||||
|
*.usertasks
|
||||||
|
config.log
|
||||||
|
config.make
|
||||||
|
config.status
|
||||||
|
aclocal.m4
|
||||||
|
install-sh
|
||||||
|
autom4te.cache/
|
||||||
|
*.user
|
||||||
|
*.tar.gz
|
||||||
|
tarballs/
|
||||||
|
test-results/
|
||||||
|
Thumbs.db
|
||||||
|
.vs/
|
||||||
|
|
||||||
|
# Mac bundle stuff
|
||||||
|
*.dmg
|
||||||
|
*.app
|
||||||
|
|
||||||
|
# resharper
|
||||||
|
*_Resharper.*
|
||||||
|
*.Resharper
|
||||||
|
|
||||||
|
# dotCover
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
#secret config
|
||||||
|
karma.cfg.json
|
||||||
|
*secrets.ini
|
||||||
25
JetHerald.sln
Normal file
25
JetHerald.sln
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 15
|
||||||
|
VisualStudioVersion = 15.0.28307.539
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JetHerald", "JetHerald\JetHerald.csproj", "{B48207B2-F0A8-4BD8-AF92-906D128EF152}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{B48207B2-F0A8-4BD8-AF92-906D128EF152}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B48207B2-F0A8-4BD8-AF92-906D128EF152}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B48207B2-F0A8-4BD8-AF92-906D128EF152}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B48207B2-F0A8-4BD8-AF92-906D128EF152}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {A8B3E56D-CF82-4D94-B2CD-396A1AF6718B}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
65
JetHerald/ChatCommandRouter.cs
Normal file
65
JetHerald/ChatCommandRouter.cs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Telegram.Bot.Args;
|
||||||
|
|
||||||
|
namespace JetHerald
|
||||||
|
{
|
||||||
|
public interface IChatCommand
|
||||||
|
{
|
||||||
|
Task<string> Execute(CommandString cmd, MessageEventArgs messageEventArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ChatCommandRouter
|
||||||
|
{
|
||||||
|
string Username { get; }
|
||||||
|
ILogger Log { get; }
|
||||||
|
|
||||||
|
public ChatCommandRouter(string username, ILogger log)
|
||||||
|
{
|
||||||
|
Log = log;
|
||||||
|
Username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> Execute(object sender, MessageEventArgs args)
|
||||||
|
{
|
||||||
|
var text = args.Message.Text;
|
||||||
|
if (CommandString.TryParse(text, out var cmd))
|
||||||
|
{
|
||||||
|
if (cmd.UserName != null && cmd.UserName != Username)
|
||||||
|
{
|
||||||
|
Log.LogDebug("Message not directed at us");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (commands.ContainsKey(cmd.Command))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Log.LogDebug($"Handling message via {commands[cmd.Command].GetType().Name}");
|
||||||
|
return await commands[cmd.Command].Execute(cmd, args);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.LogError(e, $"Error while executing command {cmd.Command}!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Log.LogDebug($"Command {cmd.Command} not found");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(IChatCommand c, params string[] cmds)
|
||||||
|
{
|
||||||
|
foreach (var cmd in cmds)
|
||||||
|
{
|
||||||
|
if (commands.ContainsKey(cmd))
|
||||||
|
throw new ArgumentException($"collision for {cmd}, commands {commands[cmd].GetType()} and {c.GetType()}");
|
||||||
|
commands[cmd] = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary<string, IChatCommand> commands = new Dictionary<string, IChatCommand>();
|
||||||
|
}
|
||||||
|
}
|
||||||
42
JetHerald/CommandString.cs
Normal file
42
JetHerald/CommandString.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace JetHerald
|
||||||
|
{
|
||||||
|
public class CommandString
|
||||||
|
{
|
||||||
|
public CommandString(string command, string username, params string[] parameters)
|
||||||
|
{
|
||||||
|
Command = command;
|
||||||
|
Parameters = parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Command { get; }
|
||||||
|
public string UserName { get; }
|
||||||
|
public string[] Parameters { get; }
|
||||||
|
|
||||||
|
static readonly char[] WS_CHARS = new[] { ' ', '\r', '\n', '\n' };
|
||||||
|
|
||||||
|
public static bool TryParse(string s, out CommandString result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
if (string.IsNullOrWhiteSpace(s) || s[0] != '/')
|
||||||
|
return false;
|
||||||
|
|
||||||
|
string[] words = s.Split(WS_CHARS, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
var cmdRegex = new Regex(@"/(?<cmd>\w+)(@(?<name>\w+))?");
|
||||||
|
var match = cmdRegex.Match(words.First());
|
||||||
|
if (!match.Success)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
string cmd = match.Groups["cmd"].Captures[0].Value;
|
||||||
|
string username = match.Groups["name"].Captures.Count > 0 ? match.Groups["name"].Captures[0].Value : null;
|
||||||
|
string[] parameters = words.Skip(1).ToArray();
|
||||||
|
|
||||||
|
result = new CommandString(cmd, username, parameters);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
47
JetHerald/Commands/CreateTopicCommand.cs
Normal file
47
JetHerald/Commands/CreateTopicCommand.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MySql.Data.MySqlClient;
|
||||||
|
using Telegram.Bot.Args;
|
||||||
|
using Telegram.Bot.Types.Enums;
|
||||||
|
|
||||||
|
namespace JetHerald.Commands
|
||||||
|
{
|
||||||
|
public class CreateTopicCommand : IChatCommand
|
||||||
|
{
|
||||||
|
Db db;
|
||||||
|
|
||||||
|
public CreateTopicCommand(Db db)
|
||||||
|
{
|
||||||
|
this.db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> Execute(CommandString cmd, MessageEventArgs messageEventArgs)
|
||||||
|
{
|
||||||
|
if (cmd.Parameters.Length < 1)
|
||||||
|
return null;
|
||||||
|
var msg = messageEventArgs.Message;
|
||||||
|
var chatid = msg.Chat.Id;
|
||||||
|
|
||||||
|
if (msg.Chat.Type != ChatType.Private)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
string name = cmd.Parameters[0];
|
||||||
|
string descr = name;
|
||||||
|
if (cmd.Parameters.Length > 1)
|
||||||
|
descr = string.Join(' ', cmd.Parameters.Skip(1));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var topic = await db.CreateTopic(msg.From.Id, name, descr);
|
||||||
|
return $"created {topic.Name}\n" +
|
||||||
|
$"readToken\n{topic.ReadToken}\n" +
|
||||||
|
$"writeToken\n{topic.WriteToken}\n" +
|
||||||
|
$"adminToken\n{topic.AdminToken}\n";
|
||||||
|
}
|
||||||
|
catch (MySqlException myDuplicate) when (myDuplicate.Number == 1062)
|
||||||
|
{
|
||||||
|
return $"topic {name} already exists";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
JetHerald/Commands/DeleteTopicCommand.cs
Normal file
33
JetHerald/Commands/DeleteTopicCommand.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Telegram.Bot.Args;
|
||||||
|
using Telegram.Bot.Types.Enums;
|
||||||
|
|
||||||
|
namespace JetHerald.Commands
|
||||||
|
{
|
||||||
|
public class DeleteTopicCommand : IChatCommand
|
||||||
|
{
|
||||||
|
Db db;
|
||||||
|
|
||||||
|
public DeleteTopicCommand(Db db)
|
||||||
|
{
|
||||||
|
this.db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> Execute(CommandString cmd, MessageEventArgs messageEventArgs)
|
||||||
|
{
|
||||||
|
if (cmd.Parameters.Length < 2)
|
||||||
|
return null;
|
||||||
|
var msg = messageEventArgs.Message;
|
||||||
|
var chatid = msg.Chat.Id;
|
||||||
|
|
||||||
|
if (msg.Chat.Type != ChatType.Private)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
string name = cmd.Parameters[0];
|
||||||
|
string adminToken = cmd.Parameters[1];
|
||||||
|
|
||||||
|
var topic = await db.DeleteTopic(name, adminToken);
|
||||||
|
return $"deleted {name} and all its subscriptions";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
JetHerald/Commands/ListCommand.cs
Normal file
31
JetHerald/Commands/ListCommand.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Telegram.Bot.Args;
|
||||||
|
|
||||||
|
namespace JetHerald
|
||||||
|
{
|
||||||
|
public class ListCommand : IChatCommand
|
||||||
|
{
|
||||||
|
Db db;
|
||||||
|
|
||||||
|
public ListCommand(Db db)
|
||||||
|
{
|
||||||
|
this.db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> Execute(CommandString cmd, MessageEventArgs messageEventArgs)
|
||||||
|
{
|
||||||
|
var msg = messageEventArgs.Message;
|
||||||
|
var chatid = msg.Chat.Id;
|
||||||
|
var topics = await db.GetTopicsForChat(chatid);
|
||||||
|
|
||||||
|
return topics.Any()
|
||||||
|
? "Topics:\n" + string.Join("\n", topics.Select(GetTopicListing))
|
||||||
|
: "No subscriptions active.";
|
||||||
|
}
|
||||||
|
|
||||||
|
static string GetTopicListing(Db.Topic t)
|
||||||
|
=> t.Name == t.Description ? t.Name : $"{t.Name}: {t.Description}";
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
38
JetHerald/Commands/SubscribeCommand.cs
Normal file
38
JetHerald/Commands/SubscribeCommand.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Telegram.Bot.Args;
|
||||||
|
|
||||||
|
namespace JetHerald
|
||||||
|
{
|
||||||
|
public class SubscribeCommand : IChatCommand
|
||||||
|
{
|
||||||
|
Db db;
|
||||||
|
|
||||||
|
public SubscribeCommand(Db db)
|
||||||
|
{
|
||||||
|
this.db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> Execute(CommandString cmd, MessageEventArgs args)
|
||||||
|
{
|
||||||
|
if (cmd.Parameters.Length < 1)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var chatid = args.Message.Chat.Id;
|
||||||
|
var token = cmd.Parameters[0];
|
||||||
|
|
||||||
|
var topic = await db.GetTopic(token, chatid);
|
||||||
|
|
||||||
|
if (topic == null)
|
||||||
|
return "topic not found";
|
||||||
|
else if (topic.ChatId == chatid)
|
||||||
|
return $"already subscribed to {topic.Name}";
|
||||||
|
else if (topic.ReadToken != token)
|
||||||
|
return "token mismatch";
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await db.CreateSubscription(topic.TopicId, chatid);
|
||||||
|
return $"subscribed to {topic.Name}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
JetHerald/Commands/UnsubscribeCommand.cs
Normal file
30
JetHerald/Commands/UnsubscribeCommand.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Telegram.Bot.Args;
|
||||||
|
|
||||||
|
namespace JetHerald
|
||||||
|
{
|
||||||
|
public class UnsubscribeCommand : IChatCommand
|
||||||
|
{
|
||||||
|
Db db;
|
||||||
|
|
||||||
|
public UnsubscribeCommand(Db db)
|
||||||
|
{
|
||||||
|
this.db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> Execute(CommandString cmd, MessageEventArgs messageEventArgs)
|
||||||
|
{
|
||||||
|
if (cmd.Parameters.Length < 1)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var msg = messageEventArgs.Message;
|
||||||
|
var chatid = msg.Chat.Id;
|
||||||
|
var topicName = cmd.Parameters[0];
|
||||||
|
int affected = await db.RemoveSubscription(topicName, chatid);
|
||||||
|
if (affected >= 1)
|
||||||
|
return $"unsubscribed from {topicName}";
|
||||||
|
else
|
||||||
|
return $"could not find subscription for {topicName}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
JetHerald/Configs.cs
Normal file
17
JetHerald/Configs.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
namespace JetHerald.Options
|
||||||
|
{
|
||||||
|
public class ConnectionStrings
|
||||||
|
{
|
||||||
|
public string DefaultConnection { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Telegram
|
||||||
|
{
|
||||||
|
public string ApiKey { get; set; }
|
||||||
|
|
||||||
|
public bool UseProxy { get; set; }
|
||||||
|
public string ProxyUrl { get; set; }
|
||||||
|
public string ProxyPassword { get; set; }
|
||||||
|
public string ProxyLogin { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
42
JetHerald/Controllers/ReportController.cs
Normal file
42
JetHerald/Controllers/ReportController.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace JetHerald.Controllers
|
||||||
|
{
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[ApiController]
|
||||||
|
public class ReportController : ControllerBase
|
||||||
|
{
|
||||||
|
Db Db { get; }
|
||||||
|
JetHeraldBot Herald { get; }
|
||||||
|
|
||||||
|
public ReportController(Db db, JetHeraldBot herald)
|
||||||
|
{
|
||||||
|
Herald = herald;
|
||||||
|
Db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> Post([FromBody] ReportArgs args)
|
||||||
|
{
|
||||||
|
var t = await Db.GetTopic(args.Topic);
|
||||||
|
if (t == null)
|
||||||
|
return new NotFoundResult();
|
||||||
|
else if (!t.WriteToken.Equals(args.WriteToken, StringComparison.OrdinalIgnoreCase))
|
||||||
|
return StatusCode(403);
|
||||||
|
|
||||||
|
await Herald.PublishMessage(t, args.Message);
|
||||||
|
return new OkResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataContract]
|
||||||
|
public class ReportArgs
|
||||||
|
{
|
||||||
|
[DataMember] public string Topic { get; set; }
|
||||||
|
[DataMember] public string Message { get; set; }
|
||||||
|
[DataMember] public string WriteToken { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
120
JetHerald/Db.cs
Normal file
120
JetHerald/Db.cs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MySql.Data.MySqlClient;
|
||||||
|
using Dapper;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using System;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Transactions;
|
||||||
|
|
||||||
|
namespace JetHerald
|
||||||
|
{
|
||||||
|
public static class TokenHelper
|
||||||
|
{
|
||||||
|
static RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
|
||||||
|
static byte[] buf = new byte[24];
|
||||||
|
|
||||||
|
public static string GetToken()
|
||||||
|
{
|
||||||
|
rng.GetBytes(buf);
|
||||||
|
return Convert.ToBase64String(buf).Replace('+', '_').Replace('/','_');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Db
|
||||||
|
{
|
||||||
|
public class Topic
|
||||||
|
{
|
||||||
|
public uint TopicId { get; set; }
|
||||||
|
public long CreatorId { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string ReadToken { get; set; }
|
||||||
|
public string WriteToken { get; set; }
|
||||||
|
public string AdminToken { get; set; }
|
||||||
|
|
||||||
|
public long? ChatId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> DeleteTopic(string name, string adminToken)
|
||||||
|
{
|
||||||
|
using (var c = GetConnection())
|
||||||
|
{
|
||||||
|
return await c.ExecuteAsync("DELETE FROM topic WHERE Name = @name AND AdminToken = @adminToken", new { name, adminToken });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Topic> GetTopic(string name)
|
||||||
|
{
|
||||||
|
using (var c = GetConnection())
|
||||||
|
return await c.QuerySingleOrDefaultAsync<Topic>("SELECT * FROM topic WHERE Name = @name", new { name });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Topic> GetTopic(string token, long chatId)
|
||||||
|
{
|
||||||
|
using (var c = GetConnection())
|
||||||
|
return await c.QuerySingleOrDefaultAsync<Topic>(
|
||||||
|
"SELECT t.*, tc.ChatId " +
|
||||||
|
"FROM topic t LEFT JOIN topic_chat tc ON t.TopicId = tc.TopicId AND tc.ChatId = @chatId " +
|
||||||
|
"WHERE ReadToken = @token", new { token, chatId});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Topic> CreateTopic(long userId, string name, string descr)
|
||||||
|
{
|
||||||
|
var t = new Topic
|
||||||
|
{
|
||||||
|
CreatorId = userId,
|
||||||
|
Name = name,
|
||||||
|
Description = descr,
|
||||||
|
ReadToken = TokenHelper.GetToken(),
|
||||||
|
WriteToken = TokenHelper.GetToken(),
|
||||||
|
AdminToken = TokenHelper.GetToken()
|
||||||
|
};
|
||||||
|
using (var c = GetConnection())
|
||||||
|
{
|
||||||
|
return await c.QuerySingleOrDefaultAsync<Topic>(
|
||||||
|
" INSERT INTO herald.topic " +
|
||||||
|
" (TopicId, CreatorId, Name, Description, ReadToken, WriteToken, AdminToken) " +
|
||||||
|
" VALUES " +
|
||||||
|
" (NULL, @CreatorId, @Name, @Description, @ReadToken, @WriteToken, @AdminToken); " +
|
||||||
|
" SELECT * FROM topic WHERE TopicId = LAST_INSERT_ID(); ",
|
||||||
|
t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public async Task<IEnumerable<long>> GetChatIdsForTopic(uint topicid)
|
||||||
|
{
|
||||||
|
using (var c = GetConnection())
|
||||||
|
return await c.QueryAsync<long>("SELECT ChatId FROM topic_chat WHERE TopicId = @topicid", new { topicid });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Topic>> GetTopicsForChat(long chatid)
|
||||||
|
{
|
||||||
|
using (var c = GetConnection())
|
||||||
|
return await c.QueryAsync<Topic>("SELECT t.* FROM topic_chat ct JOIN topic t on t.TopicId = ct.TopicId WHERE ct.ChatId = @chatid", new { chatid });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CreateSubscription(uint topicId, long chatId)
|
||||||
|
{
|
||||||
|
using (var c = GetConnection())
|
||||||
|
await c.ExecuteAsync("INSERT INTO topic_chat (ChatId, TopicId ) VALUES (@chatId, @topicId)", new { topicId, chatId });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> RemoveSubscription(string topicName, long chatId)
|
||||||
|
{
|
||||||
|
using (var c = GetConnection())
|
||||||
|
return await c.ExecuteAsync(
|
||||||
|
"DELETE tc " +
|
||||||
|
"FROM topic_chat tc JOIN topic t ON tc.TopicId = t.TopicId " +
|
||||||
|
"WHERE t.Name = @topicName AND tc.ChatId = @chatId;",
|
||||||
|
new { topicName, chatId });
|
||||||
|
}
|
||||||
|
|
||||||
|
public Db(IOptions<Options.ConnectionStrings> cfg)
|
||||||
|
{
|
||||||
|
Config = cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
IOptions<Options.ConnectionStrings> Config { get; }
|
||||||
|
MySqlConnection GetConnection() => new MySqlConnection(Config.Value.DefaultConnection);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
JetHerald/JetHerald.csproj
Normal file
27
JetHerald/JetHerald.csproj
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="wwwroot\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Dapper" Version="1.60.6" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.App" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.1.2" PrivateAssets="All" />
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.1.1" />
|
||||||
|
<PackageReference Include="MySql.Data" Version="8.0.17" />
|
||||||
|
<PackageReference Include="NLog.Web.AspNetCore" Version="4.8.4" />
|
||||||
|
<PackageReference Include="Telegram.Bot" Version="14.12.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Update="appsettings.json">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
85
JetHerald/JetHeraldBot.cs
Normal file
85
JetHerald/JetHeraldBot.cs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Telegram.Bot;
|
||||||
|
using Telegram.Bot.Args;
|
||||||
|
using Telegram.Bot.Types.Enums;
|
||||||
|
|
||||||
|
using JetHerald.Commands;
|
||||||
|
|
||||||
|
namespace JetHerald
|
||||||
|
{
|
||||||
|
public class JetHeraldBot
|
||||||
|
{
|
||||||
|
Db Db { get; set; }
|
||||||
|
Options.Telegram Config { get; }
|
||||||
|
ILogger<JetHeraldBot> Log { get; }
|
||||||
|
|
||||||
|
public JetHeraldBot(Db db, IOptions<Options.Telegram> cfg, ILogger<JetHeraldBot> log)
|
||||||
|
{
|
||||||
|
Db = db;
|
||||||
|
Config = cfg.Value;
|
||||||
|
Log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
TelegramBotClient Client { get; set; }
|
||||||
|
ChatCommandRouter Commands;
|
||||||
|
Telegram.Bot.Types.User Me { get; set; }
|
||||||
|
|
||||||
|
public async Task Init()
|
||||||
|
{
|
||||||
|
if (Config.UseProxy)
|
||||||
|
{
|
||||||
|
Client = new TelegramBotClient(Config.ApiKey);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var httpProxy = new WebProxy(Config.ProxyUrl)
|
||||||
|
{ Credentials = new NetworkCredential(Config.ProxyLogin, Config.ProxyPassword) };
|
||||||
|
Client = new TelegramBotClient(Config.ApiKey, httpProxy);
|
||||||
|
}
|
||||||
|
Me = await Client.GetMeAsync();
|
||||||
|
|
||||||
|
Commands = new ChatCommandRouter(Me.Username, Log);
|
||||||
|
Commands.Add(new CreateTopicCommand(Db), "createtopic");
|
||||||
|
Commands.Add(new DeleteTopicCommand(Db), "deletetopic");
|
||||||
|
Commands.Add(new SubscribeCommand(Db), "subscribe", "sub");
|
||||||
|
Commands.Add(new UnsubscribeCommand(Db), "unsubscribe", "unsub");
|
||||||
|
Commands.Add(new ListCommand(Db), "list");
|
||||||
|
|
||||||
|
Client.OnMessage += BotOnMessageReceived;
|
||||||
|
Client.StartReceiving();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PublishMessage(Db.Topic topic, string message)
|
||||||
|
{
|
||||||
|
var chatIds = await Db.GetChatIdsForTopic(topic.TopicId);
|
||||||
|
var formatted = $"|{topic.Description}|:\n{message}";
|
||||||
|
foreach (var c in chatIds)
|
||||||
|
await Client.SendTextMessageAsync(c, formatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
async void BotOnMessageReceived(object sender, MessageEventArgs messageEventArgs)
|
||||||
|
{
|
||||||
|
var msg = messageEventArgs.Message;
|
||||||
|
if (msg == null || msg.Type != MessageType.Text)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var reply = await Commands.Execute(sender, messageEventArgs);
|
||||||
|
if (reply != null)
|
||||||
|
await Client.SendTextMessageAsync(
|
||||||
|
chatId: msg.Chat.Id,
|
||||||
|
text: reply,
|
||||||
|
replyToMessageId: msg.MessageId);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.LogError(e, "Exception occured during handling of command: "+ msg.Text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
JetHerald/NLog.config
Normal file
27
JetHerald/NLog.config
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
autoReload="true">
|
||||||
|
|
||||||
|
<!-- enable asp.net core layout renderers -->
|
||||||
|
<extensions>
|
||||||
|
<add assembly="NLog.Web.AspNetCore"/>
|
||||||
|
</extensions>
|
||||||
|
|
||||||
|
<targets>
|
||||||
|
<target xsi:type="File" name="allfile" fileName="logs\nlog-all-${date:format=yyyy-MM-dd-HH-mm-ss}.log"
|
||||||
|
layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}" />
|
||||||
|
|
||||||
|
<target name="logconsole" xsi:type="Console" />
|
||||||
|
</targets>
|
||||||
|
|
||||||
|
<!-- rules to map from logger name to target -->
|
||||||
|
<rules>
|
||||||
|
<!--All logs, including from Microsoft-->
|
||||||
|
<logger name="*" minlevel="Trace" writeTo="allfile" />
|
||||||
|
<logger name="*" minlevel="Trace" writeTo="logconsole" />
|
||||||
|
|
||||||
|
<!--Skip non-critical Microsoft logs and so log only own logs-->
|
||||||
|
<logger name="Microsoft.*" maxlevel="Info" final="true" />
|
||||||
|
</rules>
|
||||||
|
</nlog>
|
||||||
49
JetHerald/Program.cs
Normal file
49
JetHerald/Program.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using NLog.Web;
|
||||||
|
|
||||||
|
namespace JetHerald
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var logger = NLog.Web.NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
logger.Debug("init main");
|
||||||
|
CreateWebHostBuilder(args).Build().Run();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.Error(ex, "Stopped program because of exception");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
NLog.LogManager.Shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
||||||
|
WebHost.CreateDefaultBuilder(args)
|
||||||
|
.UseStartup<Startup>()
|
||||||
|
.ConfigureAppConfiguration(config =>
|
||||||
|
{
|
||||||
|
config.AddIniFile("secrets.ini");
|
||||||
|
})
|
||||||
|
.ConfigureLogging(logging =>
|
||||||
|
{
|
||||||
|
logging.ClearProviders();
|
||||||
|
logging.SetMinimumLevel(LogLevel.Trace);
|
||||||
|
})
|
||||||
|
.UseNLog(); // NLog: setup NLog for Dependency injection
|
||||||
|
}
|
||||||
|
}
|
||||||
30
JetHerald/Properties/launchSettings.json
Normal file
30
JetHerald/Properties/launchSettings.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:57041",
|
||||||
|
"sslPort": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": false,
|
||||||
|
"launchUrl": "api/values",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"JetHerald": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": false,
|
||||||
|
"launchUrl": "api/values",
|
||||||
|
"applicationUrl": "http://localhost:5000",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
JetHerald/Startup.cs
Normal file
43
JetHerald/Startup.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace JetHerald
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public Startup(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.Configure<Options.ConnectionStrings>(Configuration.GetSection("ConnectionStrings"));
|
||||||
|
services.Configure<Options.Telegram>(Configuration.GetSection("Telegram"));
|
||||||
|
services.AddSingleton<Db>();
|
||||||
|
services.AddSingleton<JetHeraldBot>();
|
||||||
|
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||||
|
{
|
||||||
|
var bot = app.ApplicationServices.GetService<JetHeraldBot>();
|
||||||
|
bot.Init().Wait();
|
||||||
|
app.UsePathBase(Configuration.GetValue<string>("PathBase"));
|
||||||
|
if (env.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseDeveloperExceptionPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseMvc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
JetHerald/appsettings.Development.json
Normal file
9
JetHerald/appsettings.Development.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Debug",
|
||||||
|
"System": "Information",
|
||||||
|
"Microsoft": "Information"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
JetHerald/appsettings.json
Normal file
11
JetHerald/appsettings.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Telegram": {
|
||||||
|
"UseProxy": "false"
|
||||||
|
},
|
||||||
|
"PathBase": "/"
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user