I am trying to create a discord bot using DSharpPlus library where if you react on a message with specific emoji, you will get a specific role. The concept is pretty straight forward but I fail to figure out one rather important concept. That is, how do I get the bot to listen for a reaction on an existing message all the time.
I tried to do it via commands and I got it to work, however the problem with this approach as I learned is that the bot only listens for reactions after I type a command and it only lasts a minute or so (based on configuration).
public class RoleCommands : BaseCommandModule
{
[Command("join")]
public async Task Join(CommandContext ctx)
{
var joinEmbed = new DiscordEmbedBuilder
{
Title = "Reaction with thumbs up!",
Color = DiscordColor.Green
};
var joinMessage = await ctx.Channel.SendMessageAsync(embed: joinEmbed).ConfigureAwait(false);
var thumbsUpEmoji = DiscordEmoji.FromName(ctx.Client, ":+1:");
var thumbsDownEmoji = DiscordEmoji.FromName(ctx.Client, ":-1:");
await joinMessage.CreateReactionAsync(thumbsUpEmoji).ConfigureAwait(false);
await joinMessage.CreateReactionAsync(thumbsDownEmoji).ConfigureAwait(false);
var interactivity = ctx.Client.GetInteractivity();
var reactionResult = await interactivity.WaitForReactionAsync(x =>
x.Message == joinMessage
&& x.User == ctx.User
&& x.Emoji == thumbsUpEmoji);
if (reactionResult.Result.Emoji == thumbsUpEmoji)
{
var role = ctx.Guild.GetRole(773965440913375282);
await ctx.Member.GrantRoleAsync(role).ConfigureAwait(false);
await joinMessage.DeleteAsync().ConfigureAwait(false);
}
}
}
How can I do this outside of a command where I can pass it a message Id and then it listens to that message for reactions all the time as oppose to a limited time?
The full answer to my question is to use DiscordClient.MessageReactionAdded += OnReactionAdded; and to implement the method as such:
private async Task OnReactionAdded(DiscordClient sender, MessageReactionRemoveEventArgs e)
{
var messageId = e.Message.Id;
var guild = e.Message.Channel.Guild;
var reactionName = e.Emoji.GetDiscordName();
var reactionDetail = ReactionDetails.FirstOrDefault(x =>
x.MessageId == messageId
&& x.GuildId == guild.Id
&& x.ReactionName == reactionName);
if (reactionDetail != null)
{
var member = e.User as DiscordMember;
if (member != null)
{
var role = guild.Roles.FirstOrDefault(x => x.Value.Id == reactionDetail.RoleId).Value;
await member.GrantRoleAsync(role).ConfigureAwait(false);
}
}
}
Store the message id somewhere then hook the MessageReactionAdded event on your DiscordClient and do your logic there.
Related
In my case I want to send the message to all sessions not only user (user can have many sessions), I used foreach to send message to all connections belong to friends with its others sessions,
there is a problem in await asynchronous and a message:
Failed writing message. Aborting connection. System.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 64. Consider using ReferenceHandler.Preserve on JsonSerializerOption
// Test if the user is offline :
// Find My opened sessions and target opened sessions
if (userInfoReciever != null)
{
var targetSessions = _context.Connections.Where(C => (C.Username == targetUserModel.UserName) && (C.Connected == true)).ToList();
if (targetSessions != null)
{
foreach (var targetSeesion in targetSessions)
{
await Clients.Client(targetSeesion.ConnectionID).SendAsync("SendDM", messageModel, userInfoSender);
await Clients.Client(targetSeesion.ConnectionID).SendAsync("SendDN", notificationModel, userInfoSender);
}
}
}
await Clients.Caller.SendAsync("MyMessage", messageModel, userInfoSender);
if (userInfoSender != null)
{
var mySessions = _context.Connections.Where(C => ((C.ConnectionID != Context.ConnectionId) && (C.Username == Context.User.Identity.Name) && (C.Connected == true))).ToList();
if (mySessions != null)
{
foreach(var mySession in mySessions)
{
await Clients.Client(mySession.ConnectionID).SendAsync("MyMessage", messageModel, userInfoSender);
}
}
}
// Send my message to the the others sessions :
// Build the message
var _message = _mapper.Map<MessageViewModel,Message>(messageModel);
var _notification = _mapper.Map<NotificationViewModel, Notification>(notificationModel);
await _context.Messages.AddAsync(_message);
await _context.Notifications.AddAsync(_notification);
await _context.SaveChangesAsync();
How to run an async Task without blocking other tasks?
I have one function that iterates though a List but the problem is that when the function is called other functions won't work again until the first function is done. What are the ways of making the HandleAsync function non-blocking ?
public static async Task HandleAsync(Message message, TelegramBotClient bot)
{
await Search(message, bot); // This should be handled without working other possible functions. I have a function similar to this but which doesn't iterate though any list.
}
private static async Task Search(Message message, TelegramBotClient bot)
{
var textSplit = message.Text.Split(new[] {' '}, 2);
if (textSplit.Length == 1)
{
await bot.SendTextMessageAsync(message.From.Id, "Failed to fetch sales. Missing game name. ",
ParseMode.Html);
}
else
{
var search = await Program.itad.SearchGameAsync(textSplit[1], limit: 10, cts: Program.Cts);
if (search.Data != null)
{
var builder = new StringBuilder();
foreach (var deal in search.Data.List)
{
var title = deal.Title;
var plain = deal.Plain;
var shop = deal.Shop != null ? deal.Shop.Name : "N/A";
var urls = deal.Urls;
var priceNew = deal.PriceNew;
var priceOld = deal.PriceOld;
var priceCut = deal.PriceCut;
builder.AppendLine($"<b>Title:</b> {title}");
builder.AppendLine($"<b>Shop:</b> {shop}");
builder.AppendLine();
builder.AppendLine($"<b>Price:</b> <strike>{priceOld}€</strike> | {priceNew}€ (-{priceCut}%)");
var buttons = new[]
{
new[]
{
InlineKeyboardButton.WithUrl("Buy", urls.Buy.AbsoluteUri),
InlineKeyboardButton.WithUrl("History",
urls.Game.AbsoluteUri.Replace("info", "history"))
}
};
var keyboard = new InlineKeyboardMarkup(buttons);
var info = await Program.itad.GetInfoAsync(plain, cts: Program.Cts);
var image = info.Data.GameInfo.Image;
if (image == null) image = new Uri("https://i.imgur.com/J7zLBLg.png");
await TelegramBot.Bot.SendPhotoAsync(message.From.Id, new InputOnlineFile(image.AbsoluteUri),
builder.ToString(), ParseMode.Html, replyMarkup: keyboard,
cancellationToken: Program.Cts.Token);
builder.Clear();
}
}
else
{
await bot.SendTextMessageAsync(message.From.Id, "Failed to fetch sales. Game not found. ",
ParseMode.Html);
}
}
Hi I created my first test bot using Microsoft BotFramework in C#.
in private async Task< Activity > HandleSystemMessage(Activity message) in if (message.Type == ActivityTypes.ConversationUpdate) normally it should notify a new member added to group or someone hit the start button of bot in Telegram Messenger. When I test it in debug mode using BotFramework emulator everything works perfectly but after I publish it I see that after hitting start button in Telegram messenger my code didn't run.
My code in ActivationType.ConversationUpdate
foreach (var item in message.MembersAdded)
{
try
{
using (var dbcontext = new WatermarkBotDBEntities())
{
dbcontext.BotUsers.Add(new BotUser()
{
AddedFriends = 0,
ConversationID = message.Conversation.Id,
ServiceUrl = message.ServiceUrl,
UserID = message.From.Id
});
dbcontext.SaveChanges();
if (Request.RequestUri.Query != "")
{
var u = dbcontext.BotUsers.Where(x => x.BotSalCode == Request.RequestUri.Query.Replace("?start=", string.Empty)).FirstOrDefault();
u.AddedFriends++;
dbcontext.Entry(u).State = System.Data.Entity.EntityState.Modified;
if (u != null)
{
var connector = new ConnectorClient(new Uri(u.ServiceUrl));
IMessageActivity newMessage = Activity.CreateMessageActivity();
newMessage.Type = ActivityTypes.Message;
//newMessage.From = new ChannelAccount("<BotId>", "<BotName>");
newMessage.From = new ChannelAccount("c3e7mhdafcecn7ng3", "Bot");
newMessage.Conversation = new ConversationAccount(false, u.ConversationID);
newMessage.Recipient = new ChannelAccount(u.UserID);
if (u.AddedFriends <= 2)
newMessage.Text = $"SomeText.";
else newMessage.Text = "SomeTex";
await connector.Conversations.SendToConversationAsync((Activity)newMessage);
dbcontext.SaveChanges();
}
}
}
}
catch (Exception ex)
{
}
So how is it possible to detect hitting start in telegram ?
Regards
I realize this is not a complete answer, but I wanted to share this code with you in case it may help. Below is the recommended way to send a welcome message, you may be able to repurpose this code for your use.
else if (message.Type == ActivityTypes.ConversationUpdate || message.Type == ActivityTypes.Message)
{
IConversationUpdateActivity iConversationUpdated = message as IConversationUpdateActivity;
if (iConversationUpdated != null)
{
ConnectorClient connector = new ConnectorClient(new System.Uri(message.ServiceUrl));
foreach (var member in iConversationUpdated.MembersAdded ?? System.Array.Empty<ChannelAccount>())
{
// if the bot is added, then
if (member.Id == iConversationUpdated.Recipient.Id)
{
var reply = ((Activity)iConversationUpdated).CreateReply(
$"Hi! I'm Botty McBot.");
await connector.Conversations.ReplyToActivityAsync(reply);
}
}
}
}
This is the answer I found for my question after lots of testing :
In MessagesController class in public async Task<HttpResponseMessage> Post([FromBody]Activity activity) function that defined by default in a BotFramework Application you have to do something like this :
if (activity.Type == ActivityTypes.Message)
{
if (activity.Text.StartsWith("/start"))
{
//This will return you the start parameter of a link like : http://telegram.me/botname?start=Parameter
var Parameter = activity.Text.Replace("/start ", "");
}
}
and if you want to send a welcome message so you can surely use the way that #JasonSowers told and use his code to send your message .
Best Regards
I am trying to set up automated messages.
When I am setting up my client I use:
client.Ready += OnClientReady;
From there I start my Scheduler class:
private Task OnClientReady()
{
var scheduler = new Scheduler(client);
scheduler.Start();
return Task.CompletedTask;
}
Which looks like this:
public class Scheduler
{
private readonly DiscordSocketClient _client;
private static Timer _timer;
public void Start(object state = null)
{
Sender.Send(_client);
_timer = new Timer(Start, null, (int)Duration.FromMinutes(1).TotalMilliseconds, 0);
}
public Scheduler(DiscordSocketClient client)
{
_client = client;
}
}
When the timer ticks, it calls out and passes the client to the Sender class below:
public static class Sender
{
public static void Send(DiscordSocketClient client)
{
var currentLocalDateTime = SystemClock.Instance.InTzdbSystemDefaultZone().GetCurrentLocalDateTime();
var elapsedRotations = new List<Rotations>();
using (var db = new GOPContext())
{
elapsedRotations = db.Rotations
.Include(r => r.RotationUsers)
.Where(r => r.LastNotification == null ||
Period.Between(r.LastNotification.Value.ToLocalDateTime(),
currentLocalDateTime).Hours >= 23)
.ToList();
}
foreach (var rotation in elapsedRotations)
{
var zone = DateTimeZoneProviders.Tzdb.GetZoneOrNull(rotation.Timezone);
var zonedDateTime = SystemClock.Instance.InZone(zone).GetCurrentZonedDateTime();
if (zonedDateTime.Hour != 17)
continue;
//I need to send a message to the channel here.
//I have access to the connected / ready client,
//and the channel Id which is "rotation.ChannelId"
}
}
}
I have tried getting the channel like this:
var channel = client.GetChannel((ulong) rotation.ChannelId);
which gives me a SocketChannel and also like this:
var channel = client.Guilds
.SelectMany(g => g.Channels)
.SingleOrDefault(c => c.Id == rotation.ChannelId);
which gives me a SocketGuildChannel. Neither of these give me an option to send a message directly to the channel. I have tried researching how to do this, but have not found anything... The documentation does not seem to have any examples of this...
It seems like a simple thing to do, but I'm at my wits end on it. Anybody know how to do this?
This is because both SocketGuildChannel and SocketChannel could be either a voice or a text channel.
Instead you want the ISocketMessageChannel, IMessageChannel or SocketTextChannel
To get this you could simply cast the SocketChannel you are getting
var channel = client.GetChannel((ulong) rotation.ChannelId);
var textChannel = channel as IMessageChannel;
if(textChannel == null)
// this was not a text channel, but a voice channel
else
textChannel.SendMessageAsync("This is a text channel");
I am new to WebSockets (this AM) and have set up a WCF WebSocket app that works when doing a trivial example I found online (http://www.codeproject.com/Articles/619343/Using-WebSocket-in-NET-Part).
I added Entity Framework and as soon as I add code to try to access data the process (just sending a message back and forth) no longer works.
Could there be some fundamental concept I could be missing?
Does anyone have any good ideas for troubleshooting?
namespace PBWebSocket
{
public class PBWebSocket : IBWebSocket
{
private SPEntities db = new SPEntities();
public async Task SendMessageToServer(Message msg)
{
var callback = OperationContext.Current.GetCallbackChannel<IPBCallback>();
if (msg.IsEmpty || ((IChannel)callback).State != CommunicationState.Opened)
{
return;
}
byte[] body = msg.GetBody<byte[]>();
string msgTextFromClient = Encoding.UTF8.GetString(body);
var reqId = Int32.Parse(msgTextFromClient);
// *** The below line breaks it ***
var req = db.Requests.Where(r => r.Id == 164).FirstOrDefault();
reqId = reqId + 2;
Message newMsg = ByteStreamMessage.CreateMessage(
new ArraySegment<byte>(Encoding.UTF8.GetBytes(reqId.ToString())));
newMsg.Properties["WebSocketMessageProperty"] =
new WebSocketMessageProperty
{ MessageType = WebSocketMessageType.Text };
await callback.SendMessageToClient(newMsg);
}
}
}