Discord support part 1

This commit is contained in:
Basique Evangelist 2021-04-18 23:25:40 +03:00
parent 041134e58e
commit 1fa9c14666
Signed by untrusted user: BasiqueEvangelist
GPG Key ID: B370219149301706
11 changed files with 238 additions and 46 deletions

View File

@ -32,7 +32,7 @@ namespace JetHerald.Commands
try try
{ {
var topic = await db.CreateTopic(msg.From.Id, name, descr); var topic = await db.CreateTopic(msg.From.Id, "Telegram", name, descr);
return $"created {topic.Name}\n" + return $"created {topic.Name}\n" +
$"readToken\n{topic.ReadToken}\n" + $"readToken\n{topic.ReadToken}\n" +
$"writeToken\n{topic.WriteToken}\n" + $"writeToken\n{topic.WriteToken}\n" +

View File

@ -0,0 +1,116 @@
using System.Linq;
using System.Threading.Tasks;
using DSharpPlus.CommandsNext;
using DSharpPlus.CommandsNext.Attributes;
using MySql.Data.MySqlClient;
namespace JetHerald
{
[ModuleLifespan(ModuleLifespan.Transient)]
public class DiscordCommands : BaseCommandModule
{
public Db db { private get; set; }
[Command("createtopic")]
[Description("Creates a topic.")]
[RequireDirectMessage]
public async Task CreateTopic(
CommandContext ctx,
[Description("The unique name of the new topic.")]
string name,
[RemainingText, Description("The name displayed in service messages. Defaults to `name`")]
string description = null)
{
if (description == null)
description = name;
await ctx.TriggerTypingAsync();
try
{
var topic = await db.CreateTopic((long)ctx.User.Id, "Discord", name, description);
await ctx.RespondAsync($"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)
{
await ctx.RespondAsync($"topic {name} already exists");
}
}
[Command("deletetopic")]
[Description("Deletes a topic.")]
[RequireDirectMessage]
public async Task DeleteTopic(
CommandContext ctx,
[Description("The name of the topic to be deleted.")]
string name,
[Description("The admin token of the topic to be deleted.")]
string adminToken)
{
await ctx.TriggerTypingAsync();
var changed = await db.DeleteTopic(name, adminToken);
if (changed > 0)
await ctx.RespondAsync($"deleted {name} and all its subscriptions");
else
await ctx.RespondAsync($"invalid topic name or admin token");
}
[Command("list")]
[Description("List all subscriptions in this channel.")]
public async Task ListSubscriptions(CommandContext ctx)
{
await ctx.TriggerTypingAsync();
var topics = await db.GetTopicsForChat((long)ctx.Channel.Id, "Discord");
await ctx.RespondAsync(topics.Any()
? "Topics:\n" + string.Join("\n", topics.Select(ListCommand.GetTopicListing))
: "No subscriptions active.");
}
[Command("subscribe")]
[Description("Subscribes to a topic.")]
public async Task Subscribe(
CommandContext ctx,
[Description("The read token of the token to subscribe to.")]
string token
)
{
await ctx.TriggerTypingAsync();
var topic = await db.GetTopic(token, (long)ctx.Channel.Id, "Discord");
if (topic == null)
await ctx.RespondAsync("topic not found");
else if (topic.ChatId == (long)ctx.Channel.Id)
await ctx.RespondAsync($"already subscribed to {topic.Name}");
else if (topic.ReadToken != token)
await ctx.RespondAsync("token mismatch");
else
{
await db.CreateSubscription(topic.TopicId, (long)ctx.Channel.Id, "Discord");
await ctx.RespondAsync($"subscribed to {topic.Name}");
}
}
[Command("unsubscribe")]
[Description("Unsubscribes from a topic.")]
public async Task Unsubscribe(
CommandContext ctx,
[Description("The name of the topic to unsubscribe from.")]
string name
)
{
await ctx.TriggerTypingAsync();
int affected = await db.RemoveSubscription(name, (long)ctx.Channel.Id, "Discord");
if (affected >= 1)
await ctx.RespondAsync($"unsubscribed from {name}");
else
await ctx.RespondAsync($"could not find subscription for {name}");
}
}
}

View File

@ -17,14 +17,14 @@ namespace JetHerald
{ {
var msg = messageEventArgs.Message; var msg = messageEventArgs.Message;
var chatid = msg.Chat.Id; var chatid = msg.Chat.Id;
var topics = await db.GetTopicsForChat(chatid); var topics = await db.GetTopicsForChat(chatid, "Telegram");
return topics.Any() return topics.Any()
? "Topics:\n" + string.Join("\n", topics.Select(GetTopicListing)) ? "Topics:\n" + string.Join("\n", topics.Select(GetTopicListing))
: "No subscriptions active."; : "No subscriptions active.";
} }
static string GetTopicListing(Db.Topic t) internal static string GetTopicListing(Db.Topic t)
=> t.Name == t.Description ? t.Name : $"{t.Name}: {t.Description}"; => t.Name == t.Description ? t.Name : $"{t.Name}: {t.Description}";
} }

View File

@ -20,7 +20,7 @@ namespace JetHerald
var chatid = args.Message.Chat.Id; var chatid = args.Message.Chat.Id;
var token = cmd.Parameters[0]; var token = cmd.Parameters[0];
var topic = await db.GetTopic(token, chatid); var topic = await db.GetTopic(token, chatid, "Telegram");
if (topic == null) if (topic == null)
return "topic not found"; return "topic not found";
@ -30,7 +30,7 @@ namespace JetHerald
return "token mismatch"; return "token mismatch";
else else
{ {
await db.CreateSubscription(topic.TopicId, chatid); await db.CreateSubscription(topic.TopicId, chatid, "Telegram");
return $"subscribed to {topic.Name}"; return $"subscribed to {topic.Name}";
} }
} }

View File

@ -20,7 +20,7 @@ namespace JetHerald
var msg = messageEventArgs.Message; var msg = messageEventArgs.Message;
var chatid = msg.Chat.Id; var chatid = msg.Chat.Id;
var topicName = cmd.Parameters[0]; var topicName = cmd.Parameters[0];
int affected = await db.RemoveSubscription(topicName, chatid); int affected = await db.RemoveSubscription(topicName, chatid, "Telegram");
if (affected >= 1) if (affected >= 1)
return $"unsubscribed from {topicName}"; return $"unsubscribed from {topicName}";
else else

View File

@ -14,4 +14,9 @@
public string ProxyPassword { get; set; } public string ProxyPassword { get; set; }
public string ProxyLogin { get; set; } public string ProxyLogin { get; set; }
} }
public class Discord
{
public string Token { get; set; }
}
} }

View File

@ -14,6 +14,7 @@ namespace JetHerald
{ {
public uint TopicId { get; set; } public uint TopicId { get; set; }
public long CreatorId { get; set; } public long CreatorId { get; set; }
public string CreatorService { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string Description { get; set; } public string Description { get; set; }
public string ReadToken { get; set; } public string ReadToken { get; set; }
@ -23,15 +24,23 @@ namespace JetHerald
public bool ExpiryMessageSent { get; set; } public bool ExpiryMessageSent { get; set; }
public long? ChatId { get; set; } public long? ChatId { get; set; }
public string Service { get; set; }
} }
public class ExpiredTopicChat public class ExpiredTopicChat
{ {
public long ChatId; public long ChatId;
public string Service;
public string Description; public string Description;
public DateTime ExpiryTime { get; set; } public DateTime ExpiryTime { get; set; }
} }
public class ChatData
{
public long ChatId;
public string Service;
}
public async Task<int> DeleteTopic(string name, string adminToken) public async Task<int> DeleteTopic(string name, string adminToken)
{ {
using (var c = GetConnection()) using (var c = GetConnection())
@ -54,18 +63,18 @@ namespace JetHerald
new { name }); new { name });
} }
public async Task<Topic> GetTopic(string token, long chatId) public async Task<Topic> GetTopic(string token, long chatId, string service)
{ {
using (var c = GetConnection()) using (var c = GetConnection())
return await c.QuerySingleOrDefaultAsync<Topic>( return await c.QuerySingleOrDefaultAsync<Topic>(
" SELECT t.*, tc.ChatId " + " SELECT t.*, tc.ChatId " +
" FROM topic t " + " FROM topic t " +
" LEFT JOIN topic_chat tc ON t.TopicId = tc.TopicId AND tc.ChatId = @chatId " + " LEFT JOIN topic_chat tc ON t.TopicId = tc.TopicId AND tc.ChatId = @chatId AND tc.Service = @service " +
" WHERE ReadToken = @token", " WHERE ReadToken = @token",
new { token, chatId }); new { token, chatId, service });
} }
public async Task<Topic> CreateTopic(long userId, string name, string descr) public async Task<Topic> CreateTopic(long userId, string service, string name, string descr)
{ {
var t = new Topic var t = new Topic
{ {
@ -74,60 +83,61 @@ namespace JetHerald
Description = descr, Description = descr,
ReadToken = TokenHelper.GetToken(), ReadToken = TokenHelper.GetToken(),
WriteToken = TokenHelper.GetToken(), WriteToken = TokenHelper.GetToken(),
AdminToken = TokenHelper.GetToken() AdminToken = TokenHelper.GetToken(),
Service = service
}; };
using (var c = GetConnection()) using (var c = GetConnection())
{ {
return await c.QuerySingleOrDefaultAsync<Topic>( return await c.QuerySingleOrDefaultAsync<Topic>(
" INSERT INTO herald.topic " + " INSERT INTO herald.topic " +
" ( CreatorId, Name, Description, ReadToken, WriteToken, AdminToken) " + " ( CreatorId, Name, Description, ReadToken, WriteToken, AdminToken, Service) " +
" VALUES " + " VALUES " +
" (@CreatorId, @Name, @Description, @ReadToken, @WriteToken, @AdminToken); " + " (@CreatorId, @Name, @Description, @ReadToken, @WriteToken, @AdminToken, @Service); " +
" SELECT * FROM topic WHERE TopicId = LAST_INSERT_ID(); ", " SELECT * FROM topic WHERE TopicId = LAST_INSERT_ID(); ",
t); t);
} }
} }
public async Task<IEnumerable<long>> GetChatIdsForTopic(uint topicid) public async Task<IEnumerable<ChatData>> GetChatIdsForTopic(uint topicid)
{ {
using (var c = GetConnection()) using (var c = GetConnection())
return await c.QueryAsync<long>( return await c.QueryAsync<ChatData>(
" SELECT ChatId" + " SELECT ChatId, Service" +
" FROM topic_chat" + " FROM topic_chat" +
" WHERE TopicId = @topicid", " WHERE TopicId = @topicid",
new { topicid }); new { topicid });
} }
public async Task<IEnumerable<Topic>> GetTopicsForChat(long chatid) public async Task<IEnumerable<Topic>> GetTopicsForChat(long chatid, string service)
{ {
using (var c = GetConnection()) using (var c = GetConnection())
return await c.QueryAsync<Topic>( return await c.QueryAsync<Topic>(
" SELECT t.*" + " SELECT t.*" +
" FROM topic_chat ct" + " FROM topic_chat ct" +
" JOIN topic t on t.TopicId = ct.TopicId" + " JOIN topic t on t.TopicId = ct.TopicId" +
" WHERE ct.ChatId = @chatid", " WHERE ct.ChatId = @chatid AND ct.Service = @service",
new { chatid }); new { chatid, service });
} }
public async Task CreateSubscription(uint topicId, long chatId) public async Task CreateSubscription(uint topicId, long chatId, string service)
{ {
using (var c = GetConnection()) using (var c = GetConnection())
await c.ExecuteAsync( await c.ExecuteAsync(
" INSERT INTO topic_chat" + " INSERT INTO topic_chat" +
" (ChatId, TopicId )" + " (ChatId, TopicId, Service)" +
" VALUES" + " VALUES" +
" (@chatId, @topicId)", " (@chatId, @topicId, @service)",
new { topicId, chatId }); new { topicId, chatId, service });
} }
public async Task<int> RemoveSubscription(string topicName, long chatId) public async Task<int> RemoveSubscription(string topicName, long chatId, string service)
{ {
using (var c = GetConnection()) using (var c = GetConnection())
return await c.ExecuteAsync( return await c.ExecuteAsync(
" DELETE tc " + " DELETE tc " +
" FROM topic_chat tc" + " FROM topic_chat tc" +
" JOIN topic t ON tc.TopicId = t.TopicId " + " JOIN topic t ON tc.TopicId = t.TopicId " +
" WHERE t.Name = @topicName AND tc.ChatId = @chatId;", " WHERE t.Name = @topicName AND tc.ChatId = @chatId AND tc.Service = @service;",
new { topicName, chatId }); new { topicName, chatId, service });
} }
public Task AddExpiry(string topicName, int addedTime) public Task AddExpiry(string topicName, int addedTime)
@ -156,7 +166,7 @@ namespace JetHerald
{ {
using var c = GetConnection(); using var c = GetConnection();
return c.QueryAsync<ExpiredTopicChat>( return c.QueryAsync<ExpiredTopicChat>(
" SELECT tc.ChatId, t.Description, t.ExpiryTime" + " SELECT tc.ChatId, tc.Service, t.Description, t.ExpiryTime" +
" FROM topic_chat tc" + " FROM topic_chat tc" +
" INNER JOIN topic t ON t.TopicId = tc.TopicId" + " INNER JOIN topic t ON t.TopicId = tc.TopicId" +
" WHERE t.ExpiryTime < CURRENT_TIMESTAMP() AND NOT t.ExpiryMessageSent", " WHERE t.ExpiryTime < CURRENT_TIMESTAMP() AND NOT t.ExpiryMessageSent",

View File

@ -10,6 +10,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Dapper" Version="1.60.6" /> <PackageReference Include="Dapper" Version="1.60.6" />
<PackageReference Include="DSharpPlus" Version="4.0.0" />
<PackageReference Include="DSharpPlus.CommandsNext" Version="4.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.1.1" /> <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.1.1" />
<PackageReference Include="MySql.Data" Version="8.0.17" /> <PackageReference Include="MySql.Data" Version="8.0.17" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.8.4" /> <PackageReference Include="NLog.Web.AspNetCore" Version="4.8.4" />

View File

@ -0,0 +1,38 @@
using System.Threading.Tasks;
using DSharpPlus;
using DSharpPlus.CommandsNext;
namespace JetHerald
{
public partial class JetHeraldBot
{
DiscordClient DiscordBot { get; set; }
async Task InitDiscord()
{
DiscordBot = new DiscordClient(new()
{
Token = DiscordConfig.Token,
TokenType = TokenType.Bot,
Intents = DiscordIntents.AllUnprivileged,
LoggerFactory = LoggerFactory
});
var commands = DiscordBot.UseCommandsNext(new CommandsNextConfiguration()
{
StringPrefixes = new[] { "!" },
Services = ServiceProvider
});
commands.RegisterCommands<DiscordCommands>();
await DiscordBot.ConnectAsync();
}
private async Task SendMessageToDiscordChannel(long chatId, string formatted)
{
await DiscordBot.SendMessageAsync(await DiscordBot.GetChannelAsync((ulong)chatId), formatted);
}
}
}

View File

@ -12,20 +12,27 @@ using System.Threading;
namespace JetHerald namespace JetHerald
{ {
public class JetHeraldBot public partial class JetHeraldBot
{ {
Db Db { get; set; } Db Db { get; set; }
Options.Telegram Config { get; } Options.Telegram TelegramConfig { get; }
Options.Discord DiscordConfig { get; }
ILogger<JetHeraldBot> Log { get; } ILogger<JetHeraldBot> Log { get; }
ILoggerFactory LoggerFactory { get; }
IServiceProvider ServiceProvider { get; }
public JetHeraldBot(Db db, IOptions<Options.Telegram> cfg, ILogger<JetHeraldBot> log) public JetHeraldBot(Db db, IOptions<Options.Telegram> telegramCfg, IOptions<Options.Discord> discordCfg, ILogger<JetHeraldBot> log, ILoggerFactory loggerFactory, IServiceProvider serviceProvider)
{ {
Db = db; Db = db;
Config = cfg.Value; TelegramConfig = telegramCfg.Value;
DiscordConfig = discordCfg.Value;
Log = log; Log = log;
LoggerFactory = loggerFactory;
ServiceProvider = serviceProvider;
} }
TelegramBotClient Client { get; set; } TelegramBotClient TelegramBot { get; set; }
ChatCommandRouter Commands; ChatCommandRouter Commands;
CancellationTokenSource HeartbeatCancellation; CancellationTokenSource HeartbeatCancellation;
Task HeartbeatTask; Task HeartbeatTask;
@ -33,17 +40,17 @@ namespace JetHerald
public async Task Init() public async Task Init()
{ {
if (Config.UseProxy) if (TelegramConfig.UseProxy)
{ {
var httpProxy = new WebProxy(Config.ProxyUrl) var httpProxy = new WebProxy(TelegramConfig.ProxyUrl)
{ Credentials = new NetworkCredential(Config.ProxyLogin, Config.ProxyPassword) }; { Credentials = new NetworkCredential(TelegramConfig.ProxyLogin, TelegramConfig.ProxyPassword) };
Client = new TelegramBotClient(Config.ApiKey, httpProxy); TelegramBot = new TelegramBotClient(TelegramConfig.ApiKey, httpProxy);
} }
else else
{ {
Client = new TelegramBotClient(Config.ApiKey); TelegramBot = new TelegramBotClient(TelegramConfig.ApiKey);
} }
Me = await Client.GetMeAsync(); Me = await TelegramBot.GetMeAsync();
Commands = new ChatCommandRouter(Me.Username, Log); Commands = new ChatCommandRouter(Me.Username, Log);
Commands.Add(new CreateTopicCommand(Db), "createtopic"); Commands.Add(new CreateTopicCommand(Db), "createtopic");
@ -55,13 +62,16 @@ namespace JetHerald
HeartbeatCancellation = new(); HeartbeatCancellation = new();
HeartbeatTask = CheckHeartbeats(HeartbeatCancellation.Token); HeartbeatTask = CheckHeartbeats(HeartbeatCancellation.Token);
Client.OnMessage += BotOnMessageReceived; TelegramBot.OnMessage += BotOnMessageReceived;
Client.StartReceiving(); TelegramBot.StartReceiving();
await InitDiscord();
} }
public async Task Stop() public async Task Stop()
{ {
Client.StopReceiving(); await DiscordBot.DisconnectAsync();
TelegramBot.StopReceiving();
HeartbeatCancellation.Cancel(); HeartbeatCancellation.Cancel();
try try
{ {
@ -82,7 +92,7 @@ namespace JetHerald
foreach (var chatSent in await Db.GetExpiredTopics(token)) foreach (var chatSent in await Db.GetExpiredTopics(token))
{ {
var formatted = $"!{chatSent.Description}!:\nTimeout expired at {chatSent.ExpiryTime}"; var formatted = $"!{chatSent.Description}!:\nTimeout expired at {chatSent.ExpiryTime}";
await Client.SendTextMessageAsync(chatSent.ChatId, formatted, cancellationToken: token); await TelegramBot.SendTextMessageAsync(chatSent.ChatId, formatted, cancellationToken: token);
} }
await Db.MarkExpiredTopics(token); await Db.MarkExpiredTopics(token);
@ -94,7 +104,12 @@ namespace JetHerald
var chatIds = await Db.GetChatIdsForTopic(topic.TopicId); var chatIds = await Db.GetChatIdsForTopic(topic.TopicId);
var formatted = $"!{topic.Description}!:\nA heartbeat has been sent."; var formatted = $"!{topic.Description}!:\nA heartbeat has been sent.";
foreach (var c in chatIds) foreach (var c in chatIds)
await Client.SendTextMessageAsync(c, formatted); {
if (c.Service == "Telegram")
await TelegramBot.SendTextMessageAsync(c.ChatId, formatted);
else if (c.Service == "Discord")
await SendMessageToDiscordChannel(c.ChatId, formatted);
}
} }
public async Task PublishMessage(Db.Topic topic, string message) public async Task PublishMessage(Db.Topic topic, string message)
@ -102,7 +117,12 @@ namespace JetHerald
var chatIds = await Db.GetChatIdsForTopic(topic.TopicId); var chatIds = await Db.GetChatIdsForTopic(topic.TopicId);
var formatted = $"|{topic.Description}|:\n{message}"; var formatted = $"|{topic.Description}|:\n{message}";
foreach (var c in chatIds) foreach (var c in chatIds)
await Client.SendTextMessageAsync(c, formatted); {
if (c.Service == "Telegram")
await TelegramBot.SendTextMessageAsync(c.ChatId, formatted);
else if (c.Service == "Discord")
await SendMessageToDiscordChannel(c.ChatId, formatted);
}
} }
async void BotOnMessageReceived(object sender, MessageEventArgs messageEventArgs) async void BotOnMessageReceived(object sender, MessageEventArgs messageEventArgs)
@ -115,7 +135,7 @@ namespace JetHerald
{ {
var reply = await Commands.Execute(sender, messageEventArgs); var reply = await Commands.Execute(sender, messageEventArgs);
if (reply != null) if (reply != null)
await Client.SendTextMessageAsync( await TelegramBot.SendTextMessageAsync(
chatId: msg.Chat.Id, chatId: msg.Chat.Id,
text: reply, text: reply,
replyToMessageId: msg.MessageId); replyToMessageId: msg.MessageId);

View File

@ -21,6 +21,7 @@ namespace JetHerald
{ {
services.Configure<Options.ConnectionStrings>(Configuration.GetSection("ConnectionStrings")); services.Configure<Options.ConnectionStrings>(Configuration.GetSection("ConnectionStrings"));
services.Configure<Options.Telegram>(Configuration.GetSection("Telegram")); services.Configure<Options.Telegram>(Configuration.GetSection("Telegram"));
services.Configure<Options.Discord>(Configuration.GetSection("Discord"));
services.AddSingleton<Db>(); services.AddSingleton<Db>();
services.AddSingleton<JetHeraldBot>(); services.AddSingleton<JetHeraldBot>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
@ -31,7 +32,7 @@ namespace JetHerald
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{ {
var bot = app.ApplicationServices.GetService<JetHeraldBot>(); var bot = app.ApplicationServices.GetService<JetHeraldBot>();
bot.Init().Wait(); bot.Init().GetAwaiter().GetResult();
app.UsePathBase(Configuration.GetValue<string>("PathBase")); app.UsePathBase(Configuration.GetValue<string>("PathBase"));
if (env.IsDevelopment()) if (env.IsDevelopment())
{ {