diff --git a/README.md b/README.md index ea12079b..02393294 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,13 @@ Works with [RealDeviceMap](https://github.com/123FLO321/RealDeviceMap) ## Description: -Sends Discord notifications based on pre-defined filters for Pokemon, raids, raid eggs, field research quests, Team Rocket invasions, gym team changes, and weather. Also supports Discord user's subscribing to Pokemon, raid, quest, and Team Rocket invasion notifications via DM. +Sends Discord notifications based on pre-defined filters for Pokemon, raids, raid eggs, field research quests, Team Rocket invasions, gym team changes, and weather. Also supports Discord user's subscribing to Pokemon, raid, quest, Team Rocket invasion, and Pokestop lure notifications via DM. ## Features: - Supports multiple Discord servers. - Discord channel alarm reports for Pokemon, raids, eggs, quests, lures, invasions, gym team changes, and weather. -- Per user custom Discord notifications for Pokemon, raids, quests, and invasions. +- Per user custom Discord notifications for Pokemon, raids, quests, invasions, and lures. - User interface to configure Discord notifications with ease (as well as Discord commands). (https://github.com/versx/WhMgr-UI) - Subscription notifications based on pre-defined distance. - Customizable alert messages with dynamic text replacement. @@ -94,6 +94,11 @@ bitsadmin /transfer dotnet-install-job /download /priority FOREGROUND https://ra "token": "", // Alarms file path. "alarms": "alarms.json", + // Geofences related to the Discord guild. **NOT** used for subscriptions. + "geofences": [ + "City1.txt", + "City2.json" + ], // Custom user subscriptions "subscriptions": { // Enable or disable custom direct message notification subscriptions per user. @@ -109,12 +114,13 @@ bitsadmin /transfer dotnet-install-job /download /priority FOREGROUND https://ra // Maximum amount of Invasion subscriptions a user can set, set as 0 for no limit. "maxInvasionSubscriptions": 0, // Maximum amount of Gym subscriptions a user can set, set as 0 for no limit. - "maxGymSubscriptions": 0 + "maxGymSubscriptions": 0, + // Maximum amount of Lure subscriptions a user can set, set as 0 for no limit. + "maxLureSubscriptions": 0 }, // Enable city role assignments. "enableCities": false, - // City/geofence role(s) used to assign city roles (if enabled) as well as depict what - // geofences are related to which Discord guild. **NOT** used for subscriptions. + // Cities used to assign roles (if enabled), **NOT** used or related to geofences or subscriptions. "cityRoles": [ "City1", "City2" @@ -147,60 +153,55 @@ bitsadmin /transfer dotnet-install-job /download /priority FOREGROUND https://ra 000000000000000000 ], // Custom Discord status per server, leave blank or null to use current version. - "status": "" + "status": "", + "dmAlertsFile": "default.json", + "embedColors": { + "pokemon": { + "iv": [ + { "min": 0, "max": 0, "color": "#ffffff" }, + { "min": 1, "max": 89, "color": "#ffff00" }, + { "min": 90, "max": 99, "color": "#ffa500" }, + { "min": 100, "max": 100, "color": "#00ff00" } + ], + "pvp": [ + { "min": 1, "max": 1, "color": "#000080" }, + { "min": 6, "max": 25, "color": "#800080" }, + { "min": 25, "max": 100, "color": "#aa2299" } + ] + }, + "raids": { + "1": "#ff69b4", + "2": "#ff69b4", + "3": "#ffff00", + "4": "#ffff00", + "5": "#800080", + "6": "#a52a2a", + "ex": "#2c2f33" + }, + "pokestops": { + "quests": "#ffa500", + "lures": { + "normal": "#ff69b4", + "glacial": "#6495ed", + "mossy": "#507d2a", + "magnetic": "#808080" + }, + "invasions": "#ff0000" + }, + "weather": { + "clear": "#ffff00", + "cloudy": "#99aab5", + "fog": "#9a9a9a", + "partlyCloudy": "#808080", + "rain": "#0000ff", + "snow": "#ffffff", + "windy": "#800080" + } + } }, "000000000000000002": { - "commandPrefix": ".", - "emojiGuildId": 000000000000000001, - "ownerId": 000000000000000000, - "donorRoleIds": [ - 000000000000000000 - ], - "moderatorRoleIds": [ - 000000000000000000 - ], - "token": "", - "alarms": "alarms2.json", - // Custom user subscriptions - "subscriptions": { - // Enable or disable custom direct message notification subscriptions per user. - "enabled": false, - // Maximum amount of Pokemon subscriptions a user can set, set as 0 for no limit. - "maxPokemonSubscriptions": 0, - // Maximum amount of PvP subscriptions a user can set, set as 0 for no limit. - "maxPvPSubscriptions": 0, - // Maximum amount of Raid subscriptions a user can set, set as 0 for no limit. - "maxRaidSubscriptions": 0, - // Maximum amount of Quest subscriptions a user can set, set as 0 for no limit. - "maxQuestSubscriptions": 0, - // Maximum amount of Invasion subscriptions a user can set, set as 0 for no limit. - "maxInvasionSubscriptions": 0, - // Maximum amount of Gym subscriptions a user can set, set as 0 for no limit. - "maxGymSubscriptions": 0 - }, - "enableCities": false, - "cityRoles": [ - "City3", - "City4" - ], - "citiesRequireSupporterRole": true, - "pruneQuestChannels": true, - "questChannelIds": [ - 000000000000000000 - ], - "nestsChannelId": 000000000000000000, - "nestsMinimumPerHour": 2, - "shinyStats": { - "enabled": true, - "clearMessages": false, - "channelId": 000000000000000000 - }, - "iconStyle": "Default", - "botChannelIds": [ - 000000000000000000 - ], - "status": null - } + // etc ... + } }, // Database configuration "database": { @@ -250,12 +251,12 @@ bitsadmin /transfer dotnet-install-job /download /priority FOREGROUND https://ra 456, 320 ], - // Minimum IV value for an event Pokemon to have to meet in order to post via Discord channel alarm or direct message subscription. + // Minimum IV value for an event Pokemon to have to meet in order to post via Discord channel alarm or direct message subscription. "eventMinimumIV": "90", // Image URL config "urls": { - // Static tile map images template. - "staticMap": "http://tiles.example.com:8080/static/klokantech-basic/{0}/{1}/15/300/175/1/png", + // Static map tileserver endpoint. + "staticMap": "http://tiles.example.com:8080", // Scanner map DTS option for embeds as `scanmaps_url` "scannerMap": "http://map.example.com/@/{0}/{1}/15" }, @@ -268,21 +269,21 @@ bitsadmin /transfer dotnet-install-job /download /priority FOREGROUND https://ra // Custom static map template files for each alarm type "staticMaps": { // Static map template for Pokemon - "pokemon": "pokemon.example.json", + "pokemon": "pokemon.example", // Static map template for Raids and Eggs - "raids": "raids.example.json", + "raids": "raids.example", // Static map template for field research quests - "quests": "quests.example.json", + "quests": "quests.example", // Static map template for Team Rocket invasions - "invasions": "invasions.example.json", + "invasions": "invasions.example", // Static map template for Pokestop lures - "lures": "lures.example.json", + "lures": "lures.example", // Static map template for Gym team control changes - "gyms": "gyms.example.json", + "gyms": "gyms.example", // Static map template for nest postings - "nests": "nests.example.json", + "nests": "nests.example", // Static map template for weather changes - "weather": "weather.example.json" + "weather": "weather.example" }, // Get text message alerts with Twilio.com "twilio": { @@ -305,6 +306,10 @@ bitsadmin /transfer dotnet-install-job /download /priority FOREGROUND https://ra "gmapsKey": "", // Minimum despawn time in minutes a Pokemon must have in order to send the alarm (default: 5 minutes) "despawnTimeMinimumMinutes": 5, + // Reload subscriptions every minute to sync with WhMgr-UI changes + "reloadSubscriptionChangesMinutes": 1, + // Maximum amount of notifications a user can receive per minute before being rate limited + "maxNotificationsPerMinute": 10, // Log webhook payloads to a file for debugging (do not enable unless you're having issues receiving data "debug": false, // Only show logs with higher or equal priority levels (Trace, Debug, Info, Warning, Error, Fatal, None) @@ -448,7 +453,7 @@ __GeoJSON Format__ "stroke-opacity": 1.0, "fill": "#0651FF", "fill-opacity": 0.5, - "priority": 2, + "priority": 2, } } ] diff --git a/config.example.json b/config.example.json index 8229e3b8..242017e0 100644 --- a/config.example.json +++ b/config.example.json @@ -24,6 +24,7 @@ "maxRaidSubscriptions": 0, "maxQuestSubscriptions": 0, "maxInvasionSubscriptions": 0, + "maxLureSubscriptions": 0, "maxGymSubscriptions": 0 }, "enableCities": false, @@ -109,6 +110,7 @@ "maxRaidSubscriptions": 0, "maxQuestSubscriptions": 0, "maxInvasionSubscriptions": 0, + "maxLureSubscriptions": 0, "maxGymSubscriptions": 0 }, "enableCities": false, diff --git a/docs/commands/subscriptions.md b/docs/commands/subscriptions.md index 7d7c54cb..e6af8860 100644 --- a/docs/commands/subscriptions.md +++ b/docs/commands/subscriptions.md @@ -158,7 +158,7 @@ Examples: **invme** - Subscribe to specific Team Rocket invasion notifications. Usage: `invme [city]` -* `` - Reward Pokemon i.e. `Dratini`, `147` +* `` - Reward Pokemon i.e. `Dratini`, `147` * `[city]` - (Optional) City name to get the notifications for or leave blank for all available cities. Examples: @@ -180,6 +180,38 @@ Examples: * `.invmenot all`
+### Pokestop Lures + +**lureme** - Subscribe to specific Pokestop lure type notifications. +Usage: `lureme [city]` + +* `` - Pokestop lure type i.e. `All`, `Normal`, `Glacial`, `Mossy`, `Magnetic`, `501`, `504` +* `[city]` - (Optional) City name to get the notifications for or leave blank for all available cities. + +Examples: + +* `.lureme` +* `.lureme all all` +* `.lureme all city1` +* `.lureme norm city1` +* `.lureme glacial,501,moss city1,city2` +
+ +**luremenot** - Unsubscribe from specific Pokestop lure type notifications. +Usage: `luremenot [city]` + +* `` - Pokestop lure type i.e. `Normal`, `Glacial`, `Mossy`, `Magnetic`, `501`, `504` +* `[city]` - (Optional) City name to get the notifications for or leave blank for all available cities. + +Examples: + +* `.luremenot` +* `.luremenot all all` +* `.luremenot all city1` +* `.luremenot norm city1` +* `.luremenot glacial,501,moss city1,city2` +
+ ### Management **import** - Import saved subscriptions file. diff --git a/docs/user-guide/config.md b/docs/user-guide/config.md index 2b5ecf69..d03bb899 100644 --- a/docs/user-guide/config.md +++ b/docs/user-guide/config.md @@ -84,11 +84,33 @@ __**Image Urls**__ `urls` "token": "", // Alarms file path. "alarms": "alarms.json", - // Enable custom direct message notification subscriptions. - "enableSubscriptions": false, + // Geofences related to the Discord guild. **NOT** used for subscriptions. + "geofences": [ + "City1.txt", + "City2.json" + ], + // Custom user subscriptions + "subscriptions": { + // Enable or disable custom direct message notification subscriptions per user. + "enabled": false, + // Maximum amount of Pokemon subscriptions a user can set, set as 0 for no limit. + "maxPokemonSubscriptions": 0, + // Maximum amount of PvP subscriptions a user can set, set as 0 for no limit. + "maxPvPSubscriptions": 0, + // Maximum amount of Raid subscriptions a user can set, set as 0 for no limit. + "maxRaidSubscriptions": 0, + // Maximum amount of Quest subscriptions a user can set, set as 0 for no limit. + "maxQuestSubscriptions": 0, + // Maximum amount of Invasion subscriptions a user can set, set as 0 for no limit. + "maxInvasionSubscriptions": 0, + // Maximum amount of Gym subscriptions a user can set, set as 0 for no limit. + "maxGymSubscriptions": 0, + // Maximum amount of Lure subscriptions a user can set, set as 0 for no limit. + "maxLureSubscriptions": 0 + }, // Enable city role assignments. "enableCities": false, - // City/geofence role(s) + // Cities used to assign roles (if enabled), **NOT** used or related to geofences or subscriptions. "cityRoles": [ "City1", "City2" @@ -121,44 +143,55 @@ __**Image Urls**__ `urls` 000000000000000000 ], // Custom Discord status per server, leave blank or null to use current version. - "status": "" + "status": "", + "dmAlertsFile": "default.json", + "embedColors": { + "pokemon": { + "iv": [ + { "min": 0, "max": 0, "color": "#ffffff" }, + { "min": 1, "max": 89, "color": "#ffff00" }, + { "min": 90, "max": 99, "color": "#ffa500" }, + { "min": 100, "max": 100, "color": "#00ff00" } + ], + "pvp": [ + { "min": 1, "max": 1, "color": "#000080" }, + { "min": 6, "max": 25, "color": "#800080" }, + { "min": 25, "max": 100, "color": "#aa2299" } + ] + }, + "raids": { + "1": "#ff69b4", + "2": "#ff69b4", + "3": "#ffff00", + "4": "#ffff00", + "5": "#800080", + "6": "#a52a2a", + "ex": "#2c2f33" + }, + "pokestops": { + "quests": "#ffa500", + "lures": { + "normal": "#ff69b4", + "glacial": "#6495ed", + "mossy": "#507d2a", + "magnetic": "#808080" + }, + "invasions": "#ff0000" + }, + "weather": { + "clear": "#ffff00", + "cloudy": "#99aab5", + "fog": "#9a9a9a", + "partlyCloudy": "#808080", + "rain": "#0000ff", + "snow": "#ffffff", + "windy": "#800080" + } + } }, "000000000000000002": { - "commandPrefix": ".", - "emojiGuildId": 000000000000000001, - "ownerId": 000000000000000000, - "donorRoleIds": [ - 000000000000000000 - ], - "moderatorIds": [ - 000000000000000000 - ], - "token": "", - "alarms": "alarms2.json", - "enableSubscriptions": false, - "enableCities": false, - "cityRoles": [ - "City3", - "City4" - ], - "citiesRequireSupporterRole": true, - "pruneQuestChannels": true, - "questChannelIds": [ - 000000000000000000 - ], - "nestsChannelId": 000000000000000000, - "nestsMinimumPerHour": 2, - "shinyStats": { - "enabled": true, - "clearMessages": false, - "channelId": 000000000000000000 - }, - "iconStyle": "Default", - "botChannelIds": [ - 000000000000000000 - ], - "status": null - } + // etc ... + } }, // Database configuration "database": { @@ -208,12 +241,12 @@ __**Image Urls**__ `urls` 456, 320 ], - // Minimum IV value for an event Pokemon to have to meet in order to post via Discord channel alarm or direct message subscription. + // Minimum IV value for an event Pokemon to have to meet in order to post via Discord channel alarm or direct message subscription. "eventMinimumIV": "90", // Image URL config "urls": { - // Static tile map images template. - "staticMap": "http://tiles.example.com:8080/static/klokantech-basic/{0}/{1}/15/300/175/1/png", + // Static map tileserver endpoint. + "staticMap": "http://tiles.example.com:8080", // Scanner map DTS option for embeds as `scanmaps_url` "scannerMap": "http://map.example.com/@/{0}/{1}/15" }, @@ -226,21 +259,21 @@ __**Image Urls**__ `urls` // Custom static map template files for each alarm type "staticMaps": { // Static map template for Pokemon - "pokemon": "pokemon.example.json", + "pokemon": "pokemon.example", // Static map template for Raids and Eggs - "raids": "raids.example.json", + "raids": "raids.example", // Static map template for field research quests - "quests": "quests.example.json", + "quests": "quests.example", // Static map template for Team Rocket invasions - "invasions": "invasions.example.json", + "invasions": "invasions.example", // Static map template for Pokestop lures - "lures": "lures.example.json", + "lures": "lures.example", // Static map template for Gym team control changes - "gyms": "gyms.example.json", + "gyms": "gyms.example", // Static map template for nest postings - "nests": "nests.example.json", + "nests": "nests.example", // Static map template for weather changes - "weather": "weather.example.json" + "weather": "weather.example" }, // Get text message alerts with Twilio.com "twilio": { @@ -263,7 +296,11 @@ __**Image Urls**__ `urls` "gmapsKey": "", // Minimum despawn time in minutes a Pokemon must have in order to send the alarm (default: 5 minutes) "despawnTimeMinimumMinutes": 5, - // Log webhook payloads to a file for debugging + // Reload subscriptions every minute to sync with WhMgr-UI changes + "reloadSubscriptionChangesMinutes": 1, + // Maximum amount of notifications a user can receive per minute before being rate limited + "maxNotificationsPerMinute": 10, + // Log webhook payloads to a file for debugging (do not enable unless you're having issues receiving data "debug": false, // Only show logs with higher or equal priority levels (Trace, Debug, Info, Warning, Error, Fatal, None) "logLevel": "Trace" @@ -305,8 +342,8 @@ __**Image Urls**__ `urls` //Alarm filters. "filters":"default.json", - //Path to geofence file. - "geofence":"geofence1.txt", + //Path to geofence file(s) or geofence name(s) to use. + "geofences": ["geofence1.txt", "geofence2.json", "city1"], //Discord webhook url address. "webhook":"" @@ -323,8 +360,8 @@ __**Image Urls**__ `urls` //Alarm filters. "filters":"100iv.json", - //Path to geofence file. - "geofence":"geofence1.txt", + //Path to geofence file(s) or geofence name(s) to use. + "geofences": ["geofence3.txt", "geofence4.json", "city2"], //Discord webhook url address. "webhook":"" diff --git a/migrations/4.sql b/migrations/4.sql new file mode 100644 index 00000000..1919a61d --- /dev/null +++ b/migrations/4.sql @@ -0,0 +1,56 @@ +CREATE TABLE `lures` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `subscription_id` int(11) NOT NULL DEFAULT 0, + `guild_id` bigint(20) DEFAULT NULL, + `user_id` bigint(20) DEFAULT NULL, + `lure_type` varchar(20) NOT NULL, + `city` text DEFAULT '[]', + PRIMARY KEY (`id`), + KEY `FK_lure_subscriptions_subscription_id` (`subscription_id`), + CONSTRAINT `FK_lure_subscriptions_subscription_id` FOREIGN KEY (`subscription_id`) REFERENCES `subscriptions` (`id`) +); + + +ALTER TABLE subscriptions ADD KEY ix_server (guild_id, user_id); +ALTER TABLE subscriptions ADD KEY ix_enabled (enabled); + +ALTER TABLE pokemon MODIFY COLUMN pokemon_id smallint(5) unsigned NOT NULL, +ALTER TABLE pokemon MODIFY COLUMN iv_list text DEFAULT '[]'; +ALTER TABLE pokemon MODIFY COLUMN city text DEFAULT '[]'; +ALTER TABLE pokemon ADD KEY ix_server (guild_id, user_id); +ALTER TABLE pokemon ADD KEY ix_pokemon_id (pokemon_id); +ALTER TABLE pokemon ADD KEY ix_form (form); +ALTER TABLE pokemon ADD KEY ix_city (city); + +ALTER TABLE pvp MODIFY COLUMN pokemon_id smallint(5) unsigned NOT NULL, +ALTER TABLE pvp MODIFY COLUMN city text DEFAULT '[]'; +ALTER TABLE pvp ADD KEY ix_server (guild_id, user_id); +ALTER TABLE pvp ADD KEY ix_pokemon_id (pokemon_id); +ALTER TABLE pvp ADD KEY ix_form (form); +ALTER TABLE pvp ADD KEY ix_city (city); + +ALTER TABLE raids MODIFY COLUMN pokemon_id smallint(5) unsigned NOT NULL, +ALTER TABLE raids MODIFY COLUMN city text DEFAULT '[]'; +ALTER TABLE raids ADD KEY ix_server (guild_id, user_id); +ALTER TABLE raids ADD KEY ix_pokemon_id (pokemon_id); +ALTER TABLE raids ADD KEY ix_form (form); +ALTER TABLE raids ADD KEY ix_city (city); + +ALTER TABLE quests MODIFY COLUMN city text DEFAULT '[]'; +ALTER TABLE quests ADD KEY ix_server (guild_id, user_id); +ALTER TABLE quests ADD KEY ix_reward (reward); +ALTER TABLE quests ADD KEY ix_city (city); + +ALTER TABLE invasions MODIFY COLUMN city text DEFAULT '[]'; +ALTER TABLE invasions MODIFY COLUMN reward_pokemon_id smallint(5) unsigned NOT NULL, +ALTER TABLE invasions ADD KEY ix_server (guild_id, user_id); +ALTER TABLE invasions ADD KEY ix_reward_pokemon_id (reward_pokemon_id); +ALTER TABLE invasions ADD KEY ix_city (city); + +ALTER TABLE gyms ADD KEY ix_server (guild_id, user_id); +ALTER TABLE gyms ADD KEY ix_name (name); + + +UPDATE subscriptions SET latitude=0 WHERE latitude IS NULL; +UPDATE subscriptions SET longitude=0 WHERE longitude IS NULL; +UPDATE subscriptions SET phone_number=NULL WHERE phone_number=''; \ No newline at end of file diff --git a/src/Bot.cs b/src/Bot.cs index 2ecac6e2..9b8edb0b 100644 --- a/src/Bot.cs +++ b/src/Bot.cs @@ -232,13 +232,14 @@ public async Task Start() _whm.GymDetailsAlarmTriggered += OnGymDetailsAlarmTriggered; _whm.WeatherAlarmTriggered += OnWeatherAlarmTriggered; // At least one server wants subscriptions - if (_whConfig.Instance.Servers.FirstOrDefault(x => x.Value.Subscriptions.Enabled).Value != null) + if (_whConfig.Instance.Servers.Any(x => x.Value.Subscriptions.Enabled)) { // Register subscription event handlers _whm.PokemonSubscriptionTriggered += OnPokemonSubscriptionTriggered; _whm.RaidSubscriptionTriggered += OnRaidSubscriptionTriggered; _whm.QuestSubscriptionTriggered += OnQuestSubscriptionTriggered; _whm.InvasionSubscriptionTriggered += OnInvasionSubscriptionTriggered; + _whm.LureSubscriptionTriggered += OnLureSubscriptionTriggered; } _whm.Start(); @@ -270,13 +271,14 @@ public async Task Stop() _whm.GymAlarmTriggered -= OnGymAlarmTriggered; _whm.GymDetailsAlarmTriggered -= OnGymDetailsAlarmTriggered; _whm.WeatherAlarmTriggered -= OnWeatherAlarmTriggered; - if (_whConfig.Instance.Servers.FirstOrDefault(x => x.Value.Subscriptions.Enabled).Value != null) + if (_whConfig.Instance.Servers.Any(x => x.Value.Subscriptions.Enabled)) { //At least one server wanted subscriptions, unregister the subscription event handlers _whm.PokemonSubscriptionTriggered -= OnPokemonSubscriptionTriggered; _whm.RaidSubscriptionTriggered -= OnRaidSubscriptionTriggered; _whm.QuestSubscriptionTriggered -= OnQuestSubscriptionTriggered; _whm.InvasionSubscriptionTriggered -= OnInvasionSubscriptionTriggered; + _whm.LureSubscriptionTriggered -= OnLureSubscriptionTriggered; } _whm.Stop(); @@ -533,7 +535,7 @@ private void OnPokemonAlarmTriggered(object sender, AlarmEventTriggeredEventArgs if (pokemon.IV == "100%") { - Statistics.Instance.Add100Percent(pokemon); + Statistics.Instance.AddHundredIV(pokemon); } } catch (Exception ex) @@ -652,7 +654,7 @@ private void OnPokestopAlarmTriggered(object sender, AlarmEventTriggeredEventArg try { var client = _servers[e.GuildId]; - var eb = pokestop.GeneratePokestopMessage(e.GuildId, client, _whConfig.Instance, e.Alarm, loc?.Name ?? e.Alarm.Name); + var eb = pokestop.GeneratePokestopMessage(e.GuildId, client, _whConfig.Instance, e.Alarm, loc?.Name ?? e.Alarm.Name, pokestop.HasLure, pokestop.HasInvasion); var jsonEmbed = new DiscordWebhookMessage { Username = eb.Username ?? Translator.Instance.Translate("UNKNOWN_POKESTOP"), @@ -836,6 +838,18 @@ private void OnInvasionSubscriptionTriggered(object sender, PokestopData e) } } + private void OnLureSubscriptionTriggered(object sender, PokestopData e) + { + if (_subProcessor == null) + return; + + if (!ThreadPool.QueueUserWorkItem(async x => await _subProcessor.ProcessLureSubscription(e))) + { + // Failed to queue thread + _logger.Error($"Failed to queue thread to process lure subscription"); + } + } + #endregion #region Private Methods diff --git a/src/Commands/Notifications.cs b/src/Commands/Notifications.cs index 787f629b..6e6e0643 100644 --- a/src/Commands/Notifications.cs +++ b/src/Commands/Notifications.cs @@ -23,6 +23,7 @@ namespace WhMgr.Commands using WhMgr.Diagnostics; using WhMgr.Extensions; using WhMgr.Localization; + using WhMgr.Net.Models; using WhMgr.Utilities; public class Notifications @@ -1430,6 +1431,161 @@ public async Task PvpMeNotAsync(CommandContext ctx, #endregion + #region Lureme / Luremenot + + [ + Command("lureme"), + Description("Subscribe to Pokestop lure notifications based on the lure type.") + ] + public async Task LureMeAsync(CommandContext ctx, + [Description("Comma delimited list of Pokestop lures to subscribe to notifications.")] string lureTypes = "all", + [Description("City to send the notification if the lure appears in otherwise if null all will be sent."), RemainingText] string city = "all") + { + if (!await CanExecute(ctx)) + return; + + var guildId = ctx.Guild?.Id ?? ctx.Client.Guilds.Keys.FirstOrDefault(x => _dep.WhConfig.Servers.ContainsKey(x)); + if (!_dep.WhConfig.Servers.ContainsKey(guildId)) + return; + + var server = _dep.WhConfig.Servers[guildId]; + var subscription = _dep.SubscriptionProcessor.Manager.GetUserSubscriptions(guildId, ctx.User.Id); + // Check subscription limits + if (server.Subscriptions.MaxLureSubscriptions > 0 && subscription.Lures.Count >= server.Subscriptions.MaxLureSubscriptions) + { + // Max limit for Lure subscriptions reached + await ctx.RespondEmbed(Translator.Instance.Translate("NOTIFY_INVALID_LURE_SUBSCRIPTIONS_LIMIT", ctx.User.Username, server.Subscriptions.MaxLureSubscriptions), DiscordColor.Red); + return; + } + + var areas = SubscriptionAreas.GetAreas(server, city); + var lures = GetLures(lureTypes); + foreach (var lureType in lures) + { + var subLure = subscription.Lures.FirstOrDefault(x => x.LureType == lureType); + if (subLure != null) + { + // Existing lure subscription + // Loop all areas, check if the area is already in subs, if not add it + foreach (var area in areas) + { + if (!subLure.Areas.Select(x => x.ToLower()).Contains(area.ToLower())) + { + subLure.Areas.Add(area); + } + } + // Save quest subscription and continue; + // REVIEW: Might not be needed + subLure.Save(); + } + else + { + // New lure subscription + subscription.Lures.Add(new LureSubscription + { + GuildId = guildId, + UserId = ctx.User.Id, + LureType = lureType, + Areas = areas + }); + } + } + subscription.Save(); + + await ctx.RespondEmbed(Translator.Instance.Translate("SUCCESS_LURE_SUBSCRIPTIONS_SUBSCRIBE").FormatText( + ctx.User.Username, + string.Compare(lureTypes, Strings.All, true) == 0 ? Strings.All : string.Join(", ", lures), + string.IsNullOrEmpty(city) + ? Translator.Instance.Translate("SUBSCRIPTIONS_FROM_ALL_CITIES") + : Translator.Instance.Translate("SUBSCRIPTIONS_FROM_CITY").FormatText(city)) + ); + _dep.SubscriptionProcessor.Manager.ReloadSubscriptions(); + } + + [ + Command("luremenot"), + Description("Unsubscribe from one or all subscribed Pokestop lure notifications by lure type.") + ] + public async Task LureMeNotAsync(CommandContext ctx, + [Description("Comma delimited list of Pokestop lures to unsubscribe from notifications.")] string lureTypes = "all", + [Description("City to send the notification if the raid appears in otherwise if null all will be sent."), RemainingText] string city = "all") + { + if (!await CanExecute(ctx)) + return; + + var guildId = ctx.Guild?.Id ?? ctx.Client.Guilds.Keys.FirstOrDefault(x => _dep.WhConfig.Servers.ContainsKey(x)); + var subscription = _dep.SubscriptionProcessor.Manager.GetUserSubscriptions(guildId, ctx.User.Id); + if (subscription == null || subscription?.Lures.Count == 0) + { + await ctx.RespondEmbed(Translator.Instance.Translate("ERROR_NO_LURE_SUBSCRIPTIONS").FormatText(ctx.User.Username, string.IsNullOrEmpty(city) + ? Translator.Instance.Translate("SUBSCRIPTIONS_FROM_ALL_CITIES") + : Translator.Instance.Translate("SUBSCRIPTIONS_FROM_CITY").FormatText(city)), + DiscordColor.Red + ); + return; + } + + if (string.Compare(lureTypes, Strings.All, true) == 0) + { + var result = await ctx.Confirm(Translator.Instance.Translate("NOTIFY_CONFIRM_REMOVE_ALL_LURE_SUBSCRIPTIONS").FormatText(ctx.User.Username, subscription.Lures.Count.ToString("N0"))); + if (!result) + return; + + subscription.Lures.ForEach(x => x.Id.Remove()); + + await ctx.RespondEmbed(Translator.Instance.Translate("NOTIFY_SUCCESS_REMOVE_ALL_LURE_SUBSCRIPTIONS").FormatText(ctx.User.Username)); + _dep.SubscriptionProcessor.Manager.ReloadSubscriptions(); + return; + } + + var areas = SubscriptionAreas.GetAreas(_dep.WhConfig.Servers[guildId], city); + var lures = GetLures(lureTypes); + foreach (var lureType in lures) + { + var subLure = subscription.Lures.FirstOrDefault(x => x.LureType == lureType); + // Check if subscribed + if (subLure == null) + return; + + foreach (var area in areas) + { + if (subLure.Areas.Select(x => x.ToLower()).Contains(area.ToLower())) + { + var index = subLure.Areas.FindIndex(x => string.Compare(x, area, true) == 0); + subLure.Areas.RemoveAt(index); + } + } + + // Check if there are no more areas set for the lure subscription + if (subLure.Areas.Count == 0) + { + // If no more areas set for the lure subscription, delete it + if (!subLure.Id.Remove()) + { + _logger.Error($"Unable to remove lure subscription for user id {subLure.UserId} from guild id {subLure.GuildId}"); + } + } + else + { + // Save/update lure subscription if cities still assigned + subLure.Save(); + } + } + subscription.Save(); + + await ctx.RespondEmbed(Translator.Instance.Translate("SUCCESS_LURE_SUBSCRIPTIONS_UNSUBSCRIBE").FormatText( + ctx.User.Username, + string.Compare(lureTypes, Strings.All, true) == 0 ? Strings.All : string.Join(", ", lures), + string.IsNullOrEmpty(city) + ? Translator.Instance.Translate("SUBSCRIPTIONS_FROM_ALL_CITIES") + : Translator.Instance.Translate("SUBSCRIPTIONS_FROM_CITY").FormatText(city)) + ); + + _dep.SubscriptionProcessor.Manager.ReloadSubscriptions(); + } + + #endregion + #region Add / Remove [ @@ -2663,13 +2819,14 @@ private async Task> BuildUserSubscriptionSettings(DiscordClient cli var server = _dep.WhConfig.Servers[guildId]; var subscription = _dep.SubscriptionProcessor.Manager.GetUserSubscriptions(guildId, user.Id); - var isSubbed = subscription?.Pokemon.Count > 0 || subscription?.PvP.Count > 0 || subscription?.Raids.Count > 0 || subscription?.Quests.Count > 0 || subscription?.Invasions.Count > 0 || subscription?.Gyms.Count > 0; - var hasPokemon = isSubbed && subscription?.Pokemon.Count > 0; - var hasPvP = isSubbed && subscription?.PvP.Count > 0; - var hasRaids = isSubbed && subscription?.Raids.Count > 0; - var hasGyms = isSubbed && subscription?.Gyms.Count > 0; - var hasQuests = isSubbed && subscription?.Quests.Count > 0; - var hasInvasions = isSubbed && subscription?.Invasions.Count > 0; + var isSubbed = subscription?.Pokemon.Count > 0 || subscription?.PvP.Count > 0 || subscription?.Raids.Count > 0 || subscription?.Quests.Count > 0 || subscription?.Invasions.Count > 0 || subscription?.Gyms.Count > 0 || subscription?.Lures.Count > 0; + var hasPokemon = isSubbed && subscription?.Pokemon?.Count > 0; + var hasPvP = isSubbed && subscription?.PvP?.Count > 0; + var hasRaids = isSubbed && subscription?.Raids?.Count > 0; + var hasGyms = isSubbed && subscription?.Gyms?.Count > 0; + var hasQuests = isSubbed && subscription?.Quests?.Count > 0; + var hasInvasions = isSubbed && subscription?.Invasions?.Count > 0; + var hasLures = isSubbed && subscription?.Lures?.Count > 0; var messages = new List(); var isSupporter = await client.IsSupporterOrHigher(user.Id, guildId, _dep.WhConfig); @@ -2798,6 +2955,18 @@ private async Task> BuildUserSubscriptionSettings(DiscordClient cli invasionsBuilder.AppendLine(); messages.Add(invasionsBuilder.ToString()); } + + if (hasLures) + { + var luresBuilder = new StringBuilder(); + luresBuilder.AppendLine(Translator.Instance.Translate("NOTIFY_SETTINGS_EMBED_LURES").FormatText(subscription.Lures.Count.ToString("N0"), server.Subscriptions.MaxLureSubscriptions == 0 ? "∞" : server.Subscriptions.MaxLureSubscriptions.ToString("N0"))); + luresBuilder.Append("```"); + luresBuilder.Append(string.Join(Environment.NewLine, GetLureSubscriptionNames(guildId, user.Id))); + luresBuilder.Append("```"); + luresBuilder.AppendLine(); + luresBuilder.AppendLine(); + messages.Add(luresBuilder.ToString()); + } return messages; } @@ -2920,6 +3089,23 @@ private List GetInvasionSubscriptionNames(ulong guildId, ulong userId) return list; } + private List GetLureSubscriptionNames(ulong guildId, ulong userId) + { + var list = new List(); + var subscription = _dep.SubscriptionProcessor.Manager.GetUserSubscriptions(guildId, userId); + var subscribedLures = subscription.Lures; + subscribedLures.Sort((x, y) => x.LureType.CompareTo(y.LureType)); + var cityRoles = _dep.WhConfig.Servers[guildId].CityRoles.Select(x => x.ToLower()); + + foreach (var lure in subscribedLures) + { + var isAllCities = cityRoles.ScrambledEquals(lure.Areas, StringComparer.Create(System.Globalization.CultureInfo.CurrentCulture, true)); + list.Add(Translator.Instance.Translate("NOTIFY_FROM").FormatText(lure.LureType, isAllCities ? Translator.Instance.Translate("ALL_AREAS") : string.Join(", ", lure.Areas))); + } + + return list; + } + private DiscordEmbedBuilder BuildExpirationMessage(ulong guildId, DiscordUser user) { var customerData = _dep.Stripe.GetCustomerData(guildId, user.Id); @@ -2973,6 +3159,28 @@ private async Task CanExecute(CommandContext ctx) return true; } + private static PokestopLureType GetLureFromName(string lureName) + { + lureName = lureName.ToLower(); + if (lureName.Contains("501") || lureName.Contains("norm")) + return PokestopLureType.Normal; + else if (lureName.Contains("502") || lureName.Contains("glac")) + return PokestopLureType.Glacial; + else if (lureName.Contains("503") || lureName.Contains("mos")) + return PokestopLureType.Mossy; + else if (lureName.Contains("504") || lureName.Contains("mag")) + return PokestopLureType.Magnetic; + return PokestopLureType.None; + } + + private static List GetLures(string lureTypes) + { + var lureNames = lureTypes.Replace(", ", ",").Replace(" ,", ",").Split(',').ToList(); + var list = new List(); + lureNames.ForEach(x => list.Add(GetLureFromName(x))); + return list; + } + #endregion } diff --git a/src/Configuration/SubscriptionsConfig.cs b/src/Configuration/SubscriptionsConfig.cs index 0f131cfa..7dc1dc2d 100644 --- a/src/Configuration/SubscriptionsConfig.cs +++ b/src/Configuration/SubscriptionsConfig.cs @@ -22,6 +22,9 @@ public class SubscriptionsConfig [JsonProperty("maxInvasionSubscriptions")] public int MaxInvasionSubscriptions { get; set; } + [JsonProperty("maxLureSubscriptions")] + public int MaxLureSubscriptions { get; set; } + [JsonProperty("maxGymSubscriptions")] public int MaxGymSubscriptions { get; set; } @@ -36,6 +39,7 @@ public SubscriptionsConfig() MaxRaidSubscriptions = 0; MaxQuestSubscriptions = 0; MaxInvasionSubscriptions = 0; + MaxLureSubscriptions = 0; MaxGymSubscriptions = 0; MaxNotificationsPerMinute = 15; } diff --git a/src/Data/Subscriptions/Models/LureSubscription.cs b/src/Data/Subscriptions/Models/LureSubscription.cs new file mode 100644 index 00000000..ef2060a4 --- /dev/null +++ b/src/Data/Subscriptions/Models/LureSubscription.cs @@ -0,0 +1,43 @@ +namespace WhMgr.Data.Subscriptions.Models +{ + using System; + using System.Collections.Generic; + + using Newtonsoft.Json; + using ServiceStack.DataAnnotations; + + using WhMgr.Net.Models; + + [ + JsonObject("lures"), + Alias("lures") + ] + public class LureSubscription : SubscriptionItem + { + [ + Alias("subscription_id"), + ForeignKey(typeof(SubscriptionObject)) + ] + public int SubscriptionId { get; set; } + + [ + JsonProperty("lure_type"), + Alias("lure_type"), + Required + ] + public PokestopLureType LureType { get; set; } + + [ + JsonProperty("city"), + Alias("city"), + Required + ] + public List Areas { get; set; } + + public LureSubscription() + { + LureType = PokestopLureType.None; + Areas = new List(); + } + } +} \ No newline at end of file diff --git a/src/Data/Subscriptions/Models/SubscriptionObject.cs b/src/Data/Subscriptions/Models/SubscriptionObject.cs index f9ee4bcf..91119d22 100644 --- a/src/Data/Subscriptions/Models/SubscriptionObject.cs +++ b/src/Data/Subscriptions/Models/SubscriptionObject.cs @@ -89,6 +89,13 @@ public class SubscriptionObject : SubscriptionItem /// Gets or sets the distance in meters a subscription should be within /// to trigger /// + [ + JsonProperty("lures"), + Alias("lures"), + Reference + ] + public List Lures { get; set; } + [ JsonProperty("distance"), Alias("distance"), @@ -166,6 +173,7 @@ public SubscriptionObject() Gyms = new List(); Quests = new List(); Invasions = new List(); + Lures = new List(); Limiter = new NotificationLimiter(); DistanceM = 0; Latitude = 0; diff --git a/src/Data/Subscriptions/SubscriptionManager.cs b/src/Data/Subscriptions/SubscriptionManager.cs index 0565d51c..54368a5f 100644 --- a/src/Data/Subscriptions/SubscriptionManager.cs +++ b/src/Data/Subscriptions/SubscriptionManager.cs @@ -10,6 +10,7 @@ using WhMgr.Configuration; using WhMgr.Data.Subscriptions.Models; using WhMgr.Diagnostics; + using WhMgr.Net.Models; /// /// User subscription manager class @@ -22,10 +23,7 @@ public class SubscriptionManager private readonly WhConfigHolder _whConfig; private List _subscriptions; - private readonly OrmLiteConnectionFactory _connFactory; - //private readonly OrmLiteConnectionFactory _scanConnFactory; - private readonly Timer _reloadTimer; #endregion @@ -61,13 +59,11 @@ public SubscriptionManager(WhConfigHolder whConfig) throw new NullReferenceException(err); } - _connFactory = new OrmLiteConnectionFactory(_whConfig.Instance.Database.Main.ToString(), MySqlDialect.Provider); - - if (_whConfig.Instance.Database?.Nests == null) + if (_whConfig.Instance?.Database?.Nests == null) { _logger.Warn("Nest database is not configured in config.json file, nest alarms and commands will not work."); } - + _connFactory = new OrmLiteConnectionFactory(_whConfig.Instance.Database.Main.ToString(), MySqlDialect.Provider); // Reload subscriptions every 60 seconds to account for UI changes @@ -187,6 +183,20 @@ public List GetUserSubscriptionsByEncounterReward(List .ToList(); } + /// + /// Gets user subscriptions from subscribed Pokestop lures + /// + /// Pokestop lure type + /// Returns list of user subscription objects + public List GetUserSubscriptionsByLureType(PokestopLureType lureType) + { + return _subscriptions? + .Where(x => x.Enabled && + x.Lures != null && + x.Lures.Exists(y => lureType == y.LureType)) + .ToList(); + } + /// /// Get all enabled user subscriptions /// @@ -261,6 +271,7 @@ public static bool RemoveAllUserSubscriptions(ulong guildId, ulong userId) conn.Delete(x => x.GuildId == guildId && x.UserId == userId); conn.Delete(x => x.GuildId == guildId && x.UserId == userId); conn.Delete(x => x.GuildId == guildId && x.UserId == userId); + conn.Delete(x => x.GuildId == guildId && x.UserId == userId); conn.Delete(x => x.GuildId == guildId && x.UserId == userId); } diff --git a/src/Data/Subscriptions/SubscriptionProcessor.cs b/src/Data/Subscriptions/SubscriptionProcessor.cs index 2c72cd47..3607b52a 100644 --- a/src/Data/Subscriptions/SubscriptionProcessor.cs +++ b/src/Data/Subscriptions/SubscriptionProcessor.cs @@ -109,6 +109,7 @@ GeofenceItem GetGeofence(ulong guildId) var matchesIVList = false; for (var i = 0; i < subscriptions.Count; i++) { + var start = DateTime.Now; try { user = subscriptions[i]; @@ -191,10 +192,9 @@ GeofenceItem GetGeofence(ulong guildId) continue; var embed = pkmn.GeneratePokemonMessage(user.GuildId, client, _whConfig.Instance, null, geofence.Name); - foreach (var emb in embed.Embeds) - { - _queue.Enqueue(new NotificationItem(user, member, emb, pokemon.Name, geofence.Name, pkmn)); - } + var end = DateTime.Now.Subtract(start); + _logger.Debug($"Took {end} to process Pokemon subscription for user {user.UserId}"); + embed.Embeds.ForEach(x => _queue.Enqueue(new NotificationItem(user, member, x, pokemon.Name, geofence.Name, pkmn))); Statistics.Instance.SubscriptionPokemonSent++; Thread.Sleep(5); @@ -248,6 +248,7 @@ GeofenceItem GetGeofence(ulong guildId) var matchesUltra = false; for (var i = 0; i < subscriptions.Count; i++) { + var start = DateTime.Now; try { user = subscriptions[i]; @@ -331,10 +332,9 @@ GeofenceItem GetGeofence(ulong guildId) continue; var embed = pkmn.GeneratePokemonMessage(user.GuildId, client, _whConfig.Instance, null, geofence.Name); - foreach (var emb in embed.Embeds) - { - _queue.Enqueue(new NotificationItem(user, member, emb, pokemon.Name, geofence.Name)); - } + var end = DateTime.Now.Subtract(start); + _logger.Debug($"Took {end} to process PvP subscription for user {user.UserId}"); + embed.Embeds.ForEach(x => _queue.Enqueue(new NotificationItem(user, member, x, pokemon.Name, geofence.Name))); Statistics.Instance.SubscriptionPokemonSent++; Thread.Sleep(5); @@ -384,6 +384,7 @@ GeofenceItem GetGeofence(ulong guildId) var pokemon = MasterFile.GetPokemon(raid.PokemonId, raid.Form); for (int i = 0; i < subscriptions.Count; i++) { + var start = DateTime.Now; try { user = subscriptions[i]; @@ -460,10 +461,9 @@ GeofenceItem GetGeofence(ulong guildId) continue; var embed = raid.GenerateRaidMessage(user.GuildId, client, _whConfig.Instance, null, geofence.Name); - foreach (var emb in embed.Embeds) - { - _queue.Enqueue(new NotificationItem(user, member, emb, pokemon.Name, geofence.Name)); - } + var end = DateTime.Now; + _logger.Debug($"Took {end} to process raid subscription for user {user.UserId}"); + embed.Embeds.ForEach(x => _queue.Enqueue(new NotificationItem(user, member, x, pokemon.Name, geofence.Name))); Statistics.Instance.SubscriptionRaidsSent++; Thread.Sleep(5); @@ -483,7 +483,7 @@ GeofenceItem GetGeofence(ulong guildId) public async Task ProcessQuestSubscription(QuestData quest) { - var reward = quest.Rewards[0].Info; + var reward = quest.Rewards.FirstOrDefault().Info; var rewardKeyword = quest.GetReward(); var questName = quest.GetQuestMessage(); @@ -512,6 +512,7 @@ GeofenceItem GetGeofence(ulong guildId) SubscriptionObject user; for (int i = 0; i < subscriptions.Count; i++) { + var start = DateTime.Now; try { user = subscriptions[i]; @@ -572,10 +573,9 @@ GeofenceItem GetGeofence(ulong guildId) continue; var embed = quest.GenerateQuestMessage(user.GuildId, client, _whConfig.Instance, null, geofence.Name); - foreach (var emb in embed.Embeds) - { - _queue.Enqueue(new NotificationItem(user, member, emb, questName, geofence.Name)); - } + var end = DateTime.Now.Subtract(start); + _logger.Debug($"Took {end} to process quest subscription for user {user.UserId}"); + embed.Embeds.ForEach(x => _queue.Enqueue(new NotificationItem(user, member, x, questName, geofence.Name))); Statistics.Instance.SubscriptionQuestsSent++; Thread.Sleep(5); @@ -630,6 +630,7 @@ GeofenceItem GetGeofence(ulong guildId) SubscriptionObject user; for (int i = 0; i < subscriptions.Count; i++) { + var start = DateTime.Now; try { user = subscriptions[i]; @@ -688,13 +689,118 @@ GeofenceItem GetGeofence(ulong guildId) if (!distanceMatches && !geofenceMatches) continue; - var embed = pokestop.GeneratePokestopMessage(user.GuildId, client, _whConfig.Instance, null, geofence?.Name); - foreach (var emb in embed.Embeds) + var embed = pokestop.GeneratePokestopMessage(user.GuildId, client, _whConfig.Instance, null, geofence?.Name, false, true); + var end = DateTime.Now.Subtract(start); + _logger.Debug($"Took {end} to process invasion subscription for user {user.UserId}"); + embed.Embeds.ForEach(x => _queue.Enqueue(new NotificationItem(user, member, x, pokestop.Name, geofence.Name))); + + Statistics.Instance.SubscriptionInvasionsSent++; + Thread.Sleep(5); + } + catch (Exception ex) + { + _logger.Error(ex); + } + } + + subscriptions.Clear(); + subscriptions = null; + user = null; + + await Task.CompletedTask; + } + + public async Task ProcessLureSubscription(PokestopData pokestop) + { + // Cache the result per-guild so that geospatial stuff isn't queried for every single subscription below + Dictionary locationCache = new Dictionary(); + + GeofenceItem GetGeofence(ulong guildId) + { + if (!locationCache.TryGetValue(guildId, out var geofence)) + { + geofence = _whm.GetGeofence(guildId, pokestop.Latitude, pokestop.Longitude); + locationCache.Add(guildId, geofence); + } + + return geofence; + } + + var subscriptions = Manager.GetUserSubscriptionsByLureType(pokestop.LureType); + if (subscriptions == null) + { + _logger.Warn($"Failed to get subscriptions from database table."); + return; + } + + SubscriptionObject user; + for (int i = 0; i < subscriptions.Count; i++) + { + var start = DateTime.Now; + try + { + user = subscriptions[i]; + if (user == null) + continue; + + if (!user.Enabled) + continue; + + if (!_whConfig.Instance.Servers.ContainsKey(user.GuildId)) + continue; + + if (!_whConfig.Instance.Servers[user.GuildId].Subscriptions.Enabled) + continue; + + if (!_servers.ContainsKey(user.GuildId)) + continue; + + var client = _servers[user.GuildId]; + + var member = await client.GetMemberById(user.GuildId, user.UserId); + if (member == null) { - _queue.Enqueue(new NotificationItem(user, member, emb, pokestop.Name, geofence.Name)); + _logger.Warn($"Failed to find member with id {user.UserId}."); + continue; } - Statistics.Instance.SubscriptionInvasionsSent++; + if (!member.HasSupporterRole(_whConfig.Instance.Servers[user.GuildId].DonorRoleIds)) + { + _logger.Info($"User {user.UserId} is not a supporter, skipping Pokestop lure {pokestop.Name}..."); + // Automatically disable users subscriptions if not supporter to prevent issues + //user.Enabled = false; + //user.Save(false); + continue; + } + + var subLure = user.Lures.FirstOrDefault(x => x.LureType == pokestop.LureType); + // Not subscribed to lure + if (subLure == null) + { + //_logger.Debug($"Skipping notification for user {member.DisplayName} ({member.Id}) for Pokestop lure {pokemon.Name}, lure is in city '{loc.Name}'."); + continue; + } + + var geofence = GetGeofence(user.GuildId); + if (geofence == null) + { + //_logger.Warn($"Failed to lookup city from coordinates {pokestop.Latitude},{pokestop.Longitude} {pokestop.PokestopId} {pokestop.Name}, skipping..."); + continue; + } + + var distanceMatches = user.DistanceM > 0 && user.DistanceM > new Coordinates(user.Latitude, user.Longitude).DistanceTo(new Coordinates(pokestop.Latitude, pokestop.Longitude)); + var geofenceMatches = subLure.Areas.Select(x => x.ToLower()).Contains(geofence.Name.ToLower()); + + // If set distance does not match and no geofences match, then skip lure... + if (!distanceMatches && !geofenceMatches) + continue; + + var end = DateTime.Now.Subtract(start); + var embed = pokestop.GeneratePokestopMessage(user.GuildId, client, _whConfig.Instance, null, geofence.Name, true, false); + _logger.Debug($"Took {end} to process lure subscription for user {user.UserId}"); + embed.Embeds.ForEach(x => _queue.Enqueue(new NotificationItem(user, member, x, pokestop.Name, geofence.Name))); + + Statistics.Instance.SubscriptionLuresSent++; Thread.Sleep(5); } catch (Exception ex) diff --git a/src/Net/Models/PokestopData.cs b/src/Net/Models/PokestopData.cs index 915d2823..e82764de 100644 --- a/src/Net/Models/PokestopData.cs +++ b/src/Net/Models/PokestopData.cs @@ -106,12 +106,12 @@ public void SetTimes() .ConvertTimeFromCoordinates(Latitude, Longitude); } - public DiscordEmbedNotification GeneratePokestopMessage(ulong guildId, DiscordClient client, WhConfig whConfig, AlarmObject alarm, string city) + public DiscordEmbedNotification GeneratePokestopMessage(ulong guildId, DiscordClient client, WhConfig whConfig, AlarmObject alarm, string city, bool useLure, bool useInvasion) { var server = whConfig.Servers[guildId]; - var alertType = HasInvasion ? AlertMessageType.Invasions : HasLure ? AlertMessageType.Lures : AlertMessageType.Pokestops; + var alertType = useInvasion ? AlertMessageType.Invasions : useLure ? AlertMessageType.Lures : AlertMessageType.Pokestops; var alert = alarm?.Alerts[alertType] ?? server.DmAlerts?[alertType] ?? AlertMessage.Defaults[alertType]; - var properties = GetProperties(client.Guilds[guildId], whConfig, city); + var properties = GetProperties(client.Guilds[guildId], whConfig, city, useLure, useInvasion); var eb = new DiscordEmbedBuilder { Title = DynamicReplacementEngine.ReplaceText(alert.Title, properties), @@ -119,9 +119,9 @@ public DiscordEmbedNotification GeneratePokestopMessage(ulong guildId, DiscordCl ImageUrl = DynamicReplacementEngine.ReplaceText(alert.ImageUrl, properties), ThumbnailUrl = DynamicReplacementEngine.ReplaceText(alert.IconUrl, properties), Description = DynamicReplacementEngine.ReplaceText(alert.Content, properties), - Color = HasInvasion + Color = useInvasion ? new DiscordColor(server.DiscordEmbedColors.Pokestops.Invasions) - : HasLure + : useLure ? LureType.BuildLureColor(server) : DiscordColor.CornflowerBlue, Footer = new DiscordEmbedBuilder.EmbedFooter @@ -140,16 +140,16 @@ public DiscordEmbedNotification GeneratePokestopMessage(ulong guildId, DiscordCl #region Private Methods - private IReadOnlyDictionary GetProperties(DiscordGuild guild, WhConfig whConfig, string city) + private IReadOnlyDictionary GetProperties(DiscordGuild guild, WhConfig whConfig, string city, bool useLure, bool useInvasion) { var lureImageUrl = IconFetcher.Instance.GetLureIcon(whConfig.Servers[guild.Id].IconStyle, LureType); var invasionImageUrl = IconFetcher.Instance.GetInvasionIcon(whConfig.Servers[guild.Id].IconStyle, GruntType); - var imageUrl = HasInvasion ? invasionImageUrl : HasLure ? lureImageUrl : Url; + var imageUrl = useInvasion ? invasionImageUrl : useLure ? lureImageUrl : Url; var gmapsLink = string.Format(Strings.GoogleMaps, Latitude, Longitude); var appleMapsLink = string.Format(Strings.AppleMaps, Latitude, Longitude); var wazeMapsLink = string.Format(Strings.WazeMaps, Latitude, Longitude); var scannerMapsLink = string.Format(whConfig.Urls.ScannerMap, Latitude, Longitude); - var staticMapLink = StaticMap.GetUrl(whConfig.Urls.StaticMap, HasInvasion ? whConfig.StaticMaps["invasions"] : HasLure ? whConfig.StaticMaps["lures"] : /* TODO: */"", Latitude, Longitude, imageUrl); + var staticMapLink = StaticMap.GetUrl(whConfig.Urls.StaticMap, useInvasion ? whConfig.StaticMaps["invasions"] : useLure ? whConfig.StaticMaps["lures"] : /* TODO: */"", Latitude, Longitude, imageUrl); var gmapsLocationLink = UrlShortener.CreateShortUrl(whConfig.ShortUrlApiUrl, gmapsLink); var appleMapsLocationLink = UrlShortener.CreateShortUrl(whConfig.ShortUrlApiUrl, appleMapsLink); var wazeMapsLocationLink = UrlShortener.CreateShortUrl(whConfig.ShortUrlApiUrl, wazeMapsLink); @@ -227,7 +227,7 @@ private IReadOnlyDictionary GetProperties(DiscordGuild guild, Wh /// /// Pokestop lure type /// - public enum PokestopLureType + public enum PokestopLureType : ushort { /// /// No Pokestop lure deployed diff --git a/src/Net/Webhooks/WebhookController.cs b/src/Net/Webhooks/WebhookController.cs index 5e80148e..d0beeb92 100644 --- a/src/Net/Webhooks/WebhookController.cs +++ b/src/Net/Webhooks/WebhookController.cs @@ -170,6 +170,12 @@ private void OnInvasionSubscriptionTriggered(PokestopData pokestop) InvasionSubscriptionTriggered?.Invoke(this, pokestop); } + public event EventHandler LureSubscriptionTriggered; + private void OnLureSubscriptionTriggered(PokestopData pokestop) + { + LureSubscriptionTriggered?.Invoke(this, pokestop); + } + #endregion #endregion @@ -294,6 +300,7 @@ private void Http_PokestopReceived(object sender, DataReceivedEventArgs Hundos { get; } public long TotalReceivedPokemon { get; set; } @@ -91,7 +93,7 @@ public Statistics() #region Public Methods - public void Add100Percent(PokemonData pokemon) + public void AddHundredIV(PokemonData pokemon) { Hundos.Add(DateTime.Now, pokemon); } @@ -103,48 +105,46 @@ public static void WriteOut() Directory.CreateDirectory(Strings.StatsFolder); } - var stats = Statistics.Instance; var sb = new System.Text.StringBuilder(); - var header = "Pokemon Alarms,Raid Alarms,Quest Alarms,Pokemon Subscriptions,Raid Subscriptions,Quest Subscriptions,Top 25 Pokemon,Top 25 Raids"; - sb.AppendLine(header); sb.AppendLine(DateTime.Now.ToString()); sb.AppendLine($"__**Pokemon**__"); - sb.AppendLine($"Alarms Sent: {stats.PokemonAlarmsSent:N0}"); - sb.AppendLine($"Total Received: {stats.TotalReceivedPokemon:N0}"); - sb.AppendLine($"With IV Stats: {stats.TotalReceivedPokemonWithStats:N0}"); - sb.AppendLine($"Missing IV Stats: {stats.TotalReceivedPokemonMissingStats:N0}"); - sb.AppendLine($"Subscriptions Sent: {stats.SubscriptionPokemonSent:N0}"); + sb.AppendLine($"Alarms Sent: {Instance.PokemonAlarmsSent:N0}"); + sb.AppendLine($"Total Received: {Instance.TotalReceivedPokemon:N0}"); + sb.AppendLine($"With IV Stats: {Instance.TotalReceivedPokemonWithStats:N0}"); + sb.AppendLine($"Missing IV Stats: {Instance.TotalReceivedPokemonMissingStats:N0}"); + sb.AppendLine($"Subscriptions Sent: {Instance.SubscriptionPokemonSent:N0}"); sb.AppendLine(); sb.AppendLine("__**Raids**__"); - sb.AppendLine($"Egg Alarms Sent: {stats.EggAlarmsSent:N0}"); - sb.AppendLine($"Raids Alarms Sent: {stats.RaidAlarmsSent:N0}"); - sb.AppendLine($"Total Eggs Received: {stats.TotalReceivedRaids:N0}"); - sb.AppendLine($"Total Raids Received: {stats.TotalReceivedRaids:N0}"); - sb.AppendLine($"Raid Subscriptions Sent: {stats.SubscriptionRaidsSent:N0}"); + sb.AppendLine($"Egg Alarms Sent: {Instance.EggAlarmsSent:N0}"); + sb.AppendLine($"Raids Alarms Sent: {Instance.RaidAlarmsSent:N0}"); + sb.AppendLine($"Total Eggs Received: {Instance.TotalReceivedRaids:N0}"); + sb.AppendLine($"Total Raids Received: {Instance.TotalReceivedRaids:N0}"); + sb.AppendLine($"Raid Subscriptions Sent: {Instance.SubscriptionRaidsSent:N0}"); sb.AppendLine(); sb.AppendLine($"__**Quests**__"); - sb.AppendLine($"Alarms Sent: {stats.QuestAlarmsSent:N0}"); - sb.AppendLine($"Total Received: {stats.TotalReceivedQuests:N0}"); - sb.AppendLine($"Subscriptions Sent: {stats.SubscriptionQuestsSent:N0}"); + sb.AppendLine($"Alarms Sent: {Instance.QuestAlarmsSent:N0}"); + sb.AppendLine($"Total Received: {Instance.TotalReceivedQuests:N0}"); + sb.AppendLine($"Subscriptions Sent: {Instance.SubscriptionQuestsSent:N0}"); sb.AppendLine(); sb.AppendLine($"__**Invasions**__"); - sb.AppendLine($"Alarms Sent: {stats.InvasionAlarmsSent:N0}"); - sb.AppendLine($"Total Received: {stats.TotalReceivedInvasions:N0}"); - sb.AppendLine($"Subscriptions Sent: {stats.SubscriptionInvasionsSent:N0}"); + sb.AppendLine($"Alarms Sent: {Instance.InvasionAlarmsSent:N0}"); + sb.AppendLine($"Total Received: {Instance.TotalReceivedInvasions:N0}"); + sb.AppendLine($"Subscriptions Sent: {Instance.SubscriptionInvasionsSent:N0}"); sb.AppendLine(); sb.AppendLine($"__**Lures**__"); - sb.AppendLine($"Alarms Sent: {stats.LureAlarmsSent:N0}"); - sb.AppendLine($"Total Received: {stats.TotalReceivedLures:N0}"); + sb.AppendLine($"Alarms Sent: {Instance.LureAlarmsSent:N0}"); + sb.AppendLine($"Total Received: {Instance.TotalReceivedLures:N0}"); + sb.AppendLine($"Subscriptions Sent: {Instance.SubscriptionLuresSent:N0}"); sb.AppendLine(); sb.AppendLine($"__**Gyms**__"); - sb.AppendLine($"Alarms Sent: {stats.GymAlarmsSent:N0}"); - sb.AppendLine($"Total Received: {stats.TotalReceivedGyms:N0}"); + sb.AppendLine($"Alarms Sent: {Instance.GymAlarmsSent:N0}"); + sb.AppendLine($"Total Received: {Instance.TotalReceivedGyms:N0}"); sb.AppendLine(); sb.AppendLine($"__**Weather**__"); - sb.AppendLine($"Alarms Sent: {stats.WeatherAlarmsSent:N0}"); - sb.AppendLine($"Total Received: {stats.TotalReceivedWeathers:N0}"); + sb.AppendLine($"Alarms Sent: {Instance.WeatherAlarmsSent:N0}"); + sb.AppendLine($"Total Received: {Instance.TotalReceivedWeathers:N0}"); sb.AppendLine(); - var hundos = string.Join(Environment.NewLine, stats.Hundos.Select(x => $"{x.Key}: {MasterFile.Instance.Pokedex[x.Value.Id].Name} {x.Value.IV} IV {x.Value.CP} CP")); + var hundos = string.Join(Environment.NewLine, Instance.Hundos.Select(x => $"{x.Key}: {MasterFile.Instance.Pokedex[x.Value.Id].Name} {x.Value.IV} IV {x.Value.CP} CP")); sb.AppendLine($"**Recent 100% Spawns**"); sb.AppendLine(string.IsNullOrEmpty(hundos) ? "None" : hundos); diff --git a/static/locale/de.json b/static/locale/de.json index 85414808..6a769cbe 100644 --- a/static/locale/de.json +++ b/static/locale/de.json @@ -28,6 +28,13 @@ "NOTIFY_INVALID_POKEMON_IDS_OR_NAMES": "{0} {1} are not valid Pokemon names or IDs.", "NOTIFY_INVALID_POKEMON_ID_OR_NAME": "{0} {1} are not valid Pokemon name or ID.", "NOTIFY_INVALID_POKEMON_ID": "{0} {1} is not a valid Pokemon id.", + "NOTIFY_INVALID_POKEMON_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Pokemon subscriptions limit of {1}, please remove subscriptions in order to add more.", + "NOTIFY_INVALID_PVP_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Pokemon subscriptions limit of {1}, please remove subscriptions in order to add more.", + "NOTIFY_INVALID_RAID_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Raid subscriptions limit of {1}, please remove subscriptions in order to add more.", + "NOTIFY_INVALID_QUEST_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Quest subscriptions limit of {1}, please remove subscriptions in order to add more.", + "NOTIFY_INVALID_INVASION_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Team Rocket Invasion subscriptions limit of {1}, please remove subscriptions in order to add more.", + "NOTIFY_INVALID_GYM_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Gym subscriptions limit of {1}, please remove subscriptions in order to add more.", + "NOTIFY_INVALID_LURE_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Lure subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_COMMON_TYPE_POKEMON": "{0} {1} is a common type Pokemon and cannot be subscribed to for notifications unless the IV is set to at least {2}% or higher.", "NOTIFY_INVALID_POKEMON_SPECIFIED": "{0} Unable to recognize any of the Pokemon you specified.", "NOTIFY_NO_POKEMON_SUBSCRIPTIONS": "{0} is not subscribed to any Pokemon notifications.", @@ -75,6 +82,7 @@ "NOTIFY_SETTINGS_EMBED_GYMS": "Gym Subscriptions: ({0}/{1} used)", "NOTIFY_SETTINGS_EMBED_QUESTS": "Quest Subscriptions: ({0}/{1} used)", "NOTIFY_SETTINGS_EMBED_INVASIONS": "Invasion Subscriptions: ({0}/{1} used)", + "NOTIFY_SETTINGS_EMBED_LURES": "Lure Subscriptions: ({0}/{1} used)", "NOTIFY_FROM": "{0} (From: {1})", "ALL_AREAS": "All Areas", "FEEDS_AVAILABLE_CITY_ROLES": "**Available City Roles:**", @@ -113,6 +121,8 @@ "DONATE_MESSAGE": "{0} This feature is only available to supporters, please donate to unlock this feature and more.\r\n\r\nDonation information can be found by typing the `donate` command.\r\n\r\n*If you have already donated and are still receiving this message, please tag an Administrator or Moderator for help.*", "SUBSCRIPTIONS_FROM_ALL_CITIES": " from **all** areas", "SUBSCRIPTIONS_FROM_CITY": " from city **{0}**", + "SUCCESS_LURE_SUBSCRIPTIONS_SUBSCRIBE": "{0} has subscribed to **{1}** lure notifications{2}.", + "SUCCESS_LURE_SUBSCRIPTIONS_UNSUBSCRIBE": "{0} has unsubscribed from **{1}** lure notifications{2}.", "SUCCESS_RAID_SUBSCRIPTIONS_SUBSCRIBE": "{0} has subscribed to **{1}** raid notifications{2}.", "SUCCESS_RAID_SUBSCRIPTIONS_UNSUBSCRIBE": "{0} has unsubscribed from **{1}** raid notifications{2}.", "ERROR_NO_RAID_SUBSCRIPTIONS": "{0} is not subscribed to any raid notifications{1}.", diff --git a/static/locale/en.json b/static/locale/en.json index 69f672ba..d776b42a 100644 --- a/static/locale/en.json +++ b/static/locale/en.json @@ -81,6 +81,13 @@ "NOTIFY_INVALID_PVP_PERCENT_RANGE": "{0} {1} must be within the range of `0-100`.", "NOTIFY_INVALID_PVP_RANK_RANGE": "{0} {1} must be within the range of `0-4096`.", "NOTIFY_INVALID_STAMINA_VALUE": "{0} {1} is not a valid stamina value. Must be between `0-15`.", + "NOTIFY_INVALID_POKEMON_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Pokemon subscriptions limit of {1}, please remove subscriptions in order to add more.", + "NOTIFY_INVALID_PVP_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Pokemon subscriptions limit of {1}, please remove subscriptions in order to add more.", + "NOTIFY_INVALID_RAID_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Raid subscriptions limit of {1}, please remove subscriptions in order to add more.", + "NOTIFY_INVALID_QUEST_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Quest subscriptions limit of {1}, please remove subscriptions in order to add more.", + "NOTIFY_INVALID_INVASION_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Team Rocket Invasion subscriptions limit of {1}, please remove subscriptions in order to add more.", + "NOTIFY_INVALID_GYM_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Gym subscriptions limit of {1}, please remove subscriptions in order to add more.", + "NOTIFY_INVALID_LURE_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Lure subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_NO_POKEMON_SUBSCRIPTIONS": "{0} is not subscribed to any Pokemon notifications.", "NOTIFY_PHONE_NUMBER_SET": "{0} Text message notifications for ultra rare Pokemon will be sent to {1}.", "NOTIFY_SETTINGS_EMBED_CITIES": "Pokemon Feed Zones: ```{0}```", @@ -98,6 +105,7 @@ "NOTIFY_SETTINGS_EMBED_PVP": "PvP Subscriptions: ({0}/{1} used)", "NOTIFY_SETTINGS_EMBED_QUESTS": "Quest Subscriptions: ({0}/{1} used)", "NOTIFY_SETTINGS_EMBED_RAIDS": "Raid Subscriptions: ({0}/{1} used)", + "NOTIFY_SETTINGS_EMBED_LURES": "Lure Subscriptions: ({0}/{1} used)", "NOTIFY_SETTINGS_EMBED_TITLE": "**{0} Notification Settings (Page: {1}/{2}):**", "NOTIFY_SUCCESS_REMOVE_ALL_GYM_SUBSCRIPTIONS": "{0} has unsubscribed from **all** gym notifications.", "NOTIFY_SUCCESS_REMOVE_ALL_INVASION_SUBSCRIPTIONS": "{0} has unsubscribed from **all** Team Rocket invasion notifications.", @@ -127,6 +135,8 @@ "SUCCESS_QUEST_SUBSCRIPTIONS_UNSUBSCRIBE": "{0} has unsubscribed from **{1}** quest notifications{2}.", "SUCCESS_RAID_SUBSCRIPTIONS_SUBSCRIBE": "{0} has subscribed to **{1}** raid notifications{2}.", "SUCCESS_RAID_SUBSCRIPTIONS_UNSUBSCRIBE": "{0} has unsubscribed from **{1}** raid notifications{2}.", + "SUCCESS_LURE_SUBSCRIPTIONS_SUBSCRIBE": "{0} has subscribed to **{1}** lure notifications{2}.", + "SUCCESS_LURE_SUBSCRIPTIONS_UNSUBSCRIBE": "{0} has unsubscribed from **{1}** lure notifications{2}.", "TIME_DAYS": "days", "TIME_HOURS": "hours", "TIME_MINUTES": "minutes", diff --git a/static/locale/es.json b/static/locale/es.json index 027217d9..de26b20f 100644 --- a/static/locale/es.json +++ b/static/locale/es.json @@ -58,6 +58,7 @@ "NOTIFY_INVALID_QUEST_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Quest subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_INVALID_INVASION_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Team Rocket Invasion subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_INVALID_GYM_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Gym subscriptions limit of {1}, please remove subscriptions in order to add more.", + "NOTIFY_INVALID_LURE_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Lure subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_ICON_STYLE_CHANGE": "{0} Icon style changed to **{1}**.", "NOTIFY_IMPORT_UPLOAD_FILE": "{0} Please upload your subscriptions.json file to import now within 3 minutes...", "NOTIFY_IMPORT_MALFORMED_DATA": "{0} Malformed subscription data, unable to import.", @@ -81,6 +82,7 @@ "NOTIFY_SETTINGS_EMBED_GYMS": "Gym Subscriptions: ({0}/{1} used)", "NOTIFY_SETTINGS_EMBED_QUESTS": "Quest Subscriptions: ({0}/{1} used)", "NOTIFY_SETTINGS_EMBED_INVASIONS": "Invasion Subscriptions: ({0}/{1} used)", + "NOTIFY_SETTINGS_EMBED_LURES": "Lure Subscriptions: ({0}/{1} used)", "NOTIFY_FROM": "{0} (From: {1})", "ALL_AREAS": "All Areas", "FEEDS_AVAILABLE_CITY_ROLES": "**Available City Roles:**", @@ -119,6 +121,8 @@ "DONATE_MESSAGE": "{0} This feature is only available to supporters, please donate to unlock this feature and more.\r\n\r\nDonation information can be found by typing the `donate` command.\r\n\r\n*If you have already donated and are still receiving this message, please tag an Administrator or Moderator for help.*", "SUBSCRIPTIONS_FROM_ALL_CITIES": " from **all** areas", "SUBSCRIPTIONS_FROM_CITY": " from city **{0}**", + "SUCCESS_LURE_SUBSCRIPTIONS_SUBSCRIBE": "{0} has subscribed to **{1}** lure notifications{2}.", + "SUCCESS_LURE_SUBSCRIPTIONS_UNSUBSCRIBE": "{0} has unsubscribed from **{1}** lure notifications{2}.", "SUCCESS_RAID_SUBSCRIPTIONS_SUBSCRIBE": "{0} has subscribed to **{1}** raid notifications{2}.", "SUCCESS_RAID_SUBSCRIPTIONS_UNSUBSCRIBE": "{0} has unsubscribed from **{1}** raid notifications{2}.", "ERROR_NO_RAID_SUBSCRIPTIONS": "{0} is not subscribed to any raid notifications{1}.", diff --git a/static/locale/fr.json b/static/locale/fr.json index 36a96f92..41eb4fe3 100644 --- a/static/locale/fr.json +++ b/static/locale/fr.json @@ -58,6 +58,7 @@ "NOTIFY_INVALID_QUEST_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Quest subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_INVALID_INVASION_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Team Rocket Invasion subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_INVALID_GYM_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Gym subscriptions limit of {1}, please remove subscriptions in order to add more.", + "NOTIFY_INVALID_LURE_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Lure subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_ICON_STYLE_CHANGE": "{0} Icon style changed to **{1}**.", "NOTIFY_IMPORT_UPLOAD_FILE": "{0} Please upload your subscriptions.json file to import now within 3 minutes...", "NOTIFY_IMPORT_MALFORMED_DATA": "{0} Malformed subscription data, unable to import.", @@ -81,6 +82,7 @@ "NOTIFY_SETTINGS_EMBED_GYMS": "Gym Subscriptions: ({0}/{1} used)", "NOTIFY_SETTINGS_EMBED_QUESTS": "Quest Subscriptions: ({0}/{1} used)", "NOTIFY_SETTINGS_EMBED_INVASIONS": "Invasion Subscriptions: ({0}/{1} used)", + "NOTIFY_SETTINGS_EMBED_LURES": "Lure Subscriptions: ({0}/{1} used)", "NOTIFY_FROM": "{0} (From: {1})", "ALL_AREAS": "All Areas", "FEEDS_AVAILABLE_CITY_ROLES": "**Available City Roles:**", @@ -119,6 +121,8 @@ "DONATE_MESSAGE": "{0} This feature is only available to supporters, please donate to unlock this feature and more.\r\n\r\nDonation information can be found by typing the `donate` command.\r\n\r\n*If you have already donated and are still receiving this message, please tag an Administrator or Moderator for help.*", "SUBSCRIPTIONS_FROM_ALL_CITIES": " from **all** areas", "SUBSCRIPTIONS_FROM_CITY": " from city **{0}**", + "SUCCESS_LURE_SUBSCRIPTIONS_SUBSCRIBE": "{0} has subscribed to **{1}** lure notifications{2}.", + "SUCCESS_LURE_SUBSCRIPTIONS_UNSUBSCRIBE": "{0} has unsubscribed from **{1}** lure notifications{2}.", "SUCCESS_RAID_SUBSCRIPTIONS_SUBSCRIBE": "{0} has subscribed to **{1}** raid notifications{2}.", "SUCCESS_RAID_SUBSCRIPTIONS_UNSUBSCRIBE": "{0} has unsubscribed from **{1}** raid notifications{2}.", "ERROR_NO_RAID_SUBSCRIPTIONS": "{0} is not subscribed to any raid notifications{1}.", diff --git a/static/locale/it.json b/static/locale/it.json index 885b0ea7..5a2ed9de 100644 --- a/static/locale/it.json +++ b/static/locale/it.json @@ -58,6 +58,7 @@ "NOTIFY_INVALID_QUEST_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Quest subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_INVALID_INVASION_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Team Rocket Invasion subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_INVALID_GYM_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Gym subscriptions limit of {1}, please remove subscriptions in order to add more.", + "NOTIFY_INVALID_LURE_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Lure subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_ICON_STYLE_CHANGE": "{0} Icon style changed to **{1}**.", "NOTIFY_IMPORT_UPLOAD_FILE": "{0} Please upload your subscriptions.json file to import now within 3 minutes...", "NOTIFY_IMPORT_MALFORMED_DATA": "{0} Malformed subscription data, unable to import.", @@ -81,6 +82,7 @@ "NOTIFY_SETTINGS_EMBED_GYMS": "Gym Subscriptions: ({0}/{1} used)", "NOTIFY_SETTINGS_EMBED_QUESTS": "Quest Subscriptions: ({0}/{1} used)", "NOTIFY_SETTINGS_EMBED_INVASIONS": "Invasion Subscriptions: ({0}/{1} used)", + "NOTIFY_SETTINGS_EMBED_LURES": "Lure Subscriptions: ({0}/{1} used)", "NOTIFY_FROM": "{0} (From: {1})", "ALL_AREAS": "All Areas", "FEEDS_AVAILABLE_CITY_ROLES": "**Available City Roles:**", @@ -119,6 +121,8 @@ "DONATE_MESSAGE": "{0} This feature is only available to supporters, please donate to unlock this feature and more.\r\n\r\nDonation information can be found by typing the `donate` command.\r\n\r\n*If you have already donated and are still receiving this message, please tag an Administrator or Moderator for help.*", "SUBSCRIPTIONS_FROM_ALL_CITIES": " from **all** areas", "SUBSCRIPTIONS_FROM_CITY": " from city **{0}**", + "SUCCESS_LURE_SUBSCRIPTIONS_SUBSCRIBE": "{0} has subscribed to **{1}** lure notifications{2}.", + "SUCCESS_LURE_SUBSCRIPTIONS_UNSUBSCRIBE": "{0} has unsubscribed from **{1}** lure notifications{2}.", "SUCCESS_RAID_SUBSCRIPTIONS_SUBSCRIBE": "{0} has subscribed to **{1}** raid notifications{2}.", "SUCCESS_RAID_SUBSCRIPTIONS_UNSUBSCRIBE": "{0} has unsubscribed from **{1}** raid notifications{2}.", "ERROR_NO_RAID_SUBSCRIPTIONS": "{0} is not subscribed to any raid notifications{1}.", diff --git a/static/locale/jp.json b/static/locale/jp.json index 75598ea9..61802a1a 100644 --- a/static/locale/jp.json +++ b/static/locale/jp.json @@ -58,6 +58,7 @@ "NOTIFY_INVALID_QUEST_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Quest subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_INVALID_INVASION_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Team Rocket Invasion subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_INVALID_GYM_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Gym subscriptions limit of {1}, please remove subscriptions in order to add more.", + "NOTIFY_INVALID_LURE_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Lure subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_ICON_STYLE_CHANGE": "{0} Icon style changed to **{1}**.", "NOTIFY_IMPORT_UPLOAD_FILE": "{0} Please upload your subscriptions.json file to import now within 3 minutes...", "NOTIFY_IMPORT_MALFORMED_DATA": "{0} Malformed subscription data, unable to import.", @@ -81,6 +82,7 @@ "NOTIFY_SETTINGS_EMBED_GYMS": "Gym Subscriptions: ({0}/{1} used)", "NOTIFY_SETTINGS_EMBED_QUESTS": "Quest Subscriptions: ({0}/{1} used)", "NOTIFY_SETTINGS_EMBED_INVASIONS": "Invasion Subscriptions: ({0}/{1} used)", + "NOTIFY_SETTINGS_EMBED_LURES": "Lure Subscriptions: ({0}/{1} used)", "NOTIFY_FROM": "{0} (From: {1})", "ALL_AREAS": "All Areas", "FEEDS_AVAILABLE_CITY_ROLES": "**Available City Roles:**", @@ -119,6 +121,8 @@ "DONATE_MESSAGE": "{0} This feature is only available to supporters, please donate to unlock this feature and more.\r\n\r\nDonation information can be found by typing the `donate` command.\r\n\r\n*If you have already donated and are still receiving this message, please tag an Administrator or Moderator for help.*", "SUBSCRIPTIONS_FROM_ALL_CITIES": " from **all** areas", "SUBSCRIPTIONS_FROM_CITY": " from city **{0}**", + "SUCCESS_LURE_SUBSCRIPTIONS_SUBSCRIBE": "{0} has subscribed to **{1}** lure notifications{2}.", + "SUCCESS_LURE_SUBSCRIPTIONS_UNSUBSCRIBE": "{0} has unsubscribed from **{1}** lure notifications{2}.", "SUCCESS_RAID_SUBSCRIPTIONS_SUBSCRIBE": "{0} has subscribed to **{1}** raid notifications{2}.", "SUCCESS_RAID_SUBSCRIPTIONS_UNSUBSCRIBE": "{0} has unsubscribed from **{1}** raid notifications{2}.", "ERROR_NO_RAID_SUBSCRIPTIONS": "{0} is not subscribed to any raid notifications{1}.", diff --git a/static/locale/ko.json b/static/locale/ko.json index bd9dab31..1fb223a1 100644 --- a/static/locale/ko.json +++ b/static/locale/ko.json @@ -59,6 +59,7 @@ "NOTIFY_INVALID_QUEST_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Quest subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_INVALID_INVASION_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Team Rocket Invasion subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_INVALID_GYM_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Gym subscriptions limit of {1}, please remove subscriptions in order to add more.", + "NOTIFY_INVALID_LURE_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Lure subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_IMPORT_UPLOAD_FILE": "{0} Please upload your subscriptions.json file to import now within 3 minutes...", "NOTIFY_IMPORT_MALFORMED_DATA": "{0} Malformed subscription data, unable to import.", "NOTIFY_IMPORT_INVALID_ATTACHMENT": "{0} Unable to get uploaded attachment.", @@ -81,6 +82,7 @@ "NOTIFY_SETTINGS_EMBED_GYMS": "Gym Subscriptions: ({0}/{1} used)", "NOTIFY_SETTINGS_EMBED_QUESTS": "Quest Subscriptions: ({0}/{1} used)", "NOTIFY_SETTINGS_EMBED_INVASIONS": "Invasion Subscriptions: ({0}/{1} used)", + "NOTIFY_SETTINGS_EMBED_LURES": "Lure Subscriptions: ({0}/{1} used)", "NOTIFY_FROM": "{0} (From: {1})", "ALL_AREAS": "All Areas", "FEEDS_AVAILABLE_CITY_ROLES": "**Available City Roles:**", @@ -119,6 +121,8 @@ "DONATE_MESSAGE": "{0} This feature is only available to supporters, please donate to unlock this feature and more.\r\n\r\nDonation information can be found by typing the `donate` command.\r\n\r\n*If you have already donated and are still receiving this message, please tag an Administrator or Moderator for help.*", "SUBSCRIPTIONS_FROM_ALL_CITIES": " from **all** areas", "SUBSCRIPTIONS_FROM_CITY": " from city **{0}**", + "SUCCESS_LURE_SUBSCRIPTIONS_SUBSCRIBE": "{0} has subscribed to **{1}** lure notifications{2}.", + "SUCCESS_LURE_SUBSCRIPTIONS_UNSUBSCRIBE": "{0} has unsubscribed from **{1}** lure notifications{2}.", "SUCCESS_RAID_SUBSCRIPTIONS_SUBSCRIBE": "{0} has subscribed to **{1}** raid notifications{2}.", "SUCCESS_RAID_SUBSCRIPTIONS_UNSUBSCRIBE": "{0} has unsubscribed from **{1}** raid notifications{2}.", "ERROR_NO_RAID_SUBSCRIPTIONS": "{0} is not subscribed to any raid notifications{1}.", diff --git a/static/locale/pt-br.json b/static/locale/pt-br.json index b76bc5a5..a8ef10ca 100644 --- a/static/locale/pt-br.json +++ b/static/locale/pt-br.json @@ -59,6 +59,7 @@ "NOTIFY_INVALID_QUEST_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Quest subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_INVALID_INVASION_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Team Rocket Invasion subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_INVALID_GYM_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Gym subscriptions limit of {1}, please remove subscriptions in order to add more.", + "NOTIFY_INVALID_LURE_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Lure subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_IMPORT_UPLOAD_FILE": "{0} Please upload your subscriptions.json file to import now within 3 minutes...", "NOTIFY_IMPORT_MALFORMED_DATA": "{0} Malformed subscription data, unable to import.", "NOTIFY_IMPORT_INVALID_ATTACHMENT": "{0} Unable to get uploaded attachment.", @@ -81,6 +82,7 @@ "NOTIFY_SETTINGS_EMBED_GYMS": "Gym Subscriptions: ({0}/{1} used)", "NOTIFY_SETTINGS_EMBED_QUESTS": "Quest Subscriptions: ({0}/{1} used)", "NOTIFY_SETTINGS_EMBED_INVASIONS": "Invasion Subscriptions: ({0}/{1} used)", + "NOTIFY_SETTINGS_EMBED_LURES": "Lure Subscriptions: ({0}/{1} used)", "NOTIFY_FROM": "{0} (From: {1})", "ALL_AREAS": "All Areas", "FEEDS_AVAILABLE_CITY_ROLES": "**Available City Roles:**", @@ -119,6 +121,8 @@ "DONATE_MESSAGE": "{0} This feature is only available to supporters, please donate to unlock this feature and more.\r\n\r\nDonation information can be found by typing the `donate` command.\r\n\r\n*If you have already donated and are still receiving this message, please tag an Administrator or Moderator for help.*", "SUBSCRIPTIONS_FROM_ALL_CITIES": " from **all** areas", "SUBSCRIPTIONS_FROM_CITY": " from city **{0}**", + "SUCCESS_LURE_SUBSCRIPTIONS_SUBSCRIBE": "{0} has subscribed to **{1}** lure notifications{2}.", + "SUCCESS_LURE_SUBSCRIPTIONS_UNSUBSCRIBE": "{0} has unsubscribed from **{1}** lure notifications{2}.", "SUCCESS_RAID_SUBSCRIPTIONS_SUBSCRIBE": "{0} has subscribed to **{1}** raid notifications{2}.", "SUCCESS_RAID_SUBSCRIPTIONS_UNSUBSCRIBE": "{0} has unsubscribed from **{1}** raid notifications{2}.", "ERROR_NO_RAID_SUBSCRIPTIONS": "{0} is not subscribed to any raid notifications{1}.", diff --git a/static/locale/th.json b/static/locale/th.json index a01ae0d7..a8b16a53 100644 --- a/static/locale/th.json +++ b/static/locale/th.json @@ -59,6 +59,7 @@ "NOTIFY_INVALID_QUEST_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Quest subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_INVALID_INVASION_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Team Rocket Invasion subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_INVALID_GYM_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Gym subscriptions limit of {1}, please remove subscriptions in order to add more.", + "NOTIFY_INVALID_LURE_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Lure subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_IMPORT_UPLOAD_FILE": "{0} Please upload your subscriptions.json file to import now within 3 minutes...", "NOTIFY_IMPORT_MALFORMED_DATA": "{0} Malformed subscription data, unable to import.", "NOTIFY_IMPORT_INVALID_ATTACHMENT": "{0} Unable to get uploaded attachment.", @@ -81,6 +82,7 @@ "NOTIFY_SETTINGS_EMBED_GYMS": "Gym Subscriptions: ({0}/{1} used)", "NOTIFY_SETTINGS_EMBED_QUESTS": "Quest Subscriptions: ({0}/{1} used)", "NOTIFY_SETTINGS_EMBED_INVASIONS": "Invasion Subscriptions: ({0}/{1} used)", + "NOTIFY_SETTINGS_EMBED_LURES": "Lure Subscriptions: ({0}/{1} used)", "NOTIFY_FROM": "{0} (From: {1})", "ALL_AREAS": "All Areas", "FEEDS_AVAILABLE_CITY_ROLES": "**Available City Roles:**", @@ -119,6 +121,8 @@ "DONATE_MESSAGE": "{0} This feature is only available to supporters, please donate to unlock this feature and more.\r\n\r\nDonation information can be found by typing the `donate` command.\r\n\r\n*If you have already donated and are still receiving this message, please tag an Administrator or Moderator for help.*", "SUBSCRIPTIONS_FROM_ALL_CITIES": " from **all** areas", "SUBSCRIPTIONS_FROM_CITY": " from city **{0}**", + "SUCCESS_LURE_SUBSCRIPTIONS_SUBSCRIBE": "{0} has subscribed to **{1}** lure notifications{2}.", + "SUCCESS_LURE_SUBSCRIPTIONS_UNSUBSCRIBE": "{0} has unsubscribed from **{1}** lure notifications{2}.", "SUCCESS_RAID_SUBSCRIPTIONS_SUBSCRIBE": "{0} has subscribed to **{1}** raid notifications{2}.", "SUCCESS_RAID_SUBSCRIPTIONS_UNSUBSCRIBE": "{0} has unsubscribed from **{1}** raid notifications{2}.", "ERROR_NO_RAID_SUBSCRIPTIONS": "{0} is not subscribed to any raid notifications{1}.", diff --git a/static/locale/zh.json b/static/locale/zh.json index 4d8433a7..b993d535 100644 --- a/static/locale/zh.json +++ b/static/locale/zh.json @@ -58,6 +58,7 @@ "NOTIFY_INVALID_QUEST_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Quest subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_INVALID_INVASION_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Team Rocket Invasion subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_INVALID_GYM_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Gym subscriptions limit of {1}, please remove subscriptions in order to add more.", + "NOTIFY_INVALID_LURE_SUBSCRIPTIONS_LIMIT": "{0} You have reached the maximum Lure subscriptions limit of {1}, please remove subscriptions in order to add more.", "NOTIFY_ICON_STYLE_CHANGE": "{0} Icon style changed to **{1}**.", "NOTIFY_IMPORT_UPLOAD_FILE": "{0} Please upload your subscriptions.json file to import now within 3 minutes...", "NOTIFY_IMPORT_MALFORMED_DATA": "{0} Malformed subscription data, unable to import.", @@ -81,6 +82,7 @@ "NOTIFY_SETTINGS_EMBED_GYMS": "Gym Subscriptions: ({0}/{1} used)", "NOTIFY_SETTINGS_EMBED_QUESTS": "Quest Subscriptions: ({0}/{1} used)", "NOTIFY_SETTINGS_EMBED_INVASIONS": "Invasion Subscriptions: ({0}/{1} used)", + "NOTIFY_SETTINGS_EMBED_LURES": "Lure Subscriptions: ({0}/{1} used)", "NOTIFY_FROM": "{0} (From: {1})", "ALL_AREAS": "All Areas", "FEEDS_AVAILABLE_CITY_ROLES": "**Available City Roles:**", @@ -119,6 +121,8 @@ "DONATE_MESSAGE": "{0} This feature is only available to supporters, please donate to unlock this feature and more.\r\n\r\nDonation information can be found by typing the `donate` command.\r\n\r\n*If you have already donated and are still receiving this message, please tag an Administrator or Moderator for help.*", "SUBSCRIPTIONS_FROM_ALL_CITIES": " from **all** areas", "SUBSCRIPTIONS_FROM_CITY": " from city **{0}**", + "SUCCESS_LURE_SUBSCRIPTIONS_SUBSCRIBE": "{0} has subscribed to **{1}** lure notifications{2}.", + "SUCCESS_LURE_SUBSCRIPTIONS_UNSUBSCRIBE": "{0} has unsubscribed from **{1}** lure notifications{2}.", "SUCCESS_RAID_SUBSCRIPTIONS_SUBSCRIBE": "{0} has subscribed to **{1}** raid notifications{2}.", "SUCCESS_RAID_SUBSCRIPTIONS_UNSUBSCRIBE": "{0} has unsubscribed from **{1}** raid notifications{2}.", "ERROR_NO_RAID_SUBSCRIPTIONS": "{0} is not subscribed to any raid notifications{1}.",