public async Task <IDictionary <string, object> > Handle(IDictionary <string, object> status, ControllerContext context, CancellationToken cancellationToken = default) { status["now"] = myClock.GetCurrentZonedDateTime().ToDateTimeOffset(); status["defaultTimeZone"] = $"{myClock.Zone.Id}, {myClock.Zone.GetZoneInterval(myClock.GetCurrentInstant())}"; status["assetsRoot"] = myUrlHelper.AssetsContent("~"); status["lastAppliedMigration"] = (await myDB.Database.GetAppliedMigrationsAsync(cancellationToken)).LastOrDefault(); status["polls"] = await myDB.Set <Poll>().LongCountAsync(cancellationToken); status["IGNs"] = await myDB.Set <Player>().Where(_ => _.Nickname != null).LongCountAsync(cancellationToken); status["FCs"] = await myDB.Set <Player>().Where(_ => _.FriendCode != null).LongCountAsync(cancellationToken); return(status); }
public async Task Execute(CancellationToken cancellationToken) { var toNotify = await myDB.Set <ReplyNotification>() .Where(n => n.MessageId == null) .AsTracking() .ToListAsync(cancellationToken); foreach (var batch in toNotify.GroupBy(n => new { n.FromChatId, n.FromMessageId })) { var counter = 0; using var notificationOperation = myTelemetryClient.StartOperation( new DependencyTelemetry(GetType().Name, null, "ChannelNotification", batch.Key.ToString())); foreach (var notification in batch) { try { if (notification.BotId is { } botId&& myBots.TryGetValue(botId, out var bot)) { var message = await bot.ForwardMessageAsync(notification.ChatId, notification.FromChatId, notification.FromMessageId, cancellationToken : cancellationToken); notification.MessageId = message.MessageId; } else { // unknown bot, do not try more notification.MessageId = -1; } }
public async Task <bool?> Handle(MessageEntityEx entity, PollMessage context = default, CancellationToken cancellationToken = default) { if (!this.ShouldProcess(entity, context)) { return(null); } var from = myClock.GetCurrentInstant().ToDateTimeOffset() - myJitterOffset; var polls = await myDB.Set <Poll>() .Where(poll => poll.Time >= from) .Where(poll => poll.Votes.Any(vote => vote.UserId == entity.Message.From.Id)) .IncludeRelatedData() .ToListAsync(cancellationToken); var builder = new TextBuilder(); if (polls.Count == 0) { builder.Append($"No upcoming raids."); } else { foreach (var poll in polls.OrderBy(poll => poll.Time)) { builder.Bold(b => b.Code(bb => bb.AppendFormat("{0:t} ", poll.Time))); poll.GetTitle(builder).NewLine(); } } var content = builder.ToTextMessageContent(disableWebPreview: true); await myBot.SendTextMessageAsync(entity.Message.Chat, content, true, cancellationToken : cancellationToken); return(false); }
public async Task <Portal> Get(string guid, Location location = default, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(guid)) { return(null); } var portalSet = myContext.Set <Portal>(); // check local & remote var portal = await portalSet.FindAsync(new object[] { guid }, cancellationToken); var now = myClock.GetCurrentInstant().ToDateTimeOffset(); if ((now - portal?.Modified)?.TotalDays < 1) // refresh every day { return(portal); } var portals = await Search(guid, location ?? myDefaultLocation, near : false, cancellationToken); var remotePortal = portals.FirstOrDefault(p => p.Guid == guid); if (remotePortal != null && portal != null) { myContext.Entry(portal).SetNotNullProperties(remotePortal); } return(remotePortal); }
public async Task <IDictionary <string, object> > Handle(IDictionary <string, object> status, ControllerContext context, CancellationToken cancellationToken = default) { status["assetsRoot"] = myUrlHelper.AssetsContent("~"); status["lastAppliedMigration"] = (await myDB.Database.GetAppliedMigrationsAsync(cancellationToken)).LastOrDefault(); status["polls"] = await myDB.Set <Poll>().LongCountAsync(cancellationToken); return(status); }
public async Task ApproveFriendship(User host, User user, CancellationToken cancellationToken = default) { var friendshipDB = myDB.Set <Friendship>(); Expression <Func <Friendship, bool> > findFriendPredicate = friendship => friendship.Id == host.Id && friendship.FriendId == user.Id || friendship.Id == user.Id && friendship.FriendId == host.Id; var friendship = friendshipDB.Local.SingleOrDefault(findFriendPredicate.Compile()) ?? await friendshipDB.FirstOrDefaultAsync(findFriendPredicate, cancellationToken); if (friendship == null) { friendship = new Friendship { Id = host.Id, FriendId = user.Id }; friendshipDB.Add(friendship); } friendship.Type = FriendshipType.Approved; await myDB.SaveChangesAsync(cancellationToken); }
public async Task <IActionResult> OnGetAsync(int raidId, CancellationToken cancellationToken = default) { Raid = await myDbContext .Set <Raid>() .FindAsync(new object[] { raidId }, cancellationToken); if (Raid == null) { return(NotFound($"Raid {raidId} not found.")); } return(Page()); }
public async Task <IActionResult> OnGetDataAsync(string bbox, CancellationToken cancellationToken) { var parts = bbox.Split(',', 4); var latLow = decimal.Parse(parts[0], NumberStyles.Currency, CultureInfo.InvariantCulture); var lonLow = decimal.Parse(parts[1], NumberStyles.Currency, CultureInfo.InvariantCulture); var latHigh = decimal.Parse(parts[2], NumberStyles.Currency, CultureInfo.InvariantCulture); var lonHigh = decimal.Parse(parts[3], NumberStyles.Currency, CultureInfo.InvariantCulture); var now = myClock.GetCurrentInstant().ToDateTimeOffset();; var polls = await myDb .Set <Poll>() .IncludeRelatedData() .Where(_ => _.Raid.RaidBossEndTime > now) .Where(_ => _.Raid.EggRaidId == null) // no eggs if boss is already known .Where(_ => _.Raid.Lat >= latLow && _.Raid.Lat <= latHigh) .Where(_ => _.Raid.Lon >= lonLow && _.Raid.Lon <= lonHigh) .DecompileAsync() .ToArrayAsync(cancellationToken); var response = new { type = "FeatureCollection", features = polls.Select(p => new { type = "Feature", id = p.Id, geometry = new { type = "Point", coordinates = new [] { p.Raid.Lat.GetValueOrDefault(), p.Raid.Lon.GetValueOrDefault() } }, properties = new { id = p.Id, raidLevel = $"R{p.Raid.RaidBossLevel}", name = p.Raid.Name, img = p.GetThumbUrl(Url), title = p.GetTitle(), description = $"{p.Raid.Description}", }, options = new { preset = p.Raid.IsEgg ? "islands#icon" : "islands#dotIcon", iconColor = p.Raid.GetEggColor() is Color c ? $"#{c.R:X2}{c.G:X2}{c.B:X2}" : null, } }) }; return(new OkObjectResult(response)); }
public async Task <IActionResult> OnGetAsync(int raidId, CancellationToken cancellationToken = default) { Raid = await myDbContext .Set <Raid>() .Where(_ => _.Id == raidId) .FirstOrDefaultAsync(cancellationToken); if (Raid == null) { return(NotFound($"Raid {raidId} not found.")); } return(Page()); }
public async Task <bool> ProcessPoll(ITelegramBotClient bot, long targetChatId, int?replyMessageId, Func <CancellationToken, Task <Poll> > getPoll, Func <TextBuilder> getInitialText, CancellationToken cancellationToken = default) { var timeZoneSettings = await myDB.Set <TimeZoneSettings>().Where(settings => settings.ChatId == targetChatId).ToListAsync(cancellationToken); if (timeZoneSettings.Count == 0) { return(false); } var poll = await getPoll(cancellationToken); if (poll?.Time is not { } datetime) { return(default); // no poll or without time
public async Task <IActionResult> gyms(CancellationToken cancellationToken = default) { var portals = await myDB.Set <Portal>().ToListAsync(cancellationToken); return(Json(new { gyms = portals .Select(portal => new { guid = portal.Guid.ToString(), lat = portal.Latitude, lng = portal.Longitude, name = portal.Name, image = portal.Image }) .Aggregate(new ExpandoObject(), (obj, _) => { obj.TryAdd(_.guid, _); return obj; }) })); }
public async Task <bool?> Handle(MessageEntityEx entity, PollMessage context = default, CancellationToken cancellationToken = default) { if (entity.Message.Chat.Type != ChatType.Private) { return(false); } var commandText = entity.AfterValue.Trim(); switch (entity.Command.ToString().ToLowerInvariant()) { case "/ign": case "/nick": case "/nickname": var author = entity.Message.From; var player = await myContext.Set <Player>().Where(p => p.UserId == author.Id).FirstOrDefaultAsync(cancellationToken); var nickname = commandText.ToString(); if (string.IsNullOrEmpty(nickname)) { if (player != null) { myContext.Remove(player); } } else { if (player == null) { player = new Player { UserId = author.Id }; myContext.Add(player); } player.Nickname = nickname; } await myContext.SaveChangesAsync(cancellationToken); return(true); default: return(false); } }
public async Task <bool?> Process(User user, string nickname, CancellationToken cancellationToken = default) { var player = await myContext.Set <Player>().Get(user, cancellationToken); if (!string.IsNullOrEmpty(nickname)) { if (player == null) { player = new Player { UserId = user.Id }; myContext.Add(player); } player.Nickname = nickname; await myContext.SaveChangesAsync(cancellationToken); } IReplyMarkup?replyMarkup = null; var builder = new TextBuilder(); if (!string.IsNullOrEmpty(player?.Nickname)) { builder .Append($"Your IGN is {player.Nickname:bold}") .NewLine() .NewLine(); replyMarkup = new InlineKeyboardMarkup(InlineKeyboardButton.WithCallbackData("Clear IGN", $"{PlayerCallbackQueryHandler.ID}:{PlayerCallbackQueryHandler.Commands.ClearIGN}")); } builder .Append($"To set up your in-game-name reply with it to this message.").NewLine() .Append($"Or use /{COMMAND} command.").NewLine() .Code("/ign your-in-game-name"); await myBot.SendTextMessageAsync(user.Id, builder.ToTextMessageContent(), cancellationToken : cancellationToken, replyMarkup : replyMarkup ?? new ForceReplyMarkup { InputFieldPlaceholder = "in-game-name" }); return(false); // processed, but not pollMessage }
public async Task <bool?> Handle(MessageEntityEx data, PollMessage context = default, CancellationToken cancellationToken = default) { if (!this.ShouldProcess(data, context)) { return(null); } var player = await myDB.Set <Player>().Get(data.Message.From, cancellationToken); var content = new TextBuilder("When someone is asking for Friendship") .ToTextMessageContent(); var replyMarkup = GetInlineKeyboardMarkup(player); await myBot.SendTextMessageAsync(data.Message.Chat.Id, content, replyMarkup : replyMarkup, cancellationToken : cancellationToken); return(false); // processed, but not pollMessage }
public async Task <Portal> Get(string guid, Location location = default, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(guid)) { return(null); } var portalSet = myContext.Set <Portal>(); var portal = await portalSet.FindAsync(new object[] { guid }, cancellationToken); if ((myClock.GetCurrentInstant().ToDateTimeOffset() - portal?.Modified)?.TotalDays < 1) // refresh every day { return(portal); } var portals = await Search(guid, location ?? myDefaultLocation, cancellationToken); return(portals.FirstOrDefault(p => p.Guid == guid)); }
public async Task <bool?> Handle(MessageEntityEx entity, PollMessage context = default, CancellationToken cancellationToken = default) { if (!this.ShouldProcess(entity, context)) { return(null); } var author = entity.Message.From; var settings = await myContext.Set <UserSettings>().Get(author, cancellationToken); var content = await GetMessage(settings, cancellationToken); var sentMessage = await myBot.SendTextMessageAsync(entity.Message.Chat, content, cancellationToken : cancellationToken, replyMarkup : new ReplyKeyboardMarkup(KeyboardButton.WithRequestLocation("Send a location to set up your home place and timezone")) { ResizeKeyboard = true, OneTimeKeyboard = true }); myCache.GetOrCreate(this[sentMessage.MessageId], _ => true); return(false); // processed, but not pollMessage }
public async Task <(string, bool, string)> Handle(CallbackQuery data, object context = default, CancellationToken cancellationToken = default) { var callback = data.Data.Split(':'); if (callback[0] != "clone") { return(null, false, null); } if (!int.TryParse(callback.ElementAtOrDefault(1) ?? "", NumberStyles.Integer, CultureInfo.InvariantCulture, out var pollId)) { return("Poll is publishing. Try later.", true, null); } var poll = await myContext .Set <Poll>() .Where(_ => _.Id == pollId) .IncludeRelatedData() .FirstOrDefaultAsync(cancellationToken); if (poll == null) { return("Poll is not found", true, null); } var pollMessage = new PollMessage { ChatId = data.From.Id, ChatType = ChatType.Private, UserId = data.From.Id, InlineMesssageId = data.InlineMessageId, Poll = poll }; await myRaidService.AddPollMessage(pollMessage, myUrlHelper, cancellationToken); var botUser = await myBot.GetMeAsync(cancellationToken); return(null, false, $"https://t.me/{botUser.Username}?start={pollMessage.GetExtendedPollId()}"); }
public async Task <bool?> Handle(ChosenInlineResult data, object context = default, CancellationToken cancellationToken = default) { var resultParts = data.ResultId.Split(':'); switch (resultParts[0]) { case PREFIX: if (myTimeZoneNotifyService.DecodeId(resultParts.Skip(1).FirstOrDefault(), out var chatId, out var messageId) && resultParts.Skip(2).FirstOrDefault() is {} timeZoneId&& myDateTimeZoneProvider.GetZoneOrNull(timeZoneId) is {}) { if (!await CheckRights(chatId, data.From, cancellationToken)) { await myBot.EditMessageTextAsync(data.InlineMessageId !, new InputTextMessageContent("You have no rights"), cancellationToken : cancellationToken); return(false); } var settings = myDB.Set <TimeZoneSettings>(); if (await EntityFrameworkQueryableExtensions.FirstOrDefaultAsync(settings.Where(s => s.ChatId == chatId && s.TimeZone == timeZoneId), cancellationToken) == null) { settings.Add(new TimeZoneSettings { ChatId = chatId, TimeZone = timeZoneId }); await myDB.SaveChangesAsync(cancellationToken); } var(content, replyMarkup) = await myTimeZoneNotifyService.GetSettingsMessage(new Chat { Id = chatId, Type = ChatType.Sender }, cancellationToken : cancellationToken); await myBot.EditMessageTextAsync(data.InlineMessageId !, content, replyMarkup, cancellationToken); if (messageId != 0) { await myBot.DeleteMessageAsync(chatId, messageId, cancellationToken); } return(true); } break; }
public async Task <(string text, bool showAlert, string url)> Handle(CallbackQuery data, object context = default, CancellationToken cancellationToken = default) { var callback = data.Data?.Split(':'); if (callback?[0] != ID) { return(null, false, null); } var player = await myDb.Set <Player>().Get(data.From, cancellationToken); switch (callback.Skip(1).FirstOrDefault()?.ToLowerInvariant()) { case Commands.ClearIGN: if (player != null) { player.Nickname = null; await myDb.SaveChangesAsync(cancellationToken); } await myBot.EditMessageReplyMarkupAsync(data, InlineKeyboardMarkup.Empty(), cancellationToken); return("Your IGN is removed", false, null); case Commands.ClearFriendCode: if (player != null) { player.FriendCode = null; await myDb.SaveChangesAsync(cancellationToken); } await myBot.EditMessageReplyMarkupAsync(data, InlineKeyboardMarkup.Empty(), cancellationToken); return("Your Friend Code is removed", false, null); } return(null, false, null); }
public async Task <DateTimeZone> GetTimeZone(InlineQuery inlineQuery, CancellationToken cancellationToken = default) { if (inlineQuery.Location is {} userLocation) { if (TimeZoneLookup.GetTimeZone(userLocation.Latitude, userLocation.Longitude).Result is {} timeZoneId) { if (myDateTimeZoneProvider.GetZoneOrNull(timeZoneId) is { } timeZone) { return(timeZone); } } } { var userSettings = await myDB.Set <UserSettings>().Get(inlineQuery.From, cancellationToken); if (userSettings?.TimeZoneId is { } timeZoneId&& myDateTimeZoneProvider.GetZoneOrNull(timeZoneId) is { } timeZone) { return(timeZone); } } return(myDefaultDateTimeZone); }
public async Task <PollMessage> GetOrCreatePollAndMessage(PollMessage pollMessage, IUrlHelper urlHelper, VoteEnum?format = null, CancellationToken cancellationToken = default) { bool exRaidGym = false; var pollId = pollMessage.PollId; if (pollId < 0) { pollId = pollMessage.PollId = -pollId; exRaidGym = true; } var poll = await myContext .Set <Poll>() .Where(_ => _.Id == pollId) .IncludeRelatedData() .FirstOrDefaultAsync(cancellationToken); if (poll != null) { var existingMessage = poll.Messages.FirstOrDefault(_ => _.InlineMesssageId == pollMessage.InlineMesssageId && _.ChatId == pollMessage.ChatId && _.MesssageId == pollMessage.MesssageId); if (existingMessage != null) { return(existingMessage); } pollMessage.Poll = poll; return(await AddPollMessage(pollMessage, urlHelper, cancellationToken)); } var pollData = GetTemporaryPoll(pollId); if (pollData == null) { return(null); } pollMessage.Poll = new Poll { Id = pollId, Title = pollData.Title, AllowedVotes = format, Owner = pollData.Owner, Portal = pollData.Portal, ExRaidGym = exRaidGym, Votes = new List <Vote>() }; myContext.Set <Poll>().Attach(pollMessage.Poll).State = EntityState.Added; if (pollData.Portal is Portal portal) { var portalSet = myContext.Set <Portal>(); var existingPortal = await portalSet.AsTracking().FirstOrDefaultAsync(p => p.Guid == portal.Guid, cancellationToken); if (existingPortal == null) { portalSet.Attach(portal).State = EntityState.Added; } else { existingPortal.Guid = portal.Guid; existingPortal.Name = portal.Name; existingPortal.Address = portal.Address; existingPortal.Image = portal.Image; existingPortal.Latitude = portal.Latitude; existingPortal.Longitude = portal.Longitude; portalSet.Attach(portal).State = EntityState.Modified; } } return(await AddPollMessage(pollMessage, urlHelper, cancellationToken, withLog : true)); }
public async Task <bool?> Handle(InlineQuery data, object context = default, CancellationToken cancellationToken = default) { var location = await myDb.Set <UserSettings>().GetLocation(data, cancellationToken); var queryParts = data.Query.Split(' ', StringSplitOptions.RemoveEmptyEntries); var poll = default(Poll); List <VoteLimit> limits = default; var pollQuery = new List <string>(queryParts.Length); var searchQuery = new List <string>(queryParts.Length); var query = pollQuery; foreach (var queryPart in queryParts) { switch (queryPart) { case { } locationPart when OpenLocationCode.IsValid(locationPart): location = OpenLocationCode.Decode(locationPart) is var code ? new Location { Longitude = (float)code.CenterLongitude, Latitude = (float)code.CenterLatitude } : location; break; case { } part when Regex.Match(part, PATTERN) is { Success: true } match: if (int.TryParse(match.Groups["pollId"].ValueSpan, out var pollId)) { poll = myRaidService .GetTemporaryPoll(pollId) .InitImplicitVotes(data.From, myBot.BotId); } query = searchQuery; break; case { } part when myGeneralInlineQueryHandler.ProcessLimitQueryString(ref limits, part): break; default: query.Add(queryPart); break; } } Portal[] portals; if (searchQuery.Count == 0) { portals = await myIngressClient.GetPortals(0.200, location, cancellationToken); } else { portals = await myIngressClient.Search(searchQuery, location, near : true, cancellationToken); } var results = new List <InlineQueryResult>(Math.Min(portals.Length, MAX_PORTALS_PER_RESPONSE) + 2); if (poll == null && pollQuery.Count != 0) { var voteFormat = await myDb.Set <Settings>().GetFormat(data.From.Id, cancellationToken); poll = await new Poll(data) { Title = string.Join(" ", pollQuery), AllowedVotes = voteFormat, ExRaidGym = false, Limits = limits }.DetectRaidTime(myTimeZoneService, () => Task.FromResult(location), async ct => myClock.GetCurrentInstant().InZone(await myGeoCoder.GetTimeZone(data, ct)), cancellationToken); for (var i = 0; i < portals.Length && i < MAX_PORTALS_PER_RESPONSE; i++) { var portalPoll = new Poll(poll) { Portal = portals[i], Limits = poll.Limits ?? limits }.InitImplicitVotes(data.From, myBot.BotId); await myRaidService.GetPollId(portalPoll, data.From, cancellationToken); results.Add(myGeneralInlineQueryHandler.GetInlineResult(portalPoll)); if (i == 0) { portalPoll.Id = -portalPoll.Id; portalPoll.ExRaidGym = true; results.Add(myGeneralInlineQueryHandler.GetInlineResult(portalPoll)); } } } else { for (var i = 0; i < portals.Length && i < MAX_PORTALS_PER_RESPONSE; i++) { var portal = portals[i]; var title = portal.Name is { } name&& !string.IsNullOrWhiteSpace(name) ? name : portal.Guid; if (portal.EncodeGuid() is { } portalGuid) { InlineQueryResultArticle Init(InlineQueryResultArticle article, InlineKeyboardButton createButton) { const int thumbnailSize = 64; article.Description = portal.Address; article.ReplyMarkup = new InlineKeyboardMarkup(createButton); article.ThumbUrl = portal.GetImage(myUrlHelper, thumbnailSize)?.AbsoluteUri; article.ThumbHeight = thumbnailSize; article.ThumbWidth = thumbnailSize; return(article); } var portalContent = new TextBuilder() .Bold(builder => builder.Sanitize(portal.Name)).NewLine() .Sanitize(portal.Address) .Link("\u200B", portal.Image) .ToTextMessageContent(); results.Add(Init( new InlineQueryResultArticle($"portal:{portal.Guid}", title, portalContent), InlineKeyboardButton.WithSwitchInlineQuery("Create a poll", $"{PREFIX}{portalGuid} {poll?.Title}"))); if (i == 0) { var exRaidPortalContent = new TextBuilder() .Sanitize("☆ ") .Bold(builder => builder.Sanitize(portal.Name)) .Sanitize(" (EX Raid Gym)").NewLine() .Sanitize(portal.Address) .Link("\u200B", portal.Image) .ToTextMessageContent(); results.Add(Init( new InlineQueryResultArticle($"portal:{portal.Guid}+", $"☆ {title} (EX Raid Gym)", exRaidPortalContent), InlineKeyboardButton.WithSwitchInlineQuery("Create a poll ☆ (EX Raid Gym)", $"{PREFIX}{portalGuid}+ {poll?.Title}"))); } } } } if (searchQuery.Count == 0) { results.Add( new InlineQueryResultArticle("EnterGymName", "Enter a Gym's Title", new InputTextMessageContent("Enter a Gym's Title to search")) { Description = "to search", ThumbUrl = default(Portal).GetImage(myUrlHelper)?.AbsoluteUri }); } if (results.Count == 0) { var search = string.Join(" ", searchQuery); results.Add(new InlineQueryResultArticle("NothingFound", "Nothing found", new TextBuilder($"Nothing found by request ").Code(builder => builder.Sanitize(search)).ToTextMessageContent()) { Description = $"Request {search}", ThumbUrl = myUrlHelper.AssetsContent(@"static_assets/png/btn_close_normal.png").AbsoluteUri }); } await myBot.AnswerInlineQueryWithValidationAsync(data.Id, results, cacheTime : 0, isPersonal : true, cancellationToken : cancellationToken); await myDb.SaveChangesAsync(cancellationToken); return(true); }
public async Task <((decimal?lat, decimal?lon) location, string gym, string distance)> ProcessGym(Raid raid, StringBuilder description, int?precision = null, MidpointRounding?rounding = null, CancellationToken cancellationToken = default) { var location = (lat : raid.Lat, lon : raid.Lon); string distance = default; var gym = raid.Gym ?? raid.PossibleGym; if (gym == null) { raid.PossibleGym = await myDbContext.Set <Raid>() .FindKnownGym((decimal)raid.Lat, (decimal)raid.Lon, precision, rounding) .Select(_ => _.Gym ?? _.PossibleGym) .FirstOrDefaultAsync(cancellationToken); } //if (!string.IsNullOrEmpty(gym)) //{ // description.Append(gym); //} //var geoCode = await myGeoCoder.Decode(lon, lat, cancellationToken: cancellationToken, // parameters: new Dictionary<string, string> // { // {"results", "1"}, // {"kind", "metro"} // }); //var metro = geoCode?.featureMember?.FirstOrDefault()?.GeoObject; try { var destination = new Location((double)location.lat, (double)location.lon); var geoRequest = new PlacesNearByRequest { Location = destination, Type = "subway_station", RankBy = RankBy.Distance, ApiKey = myGeoCoderOptions.GoogleKey }; var geoResponse = await GoogleMaps.PlacesNearBy.QueryAsync(geoRequest, myTelemetryClient, cancellationToken); Result foundAddress = null; Action <StringBuilder> postProcessor = null; foreach (var address in geoResponse.Results) { if (address.Types.Contains("subway_station")) { var distanceMatrixRequest = new DistanceMatrixRequest { Origins = new[] { $"place_id:{address.PlaceId}" }, Destinations = new[] { destination.LocationString }, Mode = DistanceMatrixTravelModes.walking, ApiKey = myGeoCoderOptions.GoogleKey }; var distanceMatrixResponse = await GoogleMaps.DistanceMatrix.QueryAsync(distanceMatrixRequest, myTelemetryClient, cancellationToken); var distanceElement = distanceMatrixResponse.Rows.FirstOrDefault()?.Elements.FirstOrDefault(); if (distanceElement?.Distance.Value <= myGeoCoderOptions.MaxDistanceToMetro) { foundAddress = address; postProcessor = descr => descr.Sanitize(distance = $" ∙ {distanceElement.Distance.Text} ∙ {distanceElement.Duration.Text}"); break; } } } string placeId; string name; if (foundAddress == null) { var geoCodingRequest = new GeocodingRequest { Location = destination, ApiKey = myGeoCoderOptions.GoogleKey }; var geoCodingResults = (await GoogleMaps.Geocode.QueryAsync(geoCodingRequest, myTelemetryClient, cancellationToken)).Results; var result = geoCodingResults.FirstOrDefault(_ => _.Types.Contains("locality")); placeId = result?.PlaceId; name = result?.FormattedAddress; } else { placeId = foundAddress.PlaceId; name = foundAddress.Name; } raid.NearByPlaceId = placeId; raid.NearByAddress = name; if (!string.IsNullOrEmpty(name)) { description .Append(description.Length > 0 ? " ∙ " : "") .Append(name); postProcessor?.Invoke(description); } } catch (Exception ex) { myTelemetryClient.TrackExceptionEx(ex); } return(location, gym, distance); }
public async Task <(string, bool, string)> Handle(CallbackQuery data, object context = default, CancellationToken cancellationToken = default) { var callback = data.Data.Split(':'); if (callback[0] != "set") { return(null, false, null); } if (!await myChatInfo.CandEditPoll(data.Message.Chat, data.From?.Id, cancellationToken)) { return("You can't edit the poll.", true, null); } var chatId = data.Message.Chat.Id; var settingsSet = myContext.Set <Settings>(); var allSettings = await settingsSet.GetSettings(chatId).AsTracking().ToListAsync(cancellationToken); Settings settings = null; switch (callback.ElementAtOrDefault(1)) { case "list": return(await Return(await SettingsList(chatId, cancellationToken))); case var identifier when int.TryParse(identifier, out var id): settings = allSettings.FirstOrDefault(setting => setting.Id == id); break; } if (settings == null) { // create a new settings = new Settings { Chat = chatId, Order = allSettings.Select(_ => _.Order).DefaultIfEmpty(-1).Max() + 1, Format = VoteEnum.Standard }; await settingsSet.AddAsync(settings, cancellationToken); } string message = null; switch (callback.ElementAtOrDefault(2) ?? "") { case "default": settings.Order = allSettings.Select(_ => _.Order).DefaultIfEmpty(1).Min() - 1; await myContext.SaveChangesAsync(cancellationToken); return(await Return(await SettingsList(chatId, cancellationToken), "Default poll format is changed.")); case "delete": settingsSet.Remove(settings); await myContext.SaveChangesAsync(cancellationToken); return(await Return(await SettingsList(chatId, cancellationToken), "Poll format is deleted.")); case var format when FlagEnums.TryParseFlags(format, out VoteEnum toggleVotes, EnumFormat.DecimalValue): settings.Format = FlagEnums.ToggleFlags(settings.Format, toggleVotes); // adjust ⁺¹ if (settings.Format.HasAnyFlags(VoteEnum.Plus) && !settings.Format.HasAnyFlags(VoteEnum.Countable)) { settings.Format = settings.Format.RemoveFlags(VoteEnum.Plus); } if (await myContext.SaveChangesAsync(cancellationToken) > 0) { message = "Poll format is changed."; } goto default; default: var buttons = new [] { VoteEnum.Team, VoteEnum.TeamHarmony, VoteEnum.Plus1, VoteEnum.Remotely, VoteEnum.Invitation, VoteEnum.MayBe, VoteEnum.Yes, VoteEnum.Thumbs, VoteEnum.HarryPotter, VoteEnum.Cancel, VoteEnum.Share } .Select(format => new [] { InlineKeyboardButton.WithCallbackData($"{(settings.Format.HasAllFlags(format) ? '☑' : '☐')} {format.Format(new StringBuilder())}", $"{ID}:{settings.Id}:{format:D}") }); if (allSettings.FirstOrDefault() is Settings existingDefault && existingDefault != settings) { buttons = buttons.Append(new[] { InlineKeyboardButton.WithCallbackData("Make as a default", $"{ID}:{settings.Id}:default") }); } buttons = buttons .Append(new [] { InlineKeyboardButton.WithCallbackData("Delete", $"{ID}:{settings.Id}:delete") }) .Append(new [] { InlineKeyboardButton.WithCallbackData("Back", $"{ID}:list") }); return(await Return(( settings.Format.Format(new StringBuilder("Selected poll format:").AppendLine()).ToTextMessageContent(), new InlineKeyboardMarkup(buttons)), message)); } async Task <(string, bool, string)> Return((InputTextMessageContent content, InlineKeyboardMarkup replyMarkup) pass, string notification = "") { await myTelegramBotClient.EditMessageTextAsync(data.Message.Chat, data.Message.MessageId, pass.content.MessageText, pass.content.ParseMode, pass.content.DisableWebPagePreview, pass.replyMarkup, cancellationToken); return(notification, false, null); } }
public async Task Execute(CancellationToken cancellationToken) { var nowWithLeadTime = myClock.GetCurrentInstant().ToDateTimeOffset() + myNotificationLeadTime; var previous = nowWithLeadTime - CheckPeriod - ErrorOffset; var polls = await myDB.Set <Poll>() .Where(poll => poll.Time > previous && poll.Time <= nowWithLeadTime) .IncludeRelatedData() .Include(poll => poll.Notifications) .ToListAsync(cancellationToken); foreach (var poll in polls) { using var notificationOperation = myTelemetryClient.StartOperation( new DependencyTelemetry(GetType().Name, null, "PollNotification", poll.Id.ToString())); var pollMode = poll.AllowedVotes?.HasFlag(VoteEnum.Invitation) ?? false ? PollMode.DefaultWithInvitation : default; var alreadyNotified = poll.Notifications.Where(notification => notification.Type == NotificationType.PollTime).Select(notification => notification.ChatId).ToHashSet(); foreach (var pollVote in poll.Votes) { var botId = pollVote.BotId; var userId = pollVote.UserId; if (!(pollVote.Team?.HasAnyFlags(VoteEnum.Notify) ?? false)) { continue; } if (alreadyNotified.Contains(userId)) { continue; } try { var pollMessage = new PollMessage { BotId = botId, UserId = userId, Chat = new Chat { Id = userId, Type = ChatType.Private }, Poll = poll, PollId = poll.Id, PollMode = pollMode }; var notificationMessage = await myRaidService.GetOrCreatePollAndMessage(pollMessage, null, poll.AllowedVotes, cancellationToken); var notification = new Notification { PollId = poll.Id, BotId = pollMessage.BotId, ChatId = notificationMessage.Chat.Id, MessageId = notificationMessage.MessageId, DateTime = notificationMessage.Modified, Type = NotificationType.PollTime }; poll.Notifications.Add(notification); myTelemetryClient.TrackEvent("Notification", new Dictionary <string, string?> { { nameof(notificationMessage.UserId), notificationMessage.UserId?.ToString() }, { nameof(notificationMessage.PollId), notificationMessage.PollId.ToString() }, { nameof(notificationMessage.BotId), notificationMessage.BotId?.ToString() }, { nameof(notificationMessage.ChatId), notificationMessage.ChatId?.ToString() }, { nameof(notificationMessage.MessageId), notificationMessage.MessageId?.ToString() } }); } catch (Exception ex) { if (ex is ApiRequestException { ErrorCode: 403 })
public async Task <(string text, bool showAlert, string url)> Handle(CallbackQuery data, object context = default, CancellationToken cancellationToken = default) { var callback = data.Data?.Split(':'); if (callback?[0] != ID) { return(null, false, null); } var host = data.From; var player = await myDB.Set <Player>().Get(host, cancellationToken); var command = callback.Skip(1).FirstOrDefault(); var commandParameter = callback.Skip(2).FirstOrDefault(); switch (command) { case Commands.SendCodeId: case Commands.AutoApproveId: case Commands.ApproveSettingsId when bool.TryParse(commandParameter, out var autoApprove) && autoApprove: if (player?.FriendCode == null) { await myFriendshipService.SetupFriendCode(myBot, host, StringSegment.Empty, cancellationToken); return("Please, specify your Friend Code first", true, null); } break; } switch (command) { case Commands.SendCodeId when long.TryParse(commandParameter, out var userId) && long.TryParse(callback.Skip(3).FirstOrDefault(), out var botId): try { if (!myBots.TryGetValue(botId, out var bot)) { bot = myBot; } await myFriendshipService.SendCode(bot, new User { Id = userId }, host, player, cancellationToken); await myBot.EditMessageReplyMarkupAsync(data, InlineKeyboardMarkup.Empty(), cancellationToken); return("Friend Code sent", false, null); } catch (ApiRequestException apiEx) when(apiEx.ErrorCode == 403) { return("User blocked personal messages for the bot.\r\nSend him/her code by yourself.", true, null); } case Commands.AskCodeId when long.TryParse(commandParameter, out var userId) && long.TryParse(callback.Skip(3).FirstOrDefault(), out var botId): try { if (!myBots.TryGetValue(botId, out var bot)) { bot = myBot; } await myFriendshipService.AskCode(host, myBot, new User { Id = userId }, bot, player, cancellationToken); await myBot.EditMessageReplyMarkupAsync(data, InlineKeyboardMarkup.Empty(), cancellationToken); return("Friend Code asked", false, null); } catch (ApiRequestException apiEx) when(apiEx.ErrorCode == 403) { return("User blocked personal messages for the bot.\r\nAsk him/her for the code by yourself.", true, null); } case Commands.ApproveId when int.TryParse(commandParameter, out var userId): await myFriendshipService.ApproveFriendship(host, new User { Id = userId }, cancellationToken); await myBot.EditMessageReplyMarkupAsync(data, InlineKeyboardMarkup.Empty(), cancellationToken); return("He/She marked as already Friend.", false, null); case Commands.AutoApproveId when int.TryParse(commandParameter, out var pollId): var poll = await myDB .Set <Poll>() .Where(_ => _.Id == pollId) .IncludeRelatedData() .FirstOrDefaultAsync(cancellationToken); if (poll == null) { return("Poll is publishing. Try later.", true, null); } var hostVote = poll.Votes.FirstOrDefault(_ => _.UserId == host.Id); if (hostVote == null) { return("Poll is publishing. Try later.", true, null); } hostVote.Team |= VoteEnum.AutoApproveFriend; // approve already awaiting requests foreach (var friendship in await myDB.Set <Friendship>() .Where(f => f.PollId == pollId && (f.Id == host.Id || f.FriendId == host.Id)).ToListAsync(cancellationToken)) { friendship.Type = FriendshipType.Approved; if (poll.Votes.SingleOrDefault(v => (v.UserId == friendship.Id || v.UserId == friendship.FriendId) && v.UserId != host.Id) is { } vote) { try { if (vote.BotId is not { } botId || !myBots.TryGetValue(botId, out var bot)) { bot = myBot; } await myFriendshipService.SendCode(bot, vote.User, host, player, cancellationToken); } catch (ApiRequestException apiEx) when(apiEx.ErrorCode == 403) { // personal messages banned for host - propose user to ask for FC manually } } } await myDB.SaveChangesAsync(cancellationToken); await myBot.EditMessageReplyMarkupAsync(data, InlineKeyboardMarkup.Empty(), cancellationToken); return($"All invitees of `{poll.Title}` will be automatically approved.", false, null); case Commands.ApproveSettingsId: if (player == null) { player = new Player { UserId = host.Id }; myDB.Add(player); } if (bool.TryParse(commandParameter, out var autoApprove)) { player.AutoApproveFriendship = autoApprove; } else { player.AutoApproveFriendship = null; } await myDB.SaveChangesAsync(cancellationToken); await myBot.EditMessageReplyMarkupAsync(data, FriendshipCommandHandler.GetInlineKeyboardMarkup(player), cancellationToken); return("Friendship settings modified", false, null); } return(null, false, null); }
public async Task <PollMessage> GetOrCreatePollAndMessage(PollMessage pollMessage, IUrlHelper urlHelper, VoteEnum?format = null, CancellationToken cancellationToken = default) { bool exRaidGym = false; var pollId = pollMessage.PollId; if (pollId < 0) { pollId = pollMessage.PollId = -pollId; exRaidGym = true; } var poll = pollMessage.Poll ?? await myContext .Set <Poll>() .Where(_ => _.Id == pollId) .IncludeRelatedData() .Include(poll => poll.Notifications) .FirstOrDefaultAsync(cancellationToken); if (poll != null) { var existingMessage = poll.Messages.FirstOrDefault(_ => _.InlineMessageId == pollMessage.InlineMessageId && _.ChatId == pollMessage.ChatId && _.MessageId == pollMessage.MessageId); if (existingMessage != null) { return(existingMessage); } pollMessage.Poll = poll; return(await AddPollMessage(pollMessage, urlHelper, cancellationToken)); } var pollData = GetTemporaryPoll(pollId); if (pollData == null) { return(null); } pollMessage.Poll = new Poll { Id = pollId, Title = pollData.Title, AllowedVotes = format, Owner = pollData.Owner, Portal = pollData.Portal, ExRaidGym = exRaidGym, Time = pollData.Time, TimeZoneId = pollData.TimeZoneId, Votes = pollData.Votes, Limits = pollData.Limits }; if (pollMessage.UserId is { } userId) { pollMessage.Poll.InitImplicitVotes(GetCachedUser(userId), pollMessage.BotId); } // MUST be set before portal myContext.Set <Poll>().Attach(pollMessage.Poll).State = EntityState.Added; if (pollData.Portal is { } portal) { var portalSet = myContext.Set <Portal>(); // always check remote var existingPortal = await portalSet.AsNoTracking().FirstOrDefaultAsync(p => p.Guid == portal.Guid, cancellationToken); if (existingPortal == null) { portalSet.Attach(portal).State = EntityState.Added; } else { myContext.Entry(existingPortal).SetNotNullProperties(portal); myContext.Entry(portal).SetNotNullProperties(existingPortal); } } return(await AddPollMessage(pollMessage, urlHelper, cancellationToken, withLog : true)); }
public async Task <bool?> Handle(InlineQuery data, object context = default, CancellationToken cancellationToken = default) { IReadOnlyCollection <InlineQueryResultBase> inlineQueryResults; string query = null; Portal portal = null; bool exRaidGym = false; string switchPmParameter = null; foreach (var queryPart in data.Query.Split(' ', StringSplitOptions.RemoveEmptyEntries)) { switch (queryPart) { case string _ when queryPart.StartsWith(GymInlineQueryHandler.PREFIX): var guid = queryPart.Substring(GymInlineQueryHandler.PREFIX.Length); if (guid.EndsWith('+')) { guid = guid.Substring(0, guid.Length - 1); exRaidGym = true; } var portalGuid = PortalEx.DecodeGuid(guid); portal = await myIngressClient.Get(portalGuid, data.Location, cancellationToken); break; default: query += (query == null ? default(char?) : ' ') + queryPart; break; } } if (string.IsNullOrWhiteSpace(data.Query)) // check whole query for sharing branch { inlineQueryResults = await myShareInlineQueryHandler.GetActivePolls(data.From, cancellationToken); } else if (string.IsNullOrWhiteSpace(query)) { inlineQueryResults = new[] { new InlineQueryResultArticle($"EnterPollTopic", "Enter a topic", new InputTextMessageContent("Enter a topic to create a poll")) { Description = "to create a poll", ThumbUrl = myUrlHelper.AssetsContent("static_assets/png/POI_Submission_Illustration_02.png").ToString() } }; } else { var pollId = await myRaidService.GetPollId(new Poll(data) { Title = query, Portal = portal }, cancellationToken); switchPmParameter = portal == null ? $"{SwitchToGymParameter}{pollId}" : null; ICollection <VoteEnum> voteFormats = await myDb.Set <Settings>().GetFormats(data.From.Id, cancellationToken).ToListAsync(cancellationToken); if (voteFormats.Count == 0) { voteFormats = VoteEnumEx.DefaultVoteFormats; } inlineQueryResults = voteFormats .Select(format => new Poll { Id = exRaidGym ? -pollId : pollId, Title = query, AllowedVotes = format, Portal = portal, ExRaidGym = exRaidGym }) .Select((fakePoll, i) => new InlineQueryResultArticle(fakePoll.GetInlineId(i), fakePoll.GetTitle(myUrlHelper), fakePoll.GetMessageText(myUrlHelper, disableWebPreview: fakePoll.DisableWebPreview())) { Description = fakePoll.AllowedVotes?.Format(new StringBuilder("Create a poll ")).ToString(), HideUrl = true, ThumbUrl = fakePoll.GetThumbUrl(myUrlHelper).ToString(), ReplyMarkup = fakePoll.GetReplyMarkup() }) .ToArray(); } await myBot.AnswerInlineQueryWithValidationAsync(data.Id, inlineQueryResults, switchPmText : switchPmParameter != null? "Link the poll to a gym" : null, switchPmParameter : switchPmParameter, cacheTime : 0, cancellationToken : cancellationToken); await myDb.SaveChangesAsync(cancellationToken); return(true); }
public async Task <bool?> Handle(InlineQuery data, object context = default, CancellationToken cancellationToken = default) { var location = data.Location; var queryParts = data.Query.Split(' ', StringSplitOptions.RemoveEmptyEntries); var poll = default(Poll); var pollQuery = new List <string>(queryParts.Length); var searchQuery = new List <string>(queryParts.Length); var query = pollQuery; foreach (var queryPart in queryParts) { switch (queryPart) { case string locationPart when OpenLocationCode.IsValid(locationPart): location = OpenLocationCode.Decode(locationPart) is var code ? new Location { Longitude = (float)code.CenterLongitude, Latitude = (float)code.CenterLatitude } : location; break; case string part when Regex.Match(part, PATTERN) is Match match && match.Success: if (int.TryParse(match.Groups["pollId"].Value, out var pollId)) { poll = myRaidService.GetTemporaryPoll(pollId); } query = searchQuery; break; default: query.Add(queryPart); break; } } Portal[] portals; if (searchQuery.Count == 0) { portals = await myIngressClient.GetPortals(0.200, location, cancellationToken); } else { portals = await myIngressClient.Search(searchQuery, location, cancellationToken); } var results = new List <InlineQueryResultBase>(Math.Min(portals.Length, MAX_PORTALS_PER_RESPONSE) + 2); if ((poll == null) && (pollQuery.Count != 0)) { var voteFormat = await myDb.Set <Settings>().GetFormat(data.From.Id, cancellationToken); for (var i = 0; i < portals.Length && i < MAX_PORTALS_PER_RESPONSE; i++) { poll = new Poll(data) { Title = string.Join(" ", pollQuery), AllowedVotes = voteFormat, Portal = portals[i], ExRaidGym = false }; await myRaidService.GetPollId(poll, cancellationToken); results.Add(new InlineQueryResultArticle(poll.GetInlineId(), poll.GetTitle(myUrlHelper), poll.GetMessageText(myUrlHelper, disableWebPreview: poll.DisableWebPreview())) { Description = poll.AllowedVotes?.Format(new StringBuilder("Create a poll ")).ToString(), HideUrl = true, ThumbUrl = poll.GetThumbUrl(myUrlHelper).ToString(), ReplyMarkup = poll.GetReplyMarkup() }); if (i == 0) { poll.Id = -poll.Id; poll.ExRaidGym = true; results.Add(new InlineQueryResultArticle(poll.GetInlineId(), poll.GetTitle(myUrlHelper) + " (EX Raid Gym)", poll.GetMessageText(myUrlHelper, disableWebPreview: poll.DisableWebPreview())) { Description = poll.AllowedVotes?.Format(new StringBuilder("Create a poll ")).ToString(), HideUrl = true, ThumbUrl = poll.GetThumbUrl(myUrlHelper).ToString(), ReplyMarkup = poll.GetReplyMarkup() }); } } } else { for (var i = 0; i < portals.Length && i < MAX_PORTALS_PER_RESPONSE; i++) { var portal = portals[i]; var title = portal.Name is string name && !string.IsNullOrWhiteSpace(name) ? name : portal.Guid; if (portal.EncodeGuid() is string portalGuid) { InlineQueryResultArticle Init(InlineQueryResultArticle article, InlineKeyboardButton createButton) { const int thumbnailSize = 64; article.Description = portal.Address; article.ReplyMarkup = new InlineKeyboardMarkup(createButton); article.ThumbUrl = portal.GetImage(myUrlHelper, thumbnailSize)?.AbsoluteUri; article.ThumbHeight = thumbnailSize; article.ThumbWidth = thumbnailSize; return(article); } var portalContent = new StringBuilder() .Bold((builder, mode) => builder.Sanitize(portal.Name)).NewLine() .Sanitize(portal.Address) .Link("\u200B", portal.Image) .ToTextMessageContent(); results.Add(Init( new InlineQueryResultArticle($"portal:{portal.Guid}", title, portalContent), InlineKeyboardButton.WithSwitchInlineQuery("Create a poll", $"{PREFIX}{portalGuid} {poll?.Title}"))); if (i == 0) { var exRaidPortalContent = new StringBuilder() .Sanitize("☆ ") .Bold((builder, mode) => builder.Sanitize(portal.Name)) .Sanitize(" (EX Raid Gym)").NewLine() .Sanitize(portal.Address) .Link("\u200B", portal.Image) .ToTextMessageContent(); results.Add(Init( new InlineQueryResultArticle($"portal:{portal.Guid}+", $"☆ {title} (EX Raid Gym)", exRaidPortalContent), InlineKeyboardButton.WithSwitchInlineQuery("Create a poll ☆ (EX Raid Gym)", $"{PREFIX}{portalGuid}+ {poll?.Title}"))); } } } } if (searchQuery.Count == 0) { results.Add( new InlineQueryResultArticle("EnterGymName", "Enter a Gym's Title", new InputTextMessageContent("Enter a Gym's Title to search")) { Description = "to search", ThumbUrl = default(Portal).GetImage(myUrlHelper)?.AbsoluteUri }); } if (results.Count == 0) { var search = string.Join(" ", searchQuery); results.Add(new InlineQueryResultArticle("NothingFound", "Nothing found", new StringBuilder($"Nothing found by request ").Code((builder, mode) => builder.Sanitize(search, mode)).ToTextMessageContent()) { Description = $"Request {search}", ThumbUrl = myUrlHelper.AssetsContent(@"static_assets/png/btn_close_normal.png").AbsoluteUri }); } await myBot.AnswerInlineQueryWithValidationAsync(data.Id, results, cacheTime : 0, isPersonal : true, cancellationToken : cancellationToken); await myDb.SaveChangesAsync(cancellationToken); return(true); }
public async Task <bool?> Handle(MessageEntityEx entity, PollMessage pollMessage, CancellationToken cancellationToken = default) { var commandText = entity.AfterValue.Trim(); switch (entity.Command.ToString().ToLowerInvariant()) { case "/new": var title = commandText; if (StringSegment.IsNullOrEmpty(title)) { return(false); } pollMessage.Poll = new Poll(myMessage) { Title = title.Value }; return(true); case "/poll" when PollEx.TryGetPollId(commandText, out var pollId, out _): case "/start" when PollEx.TryGetPollId(commandText, out pollId, out _): var existingPoll = await myContext .Set <Poll>() .Where(_ => _.Id == pollId) .IncludeRelatedData() .FirstOrDefaultAsync(cancellationToken); if (existingPoll == null) { return(false); } pollMessage.Poll = existingPoll; return(true); case "/set": var(setContent, setReplyMarkup) = await mySetCallbackQueryHandler.SettingsList(myMessage.Chat.Id, cancellationToken); await myTelegramBotClient.SendTextMessageAsync(myMessage.Chat, setContent.MessageText, setContent.ParseMode, setContent.DisableWebPagePreview, disableNotification : true, replyMarkup : setReplyMarkup, cancellationToken : cancellationToken); return(false); // processed, but not pollMessage case "/help" when myMessage.Chat.Type == ChatType.Private: await myTelegramBotClient.SendTextMessageAsync(myMessage.Chat, "http://telegra.ph/Raid-Battles-Bot-Help-02-18", cancellationToken : cancellationToken); return(false); // processed, but not pollMessage // deep linking to gym case "/start" when commandText.StartsWith(GeneralInlineQueryHandler.SwitchToGymParameter, StringComparison.Ordinal): var query = commandText.Substring(GeneralInlineQueryHandler.SwitchToGymParameter.Length); var pollTitle = new StringBuilder("Poll creation"); if (int.TryParse(query, out int gymPollId)) { pollTitle .NewLine() .Bold((builder, m) => builder.Sanitize(myRaidService.GetTemporaryPoll(gymPollId)?.Title, m)); } var content = pollTitle.ToTextMessageContent(); await myTelegramBotClient.SendTextMessageAsync(myMessage.Chat, content.MessageText, content.ParseMode, content.DisableWebPagePreview, disableNotification : true, replyMarkup : new InlineKeyboardMarkup(InlineKeyboardButton.WithSwitchInlineQueryCurrentChat("Choose a Gym", $"{GymInlineQueryHandler.PREFIX}{query} ")), cancellationToken : cancellationToken); return(false); case "/p" when int.TryParse(commandText, out var pollId): var poll = await myContext .Set <Poll>() .Where(_ => _.Id == pollId) .IncludeRelatedData() .FirstOrDefaultAsync(cancellationToken); if (poll == null) { return(false); } var voters = poll.Votes.ToDictionary(vote => vote.UserId, vote => vote.User); var users = poll.Messages .Where(message => message.UserId != null) .Select(message => message.UserId.Value) .Distinct() .ToList(); var unknownUsers = users.Where(u => !voters.ContainsKey(u)).ToList(); var data = await myContext .Set <Vote>() .Where(v => unknownUsers.Contains(v.UserId)) .GroupBy(v => v.UserId) .Select(vv => vv.OrderByDescending(vote => vote.Modified).First()) .ToListAsync(cancellationToken); var allVoters = voters.Values.Concat(data.Select(d => d.User)) .ToDictionary(u => u.Id, u => u); var info = users .Select(u => allVoters.TryGetValue(u, out var user) ? user : new User { Id = u, FirstName = u.ToString() }) .Aggregate( poll.GetDescription(myUrlHelper).NewLine().NewLine(), (builder, user) => builder.Append(user.GetLink()).NewLine()) .ToTextMessageContent(disableWebPreview: true); await myTelegramBotClient.SendTextMessageAsync(myMessage.Chat, info.MessageText, info.ParseMode, info.DisableWebPagePreview, disableNotification : true, replyToMessageId : myMessage.MessageId, cancellationToken : cancellationToken); return(false); } return(null); }