Discord support part 1

This commit is contained in:
Basique Evangelist 2021-04-18 23:25:40 +03:00 committed by BasiqueEvangelist
parent 6854b8aebc
commit 04a04f418c
11 changed files with 238 additions and 46 deletions

View File

@ -32,7 +32,7 @@ namespace JetHerald.Commands
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" +
$"readToken\n{topic.ReadToken}\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 chatid = msg.Chat.Id;
var topics = await db.GetTopicsForChat(chatid);
var topics = await db.GetTopicsForChat(chatid, "Telegram");
return topics.Any()
? "Topics:\n" + string.Join("\n", topics.Select(GetTopicListing))
: "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}";
}

View File

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

View File

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

View File

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

View File

@ -10,6 +10,8 @@
<ItemGroup>
<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="MySql.Data" Version="8.0.17" />
<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
{
public class JetHeraldBot
public partial class JetHeraldBot
{
Db Db { get; set; }
Options.Telegram Config { get; }
Options.Telegram TelegramConfig { get; }
Options.Discord DiscordConfig { 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;
Config = cfg.Value;
TelegramConfig = telegramCfg.Value;
DiscordConfig = discordCfg.Value;
Log = log;
LoggerFactory = loggerFactory;
ServiceProvider = serviceProvider;
}
TelegramBotClient Client { get; set; }
TelegramBotClient TelegramBot { get; set; }
ChatCommandRouter Commands;
CancellationTokenSource HeartbeatCancellation;
Task HeartbeatTask;
@ -33,17 +40,17 @@ namespace JetHerald
public async Task Init()
{
if (Config.UseProxy)
if (TelegramConfig.UseProxy)
{
var httpProxy = new WebProxy(Config.ProxyUrl)
{ Credentials = new NetworkCredential(Config.ProxyLogin, Config.ProxyPassword) };
Client = new TelegramBotClient(Config.ApiKey, httpProxy);
var httpProxy = new WebProxy(TelegramConfig.ProxyUrl)
{ Credentials = new NetworkCredential(TelegramConfig.ProxyLogin, TelegramConfig.ProxyPassword) };
TelegramBot = new TelegramBotClient(TelegramConfig.ApiKey, httpProxy);
}
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.Add(new CreateTopicCommand(Db), "createtopic");
@ -55,13 +62,16 @@ namespace JetHerald
HeartbeatCancellation = new();
HeartbeatTask = CheckHeartbeats(HeartbeatCancellation.Token);
Client.OnMessage += BotOnMessageReceived;
Client.StartReceiving();
TelegramBot.OnMessage += BotOnMessageReceived;
TelegramBot.StartReceiving();
await InitDiscord();
}
public async Task Stop()
{
Client.StopReceiving();
await DiscordBot.DisconnectAsync();
TelegramBot.StopReceiving();
HeartbeatCancellation.Cancel();
try
{
@ -82,7 +92,7 @@ namespace JetHerald
foreach (var chatSent in await Db.GetExpiredTopics(token))
{
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);
@ -94,7 +104,12 @@ namespace JetHerald
var chatIds = await Db.GetChatIdsForTopic(topic.TopicId);
var formatted = $"!{topic.Description}!:\nA heartbeat has been sent.";
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)
@ -102,7 +117,12 @@ namespace JetHerald
var chatIds = await Db.GetChatIdsForTopic(topic.TopicId);
var formatted = $"|{topic.Description}|:\n{message}";
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)
@ -115,7 +135,7 @@ namespace JetHerald
{
var reply = await Commands.Execute(sender, messageEventArgs);
if (reply != null)
await Client.SendTextMessageAsync(
await TelegramBot.SendTextMessageAsync(
chatId: msg.Chat.Id,
text: reply,
replyToMessageId: msg.MessageId);

View File

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