diff --git a/README.md b/README.md index 5af3af15..de51b0f9 100644 --- a/README.md +++ b/README.md @@ -1,748 +1,761 @@ -# Brock Webhook Manager v4.0 - -### PokeAlarm, Poracle, WDR, etc alternative. -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, gym team changes, and weather. Also supports Discord user's subscribing to Pokemon, raid, quest, and Team Rocket invasion 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. -- User interface to configure Discord notifications with ease (as well as Discord commands). (https://github.com/versx/WhMgr-UI) -- Notifications based on pre-defined distance. -- Customizable alert messages with dynamic text replacement. -- Support for multiple cities/areas using roles and geofences per server. -- Daily shiny stats reporting. -- Automatic quest message purge at midnight. -- Support for Donors/Supporters only notifications. -- Direct messages of Pokemon notifications based on city roles assigned. -- Custom prefix support as well as mentionable user support for commands. -- Subscriptions based on distance from a set location or specific gym names. -- Twilio text message alerts for ultra rare Pokemon. -- Custom image support for Discord alarm reports. -- Custom icon style selection for Discord user notifications. -- External emoji server support. -- Custom static map format support. -- Support for language translation. -- Multi threaded, low processing consumption. -- Lots more... - -## Documentation: -[ReadTheDocs](https://whmgr.rtfd.io/) - -## Getting Started: - -1.) Run the following to install .NET Core runtime, clone respository, and copy example Alerts, Filters, Geofences, config and alarm files. -``` -Linux/macOS: -wget https://raw.githubusercontent.com/versx/WhMgr/master/install.sh && chmod +x install.sh && ./install.sh && rm install.sh - -Windows: -bitsadmin /transfer dotnet-install-job /download /priority FOREGROUND https://raw.githubusercontent.com/versx/WhMgr/master/install.bat install.bat | start install.bat -``` -2.) Edit `config.json` either open in Notepad/++ or `vi config.json`. - a.) [Create bot token](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token) - b.) Input your bot token and config options. -```js -{ - // Http listener port for raw webhook data. - "port": 8008, - // Locale language translation - "locale": "en", - // ShortURL API (yourls.org API, i.e. `https://domain.com/yourls-api.php?signature=XXXXXX`) - "shortUrlApiUrl": null, - // Stripe API key (Stripe production API key, i.e. rk_3824802934 - "stripeApiKey": "" - // List of Discord servers to connect and post webhook messages to. - "servers": { - // Discord server #1 guild ID - "000000000000000001": { - // Bot command prefix, leave blank to use @mention - "commandPrefix": ".", - // Discord server server ID. - "guildId": 000000000000000001, - // Discord Emoji server ID. (Can be same as `guildId`) - "emojiGuildId": 000000000000000001, - // Discord server owner ID. - "ownerId": 000000000000000000, - // Donor/Supporter role ID(s). - "donorRoleIds": [ - 000000000000000000 - ], - // Moderator Discord ID(s). - "moderatorIds": [ - 000000000000000000 - ], - // Discord bot token with user. - "token": "", - // Alarms file path. - "alarms": "alarms.json", - // Enable custom direct message notification subscriptions. - "enableSubscriptions": false, - // Enable city role assignments. - "enableCities": false, - // City/geofence role(s) - "cityRoles": [ - "City1", - "City2" - ], - // Assigning city roles requires Donor/Supporter role. - "citiesRequireSupporterRole": true, - // Prune old field research quests at midnight. - "pruneQuestChannels": true, - // Channel ID(s) of quest channels to prune at midnight. - "questChannelIds": [ - 000000000000000000 - ], - // Channel ID to post nests. - "nestsChannelId": 000000000000000000, - // Shiny stats configuration - "shinyStats": { - // Enable shiny stats posting. - "enabled": true, - // Clear previous shiny stat messages. - "clearMessages": false, - // Channel ID to post shiny stats. - "channelId": 000000000000000000 - }, - // Icon style to use. - "iconStyle": "Default", - // Channel ID(s) bot commands can be executed in. - "botChannelIds": [ - 000000000000000000 - ], - // Custom Discord status per server, leave blank or null to use current version. - "status": "" - }, - "000000000000000002": { - "commandPrefix": ".", - "guildId": 000000000000000001, - "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, - "shinyStats": { - "enabled": true, - "clearMessages": false, - "channelId": 000000000000000000 - }, - "iconStyle": "Default", - "botChannelIds": [ - 000000000000000000 - ], - "status": null - } - }, - // Database configuration - "database": { - // Database to store notification subscriptions. - "main": { - // Database hostname or IP address. - "host": "127.0.0.1", - // Database connection port. - "port": 3306, - // Database user account name. - "username": "root", - // Database user account password. - "password": "password", - // Brock database name. - "database": "brock3" - }, - // Scanner databse config - "scanner": { - // Database hostname or IP address. - "host": "127.0.0.1", - // Database connection port. - "port": 3306, - // Database user account name. - "username": "root", - // Database user account password. - "password": "password", - // RDM database name. - "database": "rdmdb" - }, - // PMSF Nests database config - "nests": { - // Database hostname or IP address. - "host": "127.0.0.1", - // Database connection port. - "port": 3306, - // Database user account name. - "username": "root", - // Database user account password. - "password": "password", - // PMSF nests database name. - "database": "manualdb" - } - }, - // List of Pokemon IDs to treat as event and restrict postings and subscriptions to 90% IV or higher. (Filled in automatically with `event set` command) - "eventPokemonIds": [ - 129, - 456, - 320 - ], - // 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" - }, - // Available icon styles - "iconStyles": { - "Default": "https://raw.githubusercontent.com/versx/WhMgr-Assets/master/original/", - "Shuffle": "https://raw.githubusercontent.com/versx/WhMgr-Assets/master/shuffle/" - }, - "staticMaps": { - "pokemon": "pokemon.example.json", - "raids": "raids.example.json", - "quests": "quests.example.json", - "invasions": "invasions.example.json", - "lures": "lures.example.json", - "gyms": "gyms.example.json", - "nests": "nests.example.json", - "weather": "weather.example.json" - }, - // Get text message alerts with Twilio.com - "twilio": { - // Determines if text message alerts are be enabled - "enabled": false, - // Twilio account SID (Get via Twilio dashboard) - "accountSid": "", - // Twilio account auth token (Get via Twilio dashboard) - "authToken": "", - // Twilio phone number that will be sending the text message alert - "from": "", - // List of Discord user ids that can receive text message alerts - "userIds": "", - // List of acceptable Pokemon to receive text message alerts from - "pokemonIds": [201, 480, 481, 482, 443, 444, 445, 633, 634, 635, 610, 611, 612], - // Minimum acceptable IV value for Pokemon to be if not ultra rare (Unown, Lake Trio) - "minIV": 100 - }, - // Log webhook payloads to a file for debugging - "debug": false, - // Only show logs with higher or equal priority levels - "logLevel": "Trace" -} -``` -3.) Edit `alarms.json` either open in Notepad/++ or `vi alarms.json`. -4.) Fill out the alarms file. -```js -{ - //Global switch for Pokemon notifications. - "enablePokemon": false, - - //Global switch for Raid/Egg notifications. - "enableRaids": false, - - //Global switch for Quest notifications. - "enableQuests": false, - - //Global switch for Pokestop notifications. - "enablePokestops": false, - - //Global switch for Gym notifications. - "enableGyms": false, - - //Global switch for Weather notifications. - "enableWeather": false, - - //List of alarms - "alarms": [{ - //Alarm name. - "name":"Alarm1", - - //Alerts file. - "alerts":"default.json", - - //Alarm filters. - "filters":"default.json", - - //Path to geofence file. - "geofence":"geofence1.txt", - - //DTS compatible mention description. - "mentions":" L " - - //Discord webhook url address. - "webhook":"" - },{ - //Alarm name. - "name":"Alarm2", - - //Alerts file. - "alerts":"default.json", - - //Alarm filters. - "filters":"100iv.json", - - //Path to geofence file. - "geofence":"geofence1.txt", - - //DTS compatible mention description. - "mentions":"" - - //Discord webhook url address. - "webhook":"" - }] -} -``` -5.) Create directory `geofences` in `bin/debug/netcoreapp2.1` directory if it doesn't already exist. -6.) Create/copy geofence files to `geofences` folder. - -*Note:* Geofence file format is the following: -```ini -[City1] -34.00,-117.00 -34.01,-117.01 -34.02,-117.02 -34.03,-117.03 -[City2] -33.00,-118.00 -33.01,-118.01 -33.02,-118.02 -33.03,-118.03 -``` -7.) Add dotnet to your environment path if it isn't already (optional): `export PATH=~/.dotnet/dotnet:$PATH` -8.) Build executable `dotnet build ../../..` (if dotnet is in your path) otherwise `~/.dotnet/dotnet build ../../..` -9.) Start WhMgr `dotnet WhMgr.dll` (if dotnet is in your path) otherwise `~/.dotnet/dotnet WhMgr.dll` (If Windows, run as Administrator) -10.) Optional User Interface for members to create subscriptions from a website instead of using Discord commands. (Still WIP but mostly done) [WhMgr UI](https://github.com/versx/WhMgr-UI) - -## Updating -1.) Pull latest changes in root folder -2.) Build project `dotnet build` -3.) Run `dotnet bin/debug/netcoreapp2.1/WhMgr.dll` - -**Important Notes:** -- Upon starting, database tables will be automatically created if `enableSubscriptions` is set to `true`. Emoji icons are also created in the specified `EmojiGuildId` upon connecting to Discord. -- Discord Permissions Needed: - * Read Messages - * Send Messages - * Manage Messages (Prune quest channels) - * Manage Roles (If cities are enabled) - * Manage Emojis - * Embed Links - * Attach Files (`export` command) - * Use External Emojis -- DM notifications can be sent to users based on: - - Pokemon ID - - Pokemon Form - - Pokemon IV - - Pokemon Level - - List of Pokemon Attack/Defense/Stamina values - - Pokemon Gender - - Raid Boss - - City - - Gym Name - - Quest Reward - - Invasion Grunt Type - - Distance (meters) - -## Notification Commands -**General Subscription Commands** - -* `enable` Enable direct message subscriptions -* `disable` Disable direct message subscriptions -* `info` List all Pokemon, Raid, Quest, Invasion, and Gym subscriptions and settings -* `set-distance` Set minimum distance to Pokemon, raids, quests, invasions and gyms need to be within. (Measured in meters) -* `expire` / `expires` Check stripe API when Donor/Supporter subscription expires - -**Pokemon Subscriptions** -* `pokeme` Subscribe to specific Pokemon notifications -* `pokemenot` Unsubscribe from specific Pokemon notifications - -**PVP Subscriptions** -* `pvpme` Subscription to specific PVP Pokemon notifications -* `pvpmenot` Unsubscribe from specific PVP Pokemon notifications - -**Raid Subscriptions** -* `raidme` Subscribe to specific Raid notifications -* `raidmenot` Unsubscribe from specific Raid notifications - -**Quest Subscriptions** -* `questme` Subscribe to specific field research quest notifications -* `questmenot` Unsubscribe from specific field research quest notifications - -**Team Rocket Invasion Subscriptions** -* `invme` Subscribe to specific Team Rocket invasion notifications -* `invmenot` Unsubscribe from specific Team Rocket invasion notifications - -**Subscriptions Management** -* `import` Import saved subscriptions file -* `export` Export subscriptions config file - -**Icon Style Selection** -* `icons` List available icon styles to choose from -* `set-icons` Set icon style to use for direct message notifications - -**City Role Assignment** -* `cities` / `feeds` List all available city roles -* `feedme` Assign city role -* `feedmenot` Unassign city role - -## Owner Only Commands -* `gyms convert` Check for any pokestops that have converted to gyms and delete them from the database. -* `nests` Post nests in channels. -* `event list` List Pokemon set as event Pokemon -* `event set ` Set Pokemon as event Pokemon (overwrites current list) -* `isbanned` Check if IP banned from PTC or NIA -* `clean-departed` Clean departed Discord member subscriptions -* `reset-quests` Reset and delete quest channels -* `shiny-stats` Manually post shiny stats - -## Dynamic Text Replacement -__**Pokemon**__ - -| Place Holder | Description | Example -|---|---|---| -| pkmn_id | Pokedex ID | 1 -| pkmn_id_3 | Pokedex ID (always 3 digits) | 001 -| pkmn_name | Pokemon name | Bulbasaur -| pkmn_img_url | Pokemon image url | http://example.com/your-specified-pokemon-url -| form | Pokemon form name | Alolan -| form_id | Form ID | 65 -| form_id_3 | Form ID (always 3 digits) | 065 -| costume | Costume name | Witch Hat -| costume_id | Costume ID | 835 -| costume_id_3 | Costume ID (always 3 digits) | 835 -| cp | Combat Power value | 1525 -| lvl | Pokemon level | 25 -| gender | Pokemon gender | Gender icon -| gender_emoji | Pokemon gender emoji | <:00000:gender_male> -| size | Pokemon size | Big -| move_1 | Fast move name | Quick Attack -| move_2 | Charge move name | Thunder -| moveset | Fast & Charge move names | Quick Attack/Thunder -| type_1 | Pokemon type | Dark -| type_2 | Pokemon type | Water -| type_1_emoji | Pokemon type emoji | <:00000:types_water> -| type_2_emoji | Pokemon type emoji | <:00000:types_rock> -| types | Both types (if 2nd exists) | Dark/Fire -| types_emoji | Type Discord emoji | <:00000:types_fire> <00001:types_dark> -| atk_iv | Attack IV stat | 15 -| def_iv | Defense IV stat | 7 -| sta_iv | Stamina IV stat | 13 -| iv | IV stat (including percent sign) | 100% -| iv_rnd | Rounded IV stat | 96% -| is_great | Great League stats (bool) | true -| is_ultra | Ultra League stats (bool) | false -| is_pvp | Has either Great or Ultra league stats | true -| great_league_emoji | Great League emoji icon | <000000:league_great> -| ultra_league_emoji | Ultra League emoji icon | <000000:league_ultra> -| pvp_stats | PvP stat ranking strings | -| height | Pokemon height | 0.79 -| weight | Pokemon weight | 116 -| is_ditto | Checks if Ditto | true -| original_pkmn_id | Pokedex ID of Ditto disguise | 13 -| original_pkmn_id_3 | Pokedex ID of Ditto disguise (always 3 digits) | 013 -| original_pkmn_name | Pokemon name of Ditto diguise | Weedle -| is_weather_boosted | Returns if Pokemon is weather boosted | true -| has_weather | Returns if Pokemon data has weather | false -| weather | Weather in-game name | PartlyCloudy -| weather_emoji | Weather in-game emoji | Weather -| username | Account username of account that found Pokemon | Frank0324 -| spawnpoint_id | Spawnpoint ID Pokemon near | 3920849203840983204980 -| encounter_id | Encounter ID of Pokemon | 392874987239487924 -| despawn_time | Pokemon despawn time | 07:33:01 PM -| despawn_time_verified | Indicates if time is confirmed or not | `~` for not verified -| is_despawn_time_verified | Returns if despawn time is verified | true -| time_left | Minutes and seconds of time left until despawn | 29m, 30s -| geofence | Geofence name Pokemon is in | City1 -| lat | Latitude coordinate of Pokemon location | 5.980921321 -| lng | Longitude coordinate of Pokemon location | 3.109283009 -| lat_5 | Latitude coordinate shortend to 5th precision | 5.98092 -| lng_5 | Longitude coordinate shortend to 5th precision | 3.10928 -| tilemaps_url | Static tile map url | http://tiles.example.com/static/pokemon-1.png -| gmaps_url | Google maps location url | https://maps.google.com/maps?q=5.980921321,3.109283009 -| applemaps_url | Apple maps location url | https://maps.apple.com/maps?daddr=5.980921321,3.109283009 -| wazemaps_url | Waze maps location url | https://www.waze.com/ul?ll=5.980921321,3.109283009&navigate=yes -| near_pokestop | Returns if Pokemon is near a Pokestop | true -| pokestop_id | Nearby Pokestop ID | 9382498723849792348798234.16 -| pokestop_name | Name of nearby Pokestop | The Amazing Pokestop -| pokestop_url | Image url of nearby Pokestop | https://google.com/imgs/gym.png -| guild_name | Name of Guild | Test Guild -| guild_img_url | Icon image url of Guild | https://discordapp.com/image1.png -[ date_time | Current date and time | 12/12/2020 12:12:12 PM -| br | Newline break | `\r\n` - -__**Raids & Eggs**__ - -| Place Holder | Description | Example -|---|---|---| -| pkmn_id | Raid boss pokedex ID | 1 -| pkmn_id_3 | Raid boss pokedex ID (always 3 digits) | 001 -| pkmn_name | Raid boss pokemon name | Bulbasaur -| pkmn_img_url | Raid boss pokemon image url | http://example.com/your-specified-pokemon-url -| form | Pokemon form name | Alolan -| form_id | Form ID | 65 -| form_id_3 | Form ID (always 3 digits) | 065 -| is_egg | Returns if raid is egg and not hatched | false -| is_ex | Returns if raid is ex pass eligible | true -| ex_emoji | Ex emoji icon | Ex -| team | Team name that has gym control | Valor -| team_emoji | Emoji of team that has gym control | <:valor:930824> -| cp | Raid boss combat power value | 36150 -| lvl | Raid boss level | 5 -| gender | Pokemon gender | Gender icon -| move_1 | Fast move name | Quick Attack -| move_2 | Charge move name | Thunder -| moveset | Fast & Charge move names | Quick Attack/Thunder -| type_1 | Pokemon type | Dark -| type_2 | Pokemon type | Water -| type_1_emoji | Pokemon type emoji | <:00000:types_water> -| type_2_emoji | Pokemon type emoji | <:00000:types_rock> -| types | Both types (if 2nd exists) | Dark/Fire -| types_emoji | Type Discord emoji | <:00000:types_fire> <00001:types_dark> -| weaknesses | Raid boss weaknesses | Rock, Ground, Dark -| weaknesses_emoji | Emoji(s) of raid boss weaknesses | Rock Ground Dark -| perfect_cp | Perfect IV CP | 1831 -| perfect_cp_boosted | Perfect IV CP if Weather boosted | 2351 -| worst_cp | Worst IV CP | 1530 -| worst_cp_boosted | Worst IV CP if Weather boosted | 1339 -| start_time | Raid start time | 08:32:00 AM -| start_time_left | Time left until raid starts | 43m, 33s -| end_time | Raid end time | 09:15:10 AM -| end_time_left | Time left until raid ends | 45, 11s -| time_left | Minutes and seconds of time left until despawn | 29m, 30s -| geofence | Geofence name raid boss is in | City1 -| lat | Latitude coordinate of Pokemon location | 5.980921321 -| lng | Longitude coordinate of Pokemon location | 3.109283009 -| lat_5 | Latitude coordinate shortend to 5th precision | 5.98092 -| lng_5 | Longitude coordinate shortend to 5th precision | 3.10928 -| tilemaps_url | Static tile map url | http://tiles.example.com/static/pokemon-1.png -| gmaps_url | Google maps location url | https://maps.google.com/maps?q=5.980921321,3.109283009 -| applemaps_url | Apple maps location url | https://maps.apple.com/maps?daddr=5.980921321,3.109283009 -| wazemaps_url | Waze maps location url | https://www.waze.com/ul?ll=5.980921321,3.109283009&navigate=yes -| gym_id | Gym ID | 9382498723849792348798234.16 -| gym_name | Name of Gym | The Amazing Gym -| gym_url | Image url of Gym | https://google.com/imgs/gym.png -| guild_name | Name of Guild | Test Guild -| guild_img_url | Icon image url of Guild | https://discordapp.com/image1.png -[ date_time | Current date and time | 12/12/2020 12:12:12 PM -| br | Newline break | `\r\n` - -__**Quests**__ - -| Place Holder | Description | Example -|---|---|---| -| quest_task | Quest task message | Catch 5 Pokemon -| quest_conditions | Quest task conditions | Dark -| quest_reward | Quest task reward | Chansey -| quest_reward_img_url | Quest reward image url | http://map.example.com/images/quest.png -| has_quest_conditions | Returns if the quest has conditions | true -| is_ditto | Checks if Ditto | true -| is_shiny | Checks if reward is shiny | false -| geofence | Geofence name raid boss is in | City1 -| lat | Latitude coordinate of Pokemon location | 5.980921321 -| lng | Longitude coordinate of Pokemon location | 3.109283009 -| lat_5 | Latitude coordinate shortend to 5th precision | 5.98092 -| lng_5 | Longitude coordinate shortend to 5th precision | 3.10928 -| tilemaps_url | Static tile map url | http://tiles.example.com/static/pokemon-1.png -| gmaps_url | Google maps location url | https://maps.google.com/maps?q=5.980921321,3.109283009 -| applemaps_url | Apple maps location url | https://maps.apple.com/maps?daddr=5.980921321,3.109283009 -| wazemaps_url | Waze maps location url | https://www.waze.com/ul?ll=5.980921321,3.109283009&navigate=yes -| pokestop_id | Pokestop ID | 9382498723849792348798234.16 -| pokestop_name | Name of Pokestop | The Amazing Pokestop -| pokestop_url | Image url of Gym | https://google.com/imgs/gym.png -| guild_name | Name of Guild | Test Guild -| guild_img_url | Icon image url of Guild | https://discordapp.com/image1.png -[ date_time | Current date and time | 12/12/2020 12:12:12 PM -| br | Newline break | `\r\n` - -**Pokestops** - -| Place Holder | Description | Example -|---|---|---| -| has_lure | Returns if Pokestop has active lure module deployed | true -| lure_type | Pokestop lure module type | Glacial -| lure_expire_time | Time lure module will expire | 07:33:19 PM -| lure_expire_time_left | Time left until lure module expires | 13m, 2s -| has_invasion | Returns if Pokestop has active Team Rocket invasion | false -| grunt_type | Grunt type | Water -| grunt_type_emoji | Emoji icon of grunt type | <:938294:types_water> -| grunt_gender | Grunt gender | Male -| invasion_expire_time | Time the invasion expires | 02:17:11 PM -| invasion_expire_time_left | Time left until invasion expires | 12m, 56s -| invasion_encounters | Possible invasions reward encounters | 80% Bulbasaur -| geofence | Geofence name raid boss is in | City1 -| lat | Latitude coordinate of Pokemon location | 5.980921321 -| lng | Longitude coordinate of Pokemon location | 3.109283009 -| lat_5 | Latitude coordinate shortend to 5th precision | 5.98092 -| lng_5 | Longitude coordinate shortend to 5th precision | 3.10928 -| tilemaps_url | Static tile map url | http://tiles.example.com/static/pokemon-1.png -| gmaps_url | Google maps location url | https://maps.google.com/maps?q=5.980921321,3.109283009 -| applemaps_url | Apple maps location url | https://maps.apple.com/maps?daddr=5.980921321,3.109283009 -| wazemaps_url | Waze maps location url | https://www.waze.com/ul?ll=5.980921321,3.109283009&navigate=yes -| pokestop_id | Pokestop ID | 9382498723849792348798234.16 -| pokestop_name | Name of Pokestop | The Amazing Pokestop -| pokestop_url | Image url of Gym | https://google.com/imgs/gym.png -| lure_img_url | Image url of lure icon | https://google.com/imgs/lure_501.png -| invasion_img_url | Image url of grunt type icon | https://google.com/imgs/grunt_50.png -| guild_name | Name of Guild | Test Guild -| guild_img_url | Icon image url of Guild | https://discordapp.com/image1.png -[ date_time | Current date and time | 12/12/2020 12:12:12 PM -| br | Newline break | `\r\n` - -**Gyms** - -| Place Holder | Description | Example -|---|---|---| -| gym_id | Gym ID | 032840982304982034.16 -| gym_name | Name of Gym | The Amazing Gym -| gym_url | Image url of Gym | https://google.com/imgs/gym.png -| gym_team | Current team that has gym control | Valor -| gym_team_emoji | Emoji icon of current team that has gym control | <:09833:valor> -| old_gym_team | Previous gym team that had gym control | Mystic -| old_gym_team_emoji | Emoji icon of previous gym team that has gym control | <:324987:mystic> -| team_changed | Returns if team's gym control changed | true -| in_battle | Returns if there's a current battle at the gym taking place | false -| under_attack | Returns if there's a current battle at the gym taking place | false -| is_ex | Returns if the gym is an ex raid eligible location | true -| ex_emoji | Ex emoji icon | <:809809:ex> -| slots_available | Number of available gym slots | 3 -| geofence | Geofence name raid boss is in | City1 -| lat | Latitude coordinate of Pokemon location | 5.980921321 -| lng | Longitude coordinate of Pokemon location | 3.109283009 -| lat_5 | Latitude coordinate shortend to 5th precision | 5.98092 -| lng_5 | Longitude coordinate shortend to 5th precision | 3.10928 -| tilemaps_url | Static tile map url | http://tiles.example.com/static/pokemon-1.png -| gmaps_url | Google maps location url | https://maps.google.com/maps?q=5.980921321,3.109283009 -| applemaps_url | Apple maps location url | https://maps.apple.com/maps?daddr=5.980921321,3.109283009 -| wazemaps_url | Waze maps location url | https://www.waze.com/ul?ll=5.980921321,3.109283009&navigate=yes -| guild_name | Name of Guild | Test Guild -| guild_img_url | Icon image url of Guild | https://discordapp.com/image1.png -[ date_time | Current date and time | 12/12/2020 12:12:12 PM -| br | Newline break | `\r\n` - -**Nests** - -| Place Holder | Description | Example -|---|---|---| -| pkmn_id | Pokedex ID | 1 -| pkmn_id_3 | Pokedex ID (always 3 digits) | 001 -| pkmn_name | Pokemon name | Bulbasaur -| pkmn_img_url | Pokemon image url | http://example.com/your-specified-pokemon-url -| avg_spawns | Average amount of spawns in the nests | 34 -| nest_name | Nest/Park name | Best Park Ever -| type_1 | Pokemon type | Dark -| type_2 | Pokemon type | Water -| type_1_emoji | Pokemon type emoji | <:00000:types_water> -| type_2_emoji | Pokemon type emoji | <:00000:types_rock> -| types | Both types (if 2nd exists) | Dark/Fire -| types_emoji | Type Discord emoji | <:00000:types_fire> <00001:types_dark> -| geofence | Geofence name nest/park is in | City1 -| lat | Latitude coordinate of Pokemon location | 5.980921321 -| lng | Longitude coordinate of S2Cell weather location | 3.109283009 -| lat_5 | Latitude coordinate shortend to 5th precision | 5.98092 -| lng_5 | Longitude coordinate shortend to 5th precision | 3.10928 -| tilemaps_url | Static tile map url | http://tiles.example.com/static/pokemon-1.png -| gmaps_url | Google maps location url | https://maps.google.com/maps?q=5.980921321,3.109283009 -| applemaps_url | Apple maps location url | https://maps.apple.com/maps?daddr=5.980921321,3.109283009 -| wazemaps_url | Waze maps location url | https://www.waze.com/ul?ll=5.980921321,3.109283009&navigate=yes -[ date_time | Current date and time | 12/12/2020 12:12:12 PM -| br | Newline break | `\r\n` - - -**Weather** - -| Place Holder | Description | Example -|---|---|---| -| id | S2Cell weather id | -9938028402 -| weather_condition | In-game gameplay condition | Cloudy -| has_weather | Returns if there is weather set | true -| weather | In-game gameplay condition | Cloudy -| weather_img_url | Weather type image url | http://google.com/imgs/weather_1.png -| wind_direction | Wind blowing direction | true -| wind_level | Wind level | 285 -| rain_level | Raid level | 285 -| cloud_level | Cloud level | 285 -| fog_level | Fog level | 285 -| snow_level | Snow level | 285 -| warn_weather | Warning weather | true -| special_effect_level | Special effect level | 2 -| severity | Weather severity | None/Moderate/Extreme -| geofence | Geofence name weather cell is in | City1 -| lat | Latitude coordinate of S2Cell weather location | 5.980921321 -| lng | Longitude coordinate of S2Cell weather location | 3.109283009 -| lat_5 | Latitude coordinate shortend to 5th precision | 5.98092 -| lng_5 | Longitude coordinate shortend to 5th precision | 3.10928 -| tilemaps_url | Static tile map url | http://tiles.example.com/static/pokemon-1.png -| gmaps_url | Google maps location url | https://maps.google.com/maps?q=5.980921321,3.109283009 -| applemaps_url | Apple maps location url | https://maps.apple.com/maps?daddr=5.980921321,3.109283009 -| wazemaps_url | Waze maps location url | https://www.waze.com/ul?ll=5.980921321,3.109283009&navigate=yes -| guild_name | Name of Guild | Test Guild -| guild_img_url | Icon image url of Guild | https://discordapp.com/image1.png -[ date_time | Current date and time | 12/12/2020 12:12:12 PM -| br | Newline break | `\r\n` - - -## TODO: -- Allow Pokemon id and name in Pokemon filter lists. -- Individual filters per Pokemon. (PA style, maybe) -- PvP ranks DTS -- Separate subscriptions DTS -- Wiki. - - -## Examples: -*All examples are completely customizable using Dynamic Text Replacement/Substitution* -Discord Pokemon Notifications: -![Pokemon Notifications](images/pkmn.png "Pokemon Notifications") - -Discord Raid Notifications: -![Raid Notifications](images/raid.png "Raid Notifications") - -Discord Raid Egg Notifications: -![Egg Notifications](images/egg.png "Egg Notifications") - -Discord Quest Notifications: -![Quest Notifications](images/quests.png "Quest Notifications") - -Discord Lure Notifications: -![Lure Notifications](images/lure.png "Lure Notifications") - -Discord Lure (Glacial) Notifications: -![Lure (Glacial) Notifications](images/lure_glacial.png "Lure (Glacial) Notifications") - -Discord Lure (Mossy) Notifications: -![Lure (Mossy) Notifications](images/lure_mossy.png "Lure (Mossy) Notifications") - -Discord Lure (Magnetic) Notifications: -![Lure (Magnetic) Notifications](images/lure_magnetic.png "Lure (Magnetic) Notifications") - -Discord Gym Team Takeover Notifications: -![Gym Team Takeover Notifications](images/gyms.png "Gym Team Takeover Notifications") - -Discord Team Rocket Invasion Notifications: -![Team Rocket Invasion Notifications](images/invasions.png "Team Rocket Invasion Notifications") - -## Current Issues: - -## Credits: -[versx](https://github.com/versx) - Developer -[PokeAlarm](https://github.com/PokeAlarm/PokeAlarm) - Dynamic Text Substitution idea -[WDR](https://github.com/PartTimeJS/WDR) - masterfile.json file - -## Discord -https://discordapp.com/invite/zZ9h9Xa +# Brock Webhook Manager v4.0 + +### PokeAlarm, Poracle, WDR, etc alternative. +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, gym team changes, and weather. Also supports Discord user's subscribing to Pokemon, raid, quest, and Team Rocket invasion 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. +- User interface to configure Discord notifications with ease (as well as Discord commands). (https://github.com/versx/WhMgr-UI) +- Notifications based on pre-defined distance. +- Customizable alert messages with dynamic text replacement. +- Support for multiple cities/areas using roles and geofences per server. +- Daily shiny stats reporting. +- Automatic quest message purge at midnight. +- Support for Donors/Supporters only notifications. +- Direct messages of Pokemon notifications based on city roles assigned. +- Custom prefix support as well as mentionable user support for commands. +- Subscriptions based on distance from a set location or specific gym names. +- Twilio text message alerts for ultra rare Pokemon. +- Custom image support for Discord alarm reports. +- Custom icon style selection for Discord user notifications. +- External emoji server support. +- Custom static map format support. +- Support for language translation. +- Multi threaded, low processing consumption. +- Lots more... + +## Documentation: +[ReadTheDocs](https://whmgr.rtfd.io/) + +## Getting Started: + +1.) Run the following to install .NET Core runtime, clone respository, and copy example Alerts, Filters, Geofences, config and alarm files. +``` +Linux/macOS: +wget https://raw.githubusercontent.com/versx/WhMgr/master/install.sh && chmod +x install.sh && ./install.sh && rm install.sh + +Windows: +bitsadmin /transfer dotnet-install-job /download /priority FOREGROUND https://raw.githubusercontent.com/versx/WhMgr/master/install.bat install.bat | start install.bat +``` +2.) Edit `config.json` either open in Notepad/++ or `vi config.json`. + a.) [Create bot token](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token) + b.) Input your bot token and config options. +```js +{ + // Http listening interface for raw webhook data. + "host": "10.0.0.10", + // Http listener port for raw webhook data. + "port": 8008, + // Locale language translation + "locale": "en", + // ShortURL API (yourls.org API, i.e. `https://domain.com/yourls-api.php?signature=XXXXXX`) + "shortUrlApiUrl": null, + // Stripe API key (Stripe production API key, i.e. rk_3824802934 + "stripeApiKey": "" + // List of Discord servers to connect and post webhook messages to. + "servers": { + // Discord server #1 guild ID + "000000000000000001": { + // Bot command prefix, leave blank to use @mention + "commandPrefix": ".", + // Discord server server ID. + "guildId": 000000000000000001, + // Discord Emoji server ID. (Can be same as `guildId`) + "emojiGuildId": 000000000000000001, + // Discord server owner ID. + "ownerId": 000000000000000000, + // Donor/Supporter role ID(s). + "donorRoleIds": [ + 000000000000000000 + ], + // Moderator Discord ID(s). + "moderatorIds": [ + 000000000000000000 + ], + // Discord bot token with user. + "token": "", + // Alarms file path. + "alarms": "alarms.json", + // Enable custom direct message notification subscriptions. + "enableSubscriptions": false, + // Enable city role assignments. + "enableCities": false, + // City/geofence role(s) + "cityRoles": [ + "City1", + "City2" + ], + // Assigning city roles requires Donor/Supporter role. + "citiesRequireSupporterRole": true, + // Prune old field research quests at midnight. + "pruneQuestChannels": true, + // Channel ID(s) of quest channels to prune at midnight. + "questChannelIds": [ + 000000000000000000 + ], + // Channel ID to post nests. + "nestsChannelId": 000000000000000000, + // Shiny stats configuration + "shinyStats": { + // Enable shiny stats posting. + "enabled": true, + // Clear previous shiny stat messages. + "clearMessages": false, + // Channel ID to post shiny stats. + "channelId": 000000000000000000 + }, + // Icon style to use. + "iconStyle": "Default", + // Channel ID(s) bot commands can be executed in. + "botChannelIds": [ + 000000000000000000 + ], + // Custom Discord status per server, leave blank or null to use current version. + "status": "" + }, + "000000000000000002": { + "commandPrefix": ".", + "guildId": 000000000000000001, + "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, + "shinyStats": { + "enabled": true, + "clearMessages": false, + "channelId": 000000000000000000 + }, + "iconStyle": "Default", + "botChannelIds": [ + 000000000000000000 + ], + "status": null + } + }, + // Database configuration + "database": { + // Database to store notification subscriptions. + "main": { + // Database hostname or IP address. + "host": "127.0.0.1", + // Database connection port. + "port": 3306, + // Database user account name. + "username": "root", + // Database user account password. + "password": "password", + // Brock database name. + "database": "brock3" + }, + // Scanner database config + "scanner": { + // Database hostname or IP address. + "host": "127.0.0.1", + // Database connection port. + "port": 3306, + // Database user account name. + "username": "root", + // Database user account password. + "password": "password", + // RDM database name. + "database": "rdmdb" + }, + // PMSF Nests database config + "nests": { + // Database hostname or IP address. + "host": "127.0.0.1", + // Database connection port. + "port": 3306, + // Database user account name. + "username": "root", + // Database user account password. + "password": "password", + // PMSF nests database name. + "database": "manualdb" + } + }, + // List of Pokemon IDs to treat as event and restrict postings and subscriptions to 90% IV or higher. (Filled in automatically with `event set` command) + "eventPokemonIds": [ + 129, + 456, + 320 + ], + // 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" + }, + // Available icon styles + "iconStyles": { + "Default": "https://raw.githubusercontent.com/versx/WhMgr-Assets/master/original/", + "Shuffle": "https://raw.githubusercontent.com/versx/WhMgr-Assets/master/shuffle/" + }, + // Custom static map template files for each alarm type + "staticMaps": { + // Static map template for Pokemon + "pokemon": "pokemon.example.json", + // Static map template for Raids and Eggs + "raids": "raids.example.json", + // Static map template for field research quests + "quests": "quests.example.json", + // Static map template for Team Rocket invasions + "invasions": "invasions.example.json", + // Static map template for Pokestop lures + "lures": "lures.example.json", + // Static map template for Gym team control changes + "gyms": "gyms.example.json", + // Static map template for nest postings + "nests": "nests.example.json", + // Static map template for weather changes + "weather": "weather.example.json" + }, + // Get text message alerts with Twilio.com + "twilio": { + // Determines if text message alerts are enabled + "enabled": false, + // Twilio account SID (Get via Twilio dashboard) + "accountSid": "", + // Twilio account auth token (Get via Twilio dashboard) + "authToken": "", + // Twilio phone number that will be sending the text message alert + "from": "", + // List of Discord user ids that can receive text message alerts + "userIds": "", + // List of acceptable Pokemon to receive text message alerts for + "pokemonIds": [201, 480, 481, 482, 443, 444, 445, 633, 634, 635, 610, 611, 612], + // Minimum acceptable IV value for Pokemon if not ultra rare (Unown, Lake Trio) + "minIV": 100 + }, + // Log webhook payloads to a file for debugging + "debug": false, + // Only show logs with higher or equal priority levels (Trace, Debug, Info, Warning, Error, Fatal, None) + "logLevel": "Trace" +} +``` +3.) Edit `alarms.json` either open in Notepad/++ or `vi alarms.json`. +4.) Fill out the alarms file. +```js +{ + //Global switch for Pokemon notifications. + "enablePokemon": false, + + //Global switch for Raid/Egg notifications. + "enableRaids": false, + + //Global switch for Quest notifications. + "enableQuests": false, + + //Global switch for Pokestop notifications. + "enablePokestops": false, + + //Global switch for Gym notifications. + "enableGyms": false, + + //Global switch for Weather notifications. + "enableWeather": false, + + //List of alarms + "alarms": [{ + //Alarm name. + "name":"Alarm1", + + //DTS compatible mention description. + "description":" L ", + + //Alerts file. + "alerts":"default.json", + + //Alarm filters. + "filters":"default.json", + + //Path to geofence file. + "geofence":"geofence1.txt", + + //Discord webhook url address. + "webhook":"" + },{ + //Alarm name. + "name":"Alarm2", + + //DTS compatible mention description. + "description":"", + + //Alerts file. + "alerts":"default.json", + + //Alarm filters. + "filters":"100iv.json", + + //Path to geofence file. + "geofence":"geofence1.txt", + + //Discord webhook url address. + "webhook":"" + }] +} +``` +5.) Create directory `geofences` in `bin/debug/netcoreapp2.1` directory if it doesn't already exist. +6.) Create/copy geofence files to `geofences` folder. + +*Note:* Geofence file format is the following: +```ini +[City1] +34.00,-117.00 +34.01,-117.01 +34.02,-117.02 +34.03,-117.03 +[City2] +33.00,-118.00 +33.01,-118.01 +33.02,-118.02 +33.03,-118.03 +``` +7.) Add dotnet to your environment path if it isn't already (optional): `export PATH=~/.dotnet/dotnet:$PATH` +8.) Build executable `dotnet build ../../..` (if dotnet is in your path) otherwise `~/.dotnet/dotnet build ../../..` +9.) Start WhMgr `dotnet WhMgr.dll` (if dotnet is in your path) otherwise `~/.dotnet/dotnet WhMgr.dll` (If Windows, run as Administrator) +10.) Optional User Interface for members to create subscriptions from a website instead of using Discord commands. (Still WIP but mostly done) [WhMgr UI](https://github.com/versx/WhMgr-UI) + +## Updating +1.) Pull latest changes in root folder +2.) Build project `dotnet build` +3.) Run `dotnet bin/debug/netcoreapp2.1/WhMgr.dll` + +**Important Notes:** +- Upon starting, database tables will be automatically created if `enableSubscriptions` is set to `true`. Emoji icons are also created in the specified `EmojiGuildId` upon connecting to Discord. +- Discord Permissions Needed: + * Read Messages + * Send Messages + * Manage Messages (Prune quest channels) + * Manage Roles (If cities are enabled) + * Manage Emojis + * Embed Links + * Attach Files (`export` command) + * Use External Emojis +- DM notifications can be sent to users based on: + - Pokemon ID + - Pokemon Form + - Pokemon IV + - Pokemon Level + - List of Pokemon Attack/Defense/Stamina values + - Pokemon Gender + - Raid Boss + - City + - Gym Name + - Quest Reward + - Invasion Grunt Type + - Distance (meters) + +## Notification Commands +**General Subscription Commands** + +* `enable` Enable direct message subscriptions +* `disable` Disable direct message subscriptions +* `info` List all Pokemon, Raid, Quest, Invasion, and Gym subscriptions and settings +* `set-distance` Set minimum distance to Pokemon, raids, quests, invasions and gyms need to be within. (Measured in meters) +* `expire` / `expires` Check stripe API when Donor/Supporter subscription expires +* `set-number` Sets the phone number to use for text message alerts for ultra rare Pokemon + +**Pokemon Subscriptions** +* `pokeme` Subscribe to specific Pokemon notifications +* `pokemenot` Unsubscribe from specific Pokemon notifications + +**PVP Subscriptions** +* `pvpme` Subscription to specific PVP Pokemon notifications +* `pvpmenot` Unsubscribe from specific PVP Pokemon notifications + +**Raid Subscriptions** +* `raidme` Subscribe to specific Raid notifications +* `raidmenot` Unsubscribe from specific Raid notifications + +**Quest Subscriptions** +* `questme` Subscribe to specific field research quest notifications +* `questmenot` Unsubscribe from specific field research quest notifications + +**Team Rocket Invasion Subscriptions** +* `invme` Subscribe to specific Team Rocket invasion notifications +* `invmenot` Unsubscribe from specific Team Rocket invasion notifications + +**Subscriptions Management** +* `import` Import saved subscriptions file +* `export` Export subscriptions config file + +**Icon Style Selection** +* `icons` List available icon styles to choose from +* `set-icons` Set icon style to use for direct message notifications + +**City Role Assignment** +* `cities` / `feeds` List all available city roles +* `feedme` Assign city role +* `feedmenot` Unassign city role + +## Owner Only Commands +* `gyms convert` Check for any pokestops that have converted to gyms and delete them from the database. +* `nests` Post nests in channels. +* `event list` List Pokemon set as event Pokemon +* `event set ` Set Pokemon as event Pokemon (overwrites current list) +* `isbanned` Check if IP banned from PTC or NIA +* `clean-departed` Clean departed Discord member subscriptions +* `reset-quests` Reset and delete quest channels +* `shiny-stats` Manually post shiny stats + +## Dynamic Text Replacement +__**Pokemon**__ + +| Place Holder | Description | Example +|---|---|---| +| pkmn_id | Pokedex ID | 1 +| pkmn_id_3 | Pokedex ID (always 3 digits) | 001 +| pkmn_name | Pokemon name | Bulbasaur +| pkmn_img_url | Pokemon image url | http://example.com/your-specified-pokemon-url +| form | Pokemon form name | Alolan +| form_id | Form ID | 65 +| form_id_3 | Form ID (always 3 digits) | 065 +| costume | Costume name | Witch Hat +| costume_id | Costume ID | 835 +| costume_id_3 | Costume ID (always 3 digits) | 835 +| cp | Combat Power value | 1525 +| lvl | Pokemon level | 25 +| gender | Pokemon gender | Gender icon +| gender_emoji | Pokemon gender emoji | <:00000:gender_male> +| size | Pokemon size | Big +| move_1 | Fast move name | Quick Attack +| move_2 | Charge move name | Thunder +| moveset | Fast & Charge move names | Quick Attack/Thunder +| type_1 | Pokemon type | Dark +| type_2 | Pokemon type | Water +| type_1_emoji | Pokemon type emoji | <:00000:types_water> +| type_2_emoji | Pokemon type emoji | <:00000:types_rock> +| types | Both types (if 2nd exists) | Dark/Fire +| types_emoji | Type Discord emoji | <:00000:types_fire> <00001:types_dark> +| atk_iv | Attack IV stat | 15 +| def_iv | Defense IV stat | 7 +| sta_iv | Stamina IV stat | 13 +| iv | IV stat (including percent sign) | 100% +| iv_rnd | Rounded IV stat | 96% +| is_great | Great League stats (bool) | true +| is_ultra | Ultra League stats (bool) | false +| is_pvp | Has either Great or Ultra league stats | true +| great_league_emoji | Great League emoji icon | <000000:league_great> +| ultra_league_emoji | Ultra League emoji icon | <000000:league_ultra> +| pvp_stats | PvP stat ranking strings | +| height | Pokemon height | 0.79 +| weight | Pokemon weight | 116 +| is_ditto | Checks if Ditto | true +| original_pkmn_id | Pokedex ID of Ditto disguise | 13 +| original_pkmn_id_3 | Pokedex ID of Ditto disguise (always 3 digits) | 013 +| original_pkmn_name | Pokemon name of Ditto diguise | Weedle +| is_weather_boosted | Returns if Pokemon is weather boosted | true +| has_weather | Returns if Pokemon data has weather | false +| weather | Weather in-game name | PartlyCloudy +| weather_emoji | Weather in-game emoji | Weather +| username | Account username of account that found Pokemon | Frank0324 +| spawnpoint_id | Spawnpoint ID Pokemon near | 3920849203840983204980 +| encounter_id | Encounter ID of Pokemon | 392874987239487924 +| despawn_time | Pokemon despawn time | 07:33:01 PM +| despawn_time_verified | Indicates if time is confirmed or not | `~` for not verified +| is_despawn_time_verified | Returns if despawn time is verified | true +| time_left | Minutes and seconds of time left until despawn | 29m, 30s +| geofence | Geofence name Pokemon is in | City1 +| lat | Latitude coordinate of Pokemon location | 5.980921321 +| lng | Longitude coordinate of Pokemon location | 3.109283009 +| lat_5 | Latitude coordinate shortend to 5th precision | 5.98092 +| lng_5 | Longitude coordinate shortend to 5th precision | 3.10928 +| tilemaps_url | Static tile map url | http://tiles.example.com/static/pokemon-1.png +| gmaps_url | Google maps location url | https://maps.google.com/maps?q=5.980921321,3.109283009 +| applemaps_url | Apple maps location url | https://maps.apple.com/maps?daddr=5.980921321,3.109283009 +| wazemaps_url | Waze maps location url | https://www.waze.com/ul?ll=5.980921321,3.109283009&navigate=yes +| near_pokestop | Returns if Pokemon is near a Pokestop | true +| pokestop_id | Nearby Pokestop ID | 9382498723849792348798234.16 +| pokestop_name | Name of nearby Pokestop | The Amazing Pokestop +| pokestop_url | Image url of nearby Pokestop | https://google.com/imgs/gym.png +| guild_name | Name of Guild | Test Guild +| guild_img_url | Icon image url of Guild | https://discordapp.com/image1.png +[ date_time | Current date and time | 12/12/2020 12:12:12 PM +| br | Newline break | `\r\n` + +__**Raids & Eggs**__ + +| Place Holder | Description | Example +|---|---|---| +| pkmn_id | Raid boss pokedex ID | 1 +| pkmn_id_3 | Raid boss pokedex ID (always 3 digits) | 001 +| pkmn_name | Raid boss pokemon name | Bulbasaur +| pkmn_img_url | Raid boss pokemon image url | http://example.com/your-specified-pokemon-url +| form | Pokemon form name | Alolan +| form_id | Form ID | 65 +| form_id_3 | Form ID (always 3 digits) | 065 +| is_egg | Returns if raid is egg and not hatched | false +| is_ex | Returns if raid is ex pass eligible | true +| ex_emoji | Ex emoji icon | Ex +| team | Team name that has gym control | Valor +| team_emoji | Emoji of team that has gym control | <:valor:930824> +| cp | Raid boss combat power value | 36150 +| lvl | Raid boss level | 5 +| gender | Pokemon gender | Gender icon +| move_1 | Fast move name | Quick Attack +| move_2 | Charge move name | Thunder +| moveset | Fast & Charge move names | Quick Attack/Thunder +| type_1 | Pokemon type | Dark +| type_2 | Pokemon type | Water +| type_1_emoji | Pokemon type emoji | <:00000:types_water> +| type_2_emoji | Pokemon type emoji | <:00000:types_rock> +| types | Both types (if 2nd exists) | Dark/Fire +| types_emoji | Type Discord emoji | <:00000:types_fire> <00001:types_dark> +| weaknesses | Raid boss weaknesses | Rock, Ground, Dark +| weaknesses_emoji | Emoji(s) of raid boss weaknesses | Rock Ground Dark +| perfect_cp | Perfect IV CP | 1831 +| perfect_cp_boosted | Perfect IV CP if Weather boosted | 2351 +| worst_cp | Worst IV CP | 1530 +| worst_cp_boosted | Worst IV CP if Weather boosted | 1339 +| start_time | Raid start time | 08:32:00 AM +| start_time_left | Time left until raid starts | 43m, 33s +| end_time | Raid end time | 09:15:10 AM +| end_time_left | Time left until raid ends | 45, 11s +| time_left | Minutes and seconds of time left until despawn | 29m, 30s +| geofence | Geofence name raid boss is in | City1 +| lat | Latitude coordinate of Pokemon location | 5.980921321 +| lng | Longitude coordinate of Pokemon location | 3.109283009 +| lat_5 | Latitude coordinate shortend to 5th precision | 5.98092 +| lng_5 | Longitude coordinate shortend to 5th precision | 3.10928 +| tilemaps_url | Static tile map url | http://tiles.example.com/static/pokemon-1.png +| gmaps_url | Google maps location url | https://maps.google.com/maps?q=5.980921321,3.109283009 +| applemaps_url | Apple maps location url | https://maps.apple.com/maps?daddr=5.980921321,3.109283009 +| wazemaps_url | Waze maps location url | https://www.waze.com/ul?ll=5.980921321,3.109283009&navigate=yes +| gym_id | Gym ID | 9382498723849792348798234.16 +| gym_name | Name of Gym | The Amazing Gym +| gym_url | Image url of Gym | https://google.com/imgs/gym.png +| guild_name | Name of Guild | Test Guild +| guild_img_url | Icon image url of Guild | https://discordapp.com/image1.png +[ date_time | Current date and time | 12/12/2020 12:12:12 PM +| br | Newline break | `\r\n` + +__**Quests**__ + +| Place Holder | Description | Example +|---|---|---| +| quest_task | Quest task message | Catch 5 Pokemon +| quest_conditions | Quest task conditions | Dark +| quest_reward | Quest task reward | Chansey +| quest_reward_img_url | Quest reward image url | http://map.example.com/images/quest.png +| has_quest_conditions | Returns if the quest has conditions | true +| is_ditto | Checks if Ditto | true +| is_shiny | Checks if reward is shiny | false +| geofence | Geofence name raid boss is in | City1 +| lat | Latitude coordinate of Pokemon location | 5.980921321 +| lng | Longitude coordinate of Pokemon location | 3.109283009 +| lat_5 | Latitude coordinate shortend to 5th precision | 5.98092 +| lng_5 | Longitude coordinate shortend to 5th precision | 3.10928 +| tilemaps_url | Static tile map url | http://tiles.example.com/static/pokemon-1.png +| gmaps_url | Google maps location url | https://maps.google.com/maps?q=5.980921321,3.109283009 +| applemaps_url | Apple maps location url | https://maps.apple.com/maps?daddr=5.980921321,3.109283009 +| wazemaps_url | Waze maps location url | https://www.waze.com/ul?ll=5.980921321,3.109283009&navigate=yes +| pokestop_id | Pokestop ID | 9382498723849792348798234.16 +| pokestop_name | Name of Pokestop | The Amazing Pokestop +| pokestop_url | Image url of Gym | https://google.com/imgs/gym.png +| guild_name | Name of Guild | Test Guild +| guild_img_url | Icon image url of Guild | https://discordapp.com/image1.png +[ date_time | Current date and time | 12/12/2020 12:12:12 PM +| br | Newline break | `\r\n` + +**Pokestops** + +| Place Holder | Description | Example +|---|---|---| +| has_lure | Returns if Pokestop has active lure module deployed | true +| lure_type | Pokestop lure module type | Glacial +| lure_expire_time | Time lure module will expire | 07:33:19 PM +| lure_expire_time_left | Time left until lure module expires | 13m, 2s +| has_invasion | Returns if Pokestop has active Team Rocket invasion | false +| grunt_type | Grunt type | Water +| grunt_type_emoji | Emoji icon of grunt type | <:938294:types_water> +| grunt_gender | Grunt gender | Male +| invasion_expire_time | Time the invasion expires | 02:17:11 PM +| invasion_expire_time_left | Time left until invasion expires | 12m, 56s +| invasion_encounters | Possible invasions reward encounters | 80% Bulbasaur +| geofence | Geofence name raid boss is in | City1 +| lat | Latitude coordinate of Pokemon location | 5.980921321 +| lng | Longitude coordinate of Pokemon location | 3.109283009 +| lat_5 | Latitude coordinate shortend to 5th precision | 5.98092 +| lng_5 | Longitude coordinate shortend to 5th precision | 3.10928 +| tilemaps_url | Static tile map url | http://tiles.example.com/static/pokemon-1.png +| gmaps_url | Google maps location url | https://maps.google.com/maps?q=5.980921321,3.109283009 +| applemaps_url | Apple maps location url | https://maps.apple.com/maps?daddr=5.980921321,3.109283009 +| wazemaps_url | Waze maps location url | https://www.waze.com/ul?ll=5.980921321,3.109283009&navigate=yes +| pokestop_id | Pokestop ID | 9382498723849792348798234.16 +| pokestop_name | Name of Pokestop | The Amazing Pokestop +| pokestop_url | Image url of Gym | https://google.com/imgs/gym.png +| lure_img_url | Image url of lure icon | https://google.com/imgs/lure_501.png +| invasion_img_url | Image url of grunt type icon | https://google.com/imgs/grunt_50.png +| guild_name | Name of Guild | Test Guild +| guild_img_url | Icon image url of Guild | https://discordapp.com/image1.png +[ date_time | Current date and time | 12/12/2020 12:12:12 PM +| br | Newline break | `\r\n` + +**Gyms** + +| Place Holder | Description | Example +|---|---|---| +| gym_id | Gym ID | 032840982304982034.16 +| gym_name | Name of Gym | The Amazing Gym +| gym_url | Image url of Gym | https://google.com/imgs/gym.png +| gym_team | Current team that has gym control | Valor +| gym_team_emoji | Emoji icon of current team that has gym control | <:09833:valor> +| old_gym_team | Previous gym team that had gym control | Mystic +| old_gym_team_emoji | Emoji icon of previous gym team that has gym control | <:324987:mystic> +| team_changed | Returns if team's gym control changed | true +| in_battle | Returns if there's a current battle at the gym taking place | false +| under_attack | Returns if there's a current battle at the gym taking place | false +| is_ex | Returns if the gym is an ex raid eligible location | true +| ex_emoji | Ex emoji icon | <:809809:ex> +| slots_available | Number of available gym slots | 3 +| geofence | Geofence name raid boss is in | City1 +| lat | Latitude coordinate of Pokemon location | 5.980921321 +| lng | Longitude coordinate of Pokemon location | 3.109283009 +| lat_5 | Latitude coordinate shortend to 5th precision | 5.98092 +| lng_5 | Longitude coordinate shortend to 5th precision | 3.10928 +| tilemaps_url | Static tile map url | http://tiles.example.com/static/pokemon-1.png +| gmaps_url | Google maps location url | https://maps.google.com/maps?q=5.980921321,3.109283009 +| applemaps_url | Apple maps location url | https://maps.apple.com/maps?daddr=5.980921321,3.109283009 +| wazemaps_url | Waze maps location url | https://www.waze.com/ul?ll=5.980921321,3.109283009&navigate=yes +| guild_name | Name of Guild | Test Guild +| guild_img_url | Icon image url of Guild | https://discordapp.com/image1.png +[ date_time | Current date and time | 12/12/2020 12:12:12 PM +| br | Newline break | `\r\n` + +**Nests** + +| Place Holder | Description | Example +|---|---|---| +| pkmn_id | Pokedex ID | 1 +| pkmn_id_3 | Pokedex ID (always 3 digits) | 001 +| pkmn_name | Pokemon name | Bulbasaur +| pkmn_img_url | Pokemon image url | http://example.com/your-specified-pokemon-url +| avg_spawns | Average amount of spawns in the nests | 34 +| nest_name | Nest/Park name | Best Park Ever +| type_1 | Pokemon type | Dark +| type_2 | Pokemon type | Water +| type_1_emoji | Pokemon type emoji | <:00000:types_water> +| type_2_emoji | Pokemon type emoji | <:00000:types_rock> +| types | Both types (if 2nd exists) | Dark/Fire +| types_emoji | Type Discord emoji | <:00000:types_fire> <00001:types_dark> +| geofence | Geofence name nest/park is in | City1 +| lat | Latitude coordinate of Pokemon location | 5.980921321 +| lng | Longitude coordinate of S2Cell weather location | 3.109283009 +| lat_5 | Latitude coordinate shortend to 5th precision | 5.98092 +| lng_5 | Longitude coordinate shortend to 5th precision | 3.10928 +| tilemaps_url | Static tile map url | http://tiles.example.com/static/pokemon-1.png +| gmaps_url | Google maps location url | https://maps.google.com/maps?q=5.980921321,3.109283009 +| applemaps_url | Apple maps location url | https://maps.apple.com/maps?daddr=5.980921321,3.109283009 +| wazemaps_url | Waze maps location url | https://www.waze.com/ul?ll=5.980921321,3.109283009&navigate=yes +[ date_time | Current date and time | 12/12/2020 12:12:12 PM +| br | Newline break | `\r\n` + + +**Weather** + +| Place Holder | Description | Example +|---|---|---| +| id | S2Cell weather id | -9938028402 +| weather_condition | In-game gameplay condition | Cloudy +| has_weather | Returns if there is weather set | true +| weather | In-game gameplay condition | Cloudy +| weather_img_url | Weather type image url | http://google.com/imgs/weather_1.png +| wind_direction | Wind blowing direction | true +| wind_level | Wind level | 285 +| rain_level | Raid level | 285 +| cloud_level | Cloud level | 285 +| fog_level | Fog level | 285 +| snow_level | Snow level | 285 +| warn_weather | Warning weather | true +| special_effect_level | Special effect level | 2 +| severity | Weather severity | None/Moderate/Extreme +| geofence | Geofence name weather cell is in | City1 +| lat | Latitude coordinate of S2Cell weather location | 5.980921321 +| lng | Longitude coordinate of S2Cell weather location | 3.109283009 +| lat_5 | Latitude coordinate shortend to 5th precision | 5.98092 +| lng_5 | Longitude coordinate shortend to 5th precision | 3.10928 +| tilemaps_url | Static tile map url | http://tiles.example.com/static/pokemon-1.png +| gmaps_url | Google maps location url | https://maps.google.com/maps?q=5.980921321,3.109283009 +| applemaps_url | Apple maps location url | https://maps.apple.com/maps?daddr=5.980921321,3.109283009 +| wazemaps_url | Waze maps location url | https://www.waze.com/ul?ll=5.980921321,3.109283009&navigate=yes +| guild_name | Name of Guild | Test Guild +| guild_img_url | Icon image url of Guild | https://discordapp.com/image1.png +[ date_time | Current date and time | 12/12/2020 12:12:12 PM +| br | Newline break | `\r\n` + + +## TODO: +- Allow Pokemon id and name in Pokemon filter lists. +- Individual filters per Pokemon. (PA style, maybe) +- PvP ranks DTS +- Separate subscriptions DTS +- Wiki. + + +## Examples: +*All examples are completely customizable using Dynamic Text Replacement/Substitution* +Discord Pokemon Notifications: +![Pokemon Notifications](images/pkmn.png "Pokemon Notifications") + +Discord Raid Notifications: +![Raid Notifications](images/raid.png "Raid Notifications") + +Discord Raid Egg Notifications: +![Egg Notifications](images/egg.png "Egg Notifications") + +Discord Quest Notifications: +![Quest Notifications](images/quests.png "Quest Notifications") + +Discord Lure Notifications: +![Lure Notifications](images/lure.png "Lure Notifications") + +Discord Lure (Glacial) Notifications: +![Lure (Glacial) Notifications](images/lure_glacial.png "Lure (Glacial) Notifications") + +Discord Lure (Mossy) Notifications: +![Lure (Mossy) Notifications](images/lure_mossy.png "Lure (Mossy) Notifications") + +Discord Lure (Magnetic) Notifications: +![Lure (Magnetic) Notifications](images/lure_magnetic.png "Lure (Magnetic) Notifications") + +Discord Gym Team Takeover Notifications: +![Gym Team Takeover Notifications](images/gyms.png "Gym Team Takeover Notifications") + +Discord Team Rocket Invasion Notifications: +![Team Rocket Invasion Notifications](images/invasions.png "Team Rocket Invasion Notifications") + +## Current Issues: +- Pokemon subscriptions are based on Discord city roles assigned currently, soon it will be based on specified cities. + +## Credits: +[versx](https://github.com/versx) - Developer +[PokeAlarm](https://github.com/PokeAlarm/PokeAlarm) - Dynamic Text Substitution idea +[WDR](https://github.com/PartTimeJS/WDR) - masterfile.json file + +## Discord +https://discordapp.com/invite/zZ9h9Xa diff --git a/alarms.example.json b/alarms.example.json index 4f169f7c..96e46bdf 100644 --- a/alarms.example.json +++ b/alarms.example.json @@ -9,13 +9,16 @@ [ { "name":"City1-Rare", + "description": " L ", "alerts": "default.json", "filters":"all.json", "geofence":"City1.txt", + "mentions":" L ", "webhook":"" }, { "name":"City1-100iv", + "description": "", "alerts": "default.json", "filters":"100iv.json", "geofence":"City1.txt", @@ -23,6 +26,7 @@ }, { "name":"City1-Raids", + "description": "", "alerts": "default.json", "filters":"raids.json", "geofence":"City1.txt", @@ -30,6 +34,7 @@ }, { "name":"City1-LegendaryRaids", + "description": "", "alerts": "default.json", "filters":"legendary_raids.json", "geofence":"City1.txt", @@ -37,6 +42,7 @@ }, { "name":"City1-ExRaids", + "description": "", "alerts": "default.json", "filters":"ex_raids.json", "geofence":"City1.txt", @@ -44,6 +50,7 @@ }, { "name": "City1-Quests", + "description": "", "alerts": "default.json", "filters": "quests.json", "geofence": "City1.txt", @@ -51,6 +58,7 @@ }, { "name": "City1-Lures", + "description": "", "alerts": "default.json", "filters": "lures.json", "geofence": "City1.txt", @@ -58,6 +66,7 @@ }, { "name": "City1-Invasions", + "description": "", "alerts": "default.json", "filters": "invasions.json", "geofence": "City1.txt", @@ -65,20 +74,23 @@ }, { "name": "City1-Gyms", + "description": "", "alerts": "default.json", "filters": "gyms.json", "geofence": "City1.txt", "webhook":"" }, { - "name":"City2-Weather", + "name":"City1-Weather", + "description": "", "alerts": "default.json", "filters":"weather.json", - "geofence":"City2.txt", + "geofence":"City1.txt", "webhook":"" }, { "name":"City2-Rare", + "description": "", "alerts": "default.json", "filters":"all.json", "geofence":"City2.txt", @@ -86,6 +98,7 @@ }, { "name":"City2-100iv", + "description": "", "alerts": "default.json", "filters":"100iv.json", "geofence":"City2.txt", @@ -93,6 +106,7 @@ }, { "name":"City2-Raids", + "description": "", "alerts": "default.json", "filters":"raids.json", "geofence":"City2.txt", @@ -100,6 +114,7 @@ }, { "name":"City2-LegendaryRaids", + "description": "", "alerts": "default.json", "filters":"legendary_raids.json", "geofence":"City2.txt", @@ -107,6 +122,7 @@ }, { "name":"City2-ExRaids", + "description": "", "alerts": "default.json", "filters":"ex_raids.json", "geofence":"City2.txt", @@ -114,6 +130,7 @@ }, { "name": "City2-Quests", + "description": "", "alerts": "default.json", "filters": "quests.json", "geofence": "City2.txt", @@ -121,6 +138,7 @@ }, { "name": "City2-Lures", + "description": "", "alerts": "default.json", "filters": "lures.json", "geofence": "City2.txt", @@ -128,6 +146,7 @@ }, { "name": "City2-Invasions", + "description": "", "alerts": "default.json", "filters": "invasions.json", "geofence": "City2.txt", @@ -135,6 +154,7 @@ }, { "name": "City2-Gyms", + "description": "", "alerts": "default.json", "filters": "gyms.json", "geofence": "City2.txt", @@ -142,6 +162,7 @@ }, { "name":"Absol-Quests", + "description": "", "alerts": "default.json", "filters":"quests_absol.json", "geofence":"City2.txt", @@ -149,6 +170,7 @@ }, { "name":"City2-Weather", + "description": "", "alerts": "default.json", "filters":"weather.json", "geofence":"City2.txt", diff --git a/config.example.json b/config.example.json index ff217511..810a6bd7 100644 --- a/config.example.json +++ b/config.example.json @@ -107,7 +107,8 @@ 320 ], "urls": { - "staticMap": "http://tiles.example.com:8080/static/klokantech-basic/{0}/{1}/15/300/175/1/png" + "staticMap": "http://tiles.example.com:8080/static/klokantech-basic/{0}/{1}/15/300/175/1/png", + "scannerMap": "http://map.example.com/@/{0}/{1}/15" }, "iconStyles": { "Default": "https://raw.githubusercontent.com/versx/WhMgr-Assets/master/original/", diff --git a/docs/commands/subscriptions.md b/docs/commands/subscriptions.md index 10ac3f57..44bcc9a9 100644 --- a/docs/commands/subscriptions.md +++ b/docs/commands/subscriptions.md @@ -6,6 +6,8 @@ **disable** - Disable direct message subscription notifications. **info** - List all Pokemon, Raid, Quest, Invasion, and Gym subscriptions and settings. **expire** / **expires** - Check stripe API when Donor/Supporter subscription expires. +**set-number** - Set a phone number to receive text message alerts for ultra rare Pokemon. + **set-distance** - Set minimum distance to Pokemon, raids, quests, invasions and gyms need to be within. (Measured in kilometers) Usage: `set-distance ,` @@ -23,7 +25,7 @@ Examples: Usage: `pokeme [iv] [level] [gender]` * `` - Parameter can take a list of Ids or names or the `all` keyword for everything. -* `` - (Optional) Minimum IV value. +* `` - (Optional) Minimum IV value, or individual attack, defense, and stamina values i.e. `0-14-15` * `` - (Optional) Minimum level value. * `` - (Optional) Specific gender `m` or `f` or `*` for all. @@ -31,6 +33,7 @@ Examples: * `.pokeme tyranitar` * `.pokeme pikachu 100 35 f` +* `.pokeme Skarmory 0-15-15 12` * `.pokeme pikachu 100` * `.pokeme all 100 35`
@@ -135,29 +138,27 @@ Examples: ### Team Rocket Invasions **invme** - Subscribe to specific Team Rocket invasion notifications. -Usage: `invme - [city]` +Usage: `invme [city]` -* `` - Grunt Pokemon type i.e. `fire`, `water` -* `` - Grunt gender i.e. `male` | `m` | `female` | `f` +* `` - Reward Pokemon i.e. `Dratini`, `147` * `[city]` - (Optional) City name to get the notifications for or leave blank for all available cities. Examples: -* `.invme tier2-f` -* `.invme ground-male city1` +* `.invme Beldum` +* `.invme beldum city1`
**invmenot** - Unsubscribe from specific Team Rocket invasion notifications. -Usage: `invmenot - [city]` +Usage: `invmenot [city]` -* `` - Grunt Pokemon type i.e. `fire`, `water` -* `` - Grunt gender i.e. `male` | `m` | `female` | `f` +* `` - Pokemon reward i.e. `Pikachu`, `25` * `[city]` - (Optional) City name to get the notifications for or leave blank for all available cities. Examples: -* `.invmenot tier2-f` -* `.invmenot ground-male city1` +* `.invmenot Bulbasaur` +* `.invmenot Dratini city1` * `.invmenot all`
diff --git a/docs/index.md b/docs/index.md index f546b198..5543df75 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,6 +2,7 @@ Works with [RealDeviceMap](https://github.com/123FLO321/RealDeviceMap) +Made in C#, runs on .NET Core CLR. Cross platform compatibility, can run on Windows, macOS, and Linux operating systems. Parses and sends Discord notifications based on pre-defined filters for Pokemon, raids, raid eggs, field research quests, gym team control changes, and weather changes. Also supports Discord user's subscribing to Pokemon, raid, quest, or Team Rocket invasion notifications via direct messages. ## Features: @@ -28,12 +29,15 @@ Parses and sends Discord notifications based on pre-defined filters for Pokemon, - Lots more... ### Frameworks and Libraries -- .NET Framework v4.6.2 -- DSharpPlus v3.2.3 -- ServiceStack.OrmLite.MySql v5.7.0 -- Stripe.net v34.1.0 +- .NET Core v2.1.803 +- DSharpPlus v3.2.3 +- DSharpPlus.CommandsNext v3.2.3 +- DSharpPlus.Interactivity v3.2.3 +- Microsoft.Win32.SystemEvents v4.7.0 - Newtonsoft.Json v12.0.3 -- MySql.Data v8.0.18 +- ServiceStack.OrmLite.MySql v5.8.0 +- Stripe.net v37.14.0 +- Twilio v5.44.0 [Click here](user-guide/config) to get started! diff --git a/docs/user-guide/alarms.md b/docs/user-guide/alarms.md index d013bd0e..918f2e6e 100644 --- a/docs/user-guide/alarms.md +++ b/docs/user-guide/alarms.md @@ -17,6 +17,8 @@ There is no limit to the amount of alarms you can add under the `alarms` propert "enablePokestops": true, //Enable or disable Gym alarms globally "enableGyms": true, + //Enable or disable Weather alarms globally + "enableWeather": true, //List of alarms "alarms": [ @@ -27,6 +29,8 @@ There is no limit to the amount of alarms you can add under the `alarms` propert "alerts": "default.json", //Alarm filters "filters":"all.json", + //Mentionable string that supports DTS + "mentions":" L ", //Geofence file name "geofence":"City1.txt", //Discord webhook url address diff --git a/docs/user-guide/filters.md b/docs/user-guide/filters.md index 5df91862..229ebbf0 100644 --- a/docs/user-guide/filters.md +++ b/docs/user-guide/filters.md @@ -16,6 +16,8 @@ Filters allow you to narrow down what is reported. All filters are options and c "size": "Big", //Tiny, Small, Normal, Large, Big "great_league": true, //Great League "ultra_league": true, //Ultra League + "min_rank": 1, //Minimum rank of #1 PVP stats + "max_rank": 5, //Maximum rank of #5 PVP stats "type": "Include", //Include or Exclude the `pokemon` list "ignoreMissing": true //Ignore Pokemon missing stats }, @@ -40,8 +42,8 @@ Filters allow you to narrow down what is reported. All filters are options and c { "enabled": true, //Filter is enabled "rewards": ["spinda", "nincada"], //Quest reward string (Chansey, stardust, candy, etc.) - "type": "Include", //Include or Exclude the `rewards` list - "isShiny": false //Only shiny encounter quests. + "isShiny": false, //Only shiny encounter quests. + "type": "Include" //Include or Exclude the `rewards` list }, "pokestops": { @@ -51,7 +53,14 @@ Filters allow you to narrow down what is reported. All filters are options and c }, "gyms": { - "enabled": true //Filter is enabled + "enabled": true, //Filter is enabled + "underAttack": true, //Only gyms that are under attack + "team": "All" //Team change to notify about (i.e. Neutral/Mystic/Valor/Instinct/All) + }, + "weather": + { + "enabled": true, //Filter is enabled + "types": ["Clear", "Rain", "PartlyCloudy", "Cloudy", "Windy", "Snow", "Fog"] //Only send weather types that are in the list } } ``` \ No newline at end of file diff --git a/examples/Templates/invasions.example.json b/examples/Templates/invasions.example.json deleted file mode 100644 index 7a5b4d64..00000000 --- a/examples/Templates/invasions.example.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "staticmap_url": "{{tilemaps_url}}", - "markers": [{ - "url": "{{marker}}", - "height": "32", - "width": "32", - "x_offset": 0, - "y_offset": 0, - "latitude": "{{lat}}", - "longitude": "{{lon}}" - }] -} \ No newline at end of file diff --git a/examples/Templates/lures.example.json b/examples/Templates/lures.example.json deleted file mode 100644 index 7a5b4d64..00000000 --- a/examples/Templates/lures.example.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "staticmap_url": "{{tilemaps_url}}", - "markers": [{ - "url": "{{marker}}", - "height": "32", - "width": "32", - "x_offset": 0, - "y_offset": 0, - "latitude": "{{lat}}", - "longitude": "{{lon}}" - }] -} \ No newline at end of file diff --git a/examples/Templates/raids.example.json b/examples/Templates/raids.example.json deleted file mode 100644 index 0d50b8ca..00000000 --- a/examples/Templates/raids.example.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "staticmap_url": "{{tilemaps_url}}", - "markers": [{ - "url": "{{pkmn_img_url}}", - "height": "32", - "width": "32", - "x_offset": 0, - "y_offset": 0, - "latitude": "{{lat}}", - "longitude": "{{lon}}" - }] -} \ No newline at end of file diff --git a/examples/Alerts/default.json b/examples/alerts/default.json similarity index 80% rename from examples/Alerts/default.json rename to examples/alerts/default.json index fbf67c53..11a5d8c1 100644 --- a/examples/Alerts/default.json +++ b/examples/alerts/default.json @@ -1,7 +1,7 @@ { "pokemon": { "avatarUrl": "", - "content": "
(//) L
**Despawn:** ( left)
**Details:** CP: IV: LV:
**Types:** | **Size:** <#has_weather> | <#is_weather_boosted> (Boosted)
**Moveset:**
<#near_pokestop>**Near Pokestop:** []()
<#is_ditto>**Catch Pokemon:**
<#has_capture_rates> % % %
<#is_pvp>
**[[Google Maps]()] [[Apple Maps]()] [[Waze Maps]()]**", + "content": " (//) L
**Despawn:** ( left)
**Details:** CP: IV: LV:
**Types:** | **Size:** <#has_weather> | <#is_weather_boosted> (Boosted)
**Moveset:**
<#near_pokestop>**Near Pokestop:** []()
<#is_ditto>**Catch Pokemon:**
<#has_capture_rates> % % %
<#is_pvp>
**[[Google]()] [[Apple]()] [[Waze]()] [[Scanner]()]**", "iconUrl": "", "title": "", "url": "", @@ -14,7 +14,7 @@ }, "pokemonMissingStats": { "avatarUrl": "", - "content": "
**Despawn:** ( left)
**Types:**
<#near_pokestop>**Near Pokestop:** []()
**[[Google Maps]()] [[Apple Maps]()] [[Waze Maps]()]**", + "content": "
**Despawn:** ( left)
**Types:**
<#near_pokestop>**Near Pokestop:** []()
**[[Google]()] [[Apple]()] [[Waze]()] [[Scanner]()]**", "iconUrl": "", "title": "", "url": "", @@ -27,7 +27,7 @@ }, "gyms": { "avatarUrl": "", - "content": "<#team_changed>Gym changed from to
<#in_battle>Gym is under attack!
**Slots Available:**
<#is_ex> Gym!**[[Google Maps]()] [[Apple Maps]()] [[Waze Maps]()]**", + "content": "<#team_changed>Gym changed from to
<#in_battle>Gym is under attack!
**Slots Available:**
<#is_ex> Gym!**[[Google]()] [[Apple]()] [[Waze]()] [[Scanner]()]**", "iconUrl": "", "title": ": ", "url": "", @@ -40,7 +40,7 @@ }, "raids": { "avatarUrl": "", - "content": " Raid Ends: ( left)
**Perfect CP:** / :white_sun_rain_cloud:
**Worst CP:** / :white_sun_rain_cloud:
**Types:** | **Level:** | **Team:**
**Moveset:**
**Weaknesses:**
<#is_ex> Gym!
**[[Google Maps]()] [[Apple Maps]()] [[Waze Maps]()]**", + "content": " Raid Ends: ( left)
**Perfect CP:** / :white_sun_rain_cloud:
**Worst CP:** / :white_sun_rain_cloud:
**Types:** | **Level:** | **Team:**
**Moveset:**
**Weaknesses:**
<#is_ex> Gym!
**[[Google]()] [[Apple]()] [[Waze]()] [[Scanner]()]**", "iconUrl": "", "title": ": ", "url": "", @@ -53,7 +53,7 @@ }, "eggs": { "avatarUrl": "", - "content": "Hatches: ()
**Ends:** ( left)
**Team:**
<#is_ex> Gym!
**[[Google Maps]()] [[Apple Maps]()] [[Waze Maps]()]**", + "content": "Hatches: ()
**Ends:** ( left)
**Team:**
<#is_ex> Gym!
**[[Google]()] [[Apple]()] [[Waze]()] [[Scanner]()]**", "iconUrl": "", "title": ": ", "url": "", @@ -66,7 +66,7 @@ }, "pokestops": { "avatarUrl": "", - "content": "<#has_lure>**Lure Expires** ( left)
**Lure Type:**
<#has_invasion>**Expires:** ( left)
**Type:** | **Gender:**

**[[Google Maps]()] [[Apple Maps]()] [[Waze Maps]()]**", + "content": "<#has_lure>**Lure Expires** ( left)
**Lure Type:**
<#has_invasion>**Expires:** ( left)
**Type:** | **Gender:**

**[[Google]()] [[Apple]()] [[Waze]()] [[Scanner]()]**", "iconUrl": "", "title": ": ", "url": "", @@ -79,7 +79,7 @@ }, "quests": { "avatarUrl": "", - "content": "**Quest:**
<#has_quest_conditions>**Condition(s):**
**Reward:**
**[[Google Maps]()] [[Apple Maps]()] [[Waze Maps]()]**", + "content": "**Quest:**
<#has_quest_conditions>**Condition(s):**
**Reward:**
**[[Google]()] [[Apple]()] [[Waze]()] [[Scanner]()]**", "iconUrl": "", "title": ": ", "url": "", @@ -92,7 +92,7 @@ }, "lures": { "avatarUrl": "", - "content": "<#has_lure>**Lure Expires:** ( left)
**Lure Type:**
**[[Google Maps]()] [[Apple Maps]()] [[Waze Maps]()]**", + "content": "<#has_lure>**Lure Expires:** ( left)
**Lure Type:**
**[[Google]()] [[Apple]()] [[Waze]()] [[Scanner]()]**", "iconUrl": "", "title": ": ", "url": "", @@ -105,7 +105,7 @@ }, "invasions": { "avatarUrl": "", - "content": "<#has_invasion>**Expires:** ( left)
**Type:** | **Gender:**

**[[Google Maps]()] [[Apple Maps]()] [[Waze Maps]()]**", + "content": "<#has_invasion>**Expires:** ( left)
**Type:** | **Gender:**

**[[Google]()] [[Apple]()] [[Waze]()] [[Scanner]()]**", "iconUrl": "", "title": ": ", "url": "", @@ -118,7 +118,7 @@ }, "nests": { "avatarUrl": "", - "content": "**Pokemon:**
**Average Spawns:** /h | **Types:**
**[[Google Maps]()] [[Apple Maps]()] [[Waze Maps]()]**", + "content": "**Pokemon:**
**Average Spawns:** /h | **Types:**
**[[Google]()] [[Apple]()] [[Waze]()] [[Scanner]()]**", "iconUrl": "", "title": ": ", "url": "", diff --git a/examples/Filters/0iv.json b/examples/filters/0iv.json similarity index 100% rename from examples/Filters/0iv.json rename to examples/filters/0iv.json diff --git a/examples/Filters/100iv.json b/examples/filters/100iv.json similarity index 100% rename from examples/Filters/100iv.json rename to examples/filters/100iv.json diff --git a/examples/Filters/100iv_basura.json b/examples/filters/100iv_basura.json similarity index 100% rename from examples/Filters/100iv_basura.json rename to examples/filters/100iv_basura.json diff --git a/examples/Filters/100iv_lvl30.json b/examples/filters/100iv_lvl30.json similarity index 100% rename from examples/Filters/100iv_lvl30.json rename to examples/filters/100iv_lvl30.json diff --git a/examples/Filters/100iv_lvl35.json b/examples/filters/100iv_lvl35.json similarity index 100% rename from examples/Filters/100iv_lvl35.json rename to examples/filters/100iv_lvl35.json diff --git a/examples/Filters/100iv_rare.json b/examples/filters/100iv_rare.json similarity index 100% rename from examples/Filters/100iv_rare.json rename to examples/filters/100iv_rare.json diff --git a/examples/Filters/100iv_ultra.json b/examples/filters/100iv_ultra.json similarity index 100% rename from examples/Filters/100iv_ultra.json rename to examples/filters/100iv_ultra.json diff --git a/examples/Filters/3rd_evo.json b/examples/filters/3rd_evo.json similarity index 100% rename from examples/Filters/3rd_evo.json rename to examples/filters/3rd_evo.json diff --git a/examples/Filters/90iv.json b/examples/filters/90iv.json similarity index 100% rename from examples/Filters/90iv.json rename to examples/filters/90iv.json diff --git a/examples/Filters/90iv_cd.json b/examples/filters/90iv_cd.json similarity index 100% rename from examples/Filters/90iv_cd.json rename to examples/filters/90iv_cd.json diff --git a/examples/Filters/90iv_gible.json b/examples/filters/90iv_gible.json similarity index 100% rename from examples/Filters/90iv_gible.json rename to examples/filters/90iv_gible.json diff --git a/examples/Filters/90iv_lvl34.json b/examples/filters/90iv_lvl34.json similarity index 100% rename from examples/Filters/90iv_lvl34.json rename to examples/filters/90iv_lvl34.json diff --git a/examples/Filters/90iv_snorlax.json b/examples/filters/90iv_snorlax.json similarity index 100% rename from examples/Filters/90iv_snorlax.json rename to examples/filters/90iv_snorlax.json diff --git a/examples/Filters/96iv.json b/examples/filters/96iv.json similarity index 100% rename from examples/Filters/96iv.json rename to examples/filters/96iv.json diff --git a/examples/Filters/98iv.json b/examples/filters/98iv.json similarity index 100% rename from examples/Filters/98iv.json rename to examples/filters/98iv.json diff --git a/examples/Filters/98iv_cd.json b/examples/filters/98iv_cd.json similarity index 100% rename from examples/Filters/98iv_cd.json rename to examples/filters/98iv_cd.json diff --git a/examples/Filters/all.json b/examples/filters/all.json similarity index 100% rename from examples/Filters/all.json rename to examples/filters/all.json diff --git a/examples/Filters/axew.json b/examples/filters/axew.json similarity index 100% rename from examples/Filters/axew.json rename to examples/filters/axew.json diff --git a/examples/Filters/bagon.json b/examples/filters/bagon.json similarity index 100% rename from examples/Filters/bagon.json rename to examples/filters/bagon.json diff --git a/examples/Filters/beldum.json b/examples/filters/beldum.json similarity index 100% rename from examples/Filters/beldum.json rename to examples/filters/beldum.json diff --git a/examples/Filters/bronzor.json b/examples/filters/bronzor.json similarity index 100% rename from examples/Filters/bronzor.json rename to examples/filters/bronzor.json diff --git a/examples/Filters/chansey.json b/examples/filters/chansey.json similarity index 100% rename from examples/Filters/chansey.json rename to examples/filters/chansey.json diff --git a/examples/Filters/clamperl.json b/examples/filters/clamperl.json similarity index 100% rename from examples/Filters/clamperl.json rename to examples/filters/clamperl.json diff --git a/examples/Filters/cranidos.json b/examples/filters/cranidos.json similarity index 100% rename from examples/Filters/cranidos.json rename to examples/filters/cranidos.json diff --git a/examples/Filters/darumaka.json b/examples/filters/darumaka.json similarity index 100% rename from examples/Filters/darumaka.json rename to examples/filters/darumaka.json diff --git a/examples/Filters/default.json b/examples/filters/default.json similarity index 70% rename from examples/Filters/default.json rename to examples/filters/default.json index 676d1586..b27e8eb0 100644 --- a/examples/Filters/default.json +++ b/examples/filters/default.json @@ -11,6 +11,8 @@ "size": "Big", //Tiny, Small, Normal, Large, Big "great_league": true, //Great League "ultra_league": true, //Ultra League + "min_rank": 1, //Minimum rank of #1 PVP stats + "max_rank": 5, //Maximum rank of #5 PVP stats "type": "Include", //Include or Exclude the `pokemon` list "ignoreMissing": true //Ignore Pokemon missing stats }, @@ -35,7 +37,8 @@ { "enabled": true, //Filter is enabled "rewards": ["spinda", "nincada"], //Quest reward string (Chansey, stardust, candy, etc.) - "isShiny": false //Only shiny encounter quests. + "isShiny": false, //Only shiny encounter quests. + "type": "Include" //Include or Exclude the `rewards` list }, "pokestops": { @@ -45,6 +48,13 @@ }, "gyms": { - "enabled": true //Filter is enabled + "enabled": true, //Filter is enabled + "underAttack": true, //Only gyms that are under attack + "team": "All" //Team change to notify about (i.e. Neutral/Mystic/Valor/Instinct/All) + }, + "weather": + { + "enabled": true, //Filter is enabled + "types": ["Clear", "Rain", "PartlyCloudy", "Cloudy", "Windy", "Snow", "Fog"] //Only send weather types that are in the list } } \ No newline at end of file diff --git a/examples/Filters/deino.json b/examples/filters/deino.json similarity index 100% rename from examples/Filters/deino.json rename to examples/filters/deino.json diff --git a/examples/Filters/ditto.json b/examples/filters/ditto.json similarity index 100% rename from examples/Filters/ditto.json rename to examples/filters/ditto.json diff --git a/examples/Filters/dratini.json b/examples/filters/dratini.json similarity index 100% rename from examples/Filters/dratini.json rename to examples/filters/dratini.json diff --git a/examples/Filters/drilbur.json b/examples/filters/drilbur.json similarity index 100% rename from examples/Filters/drilbur.json rename to examples/filters/drilbur.json diff --git a/examples/Filters/durant.json b/examples/filters/durant.json similarity index 100% rename from examples/Filters/durant.json rename to examples/filters/durant.json diff --git a/examples/Filters/dwebble.json b/examples/filters/dwebble.json similarity index 100% rename from examples/Filters/dwebble.json rename to examples/filters/dwebble.json diff --git a/examples/Filters/ex_raids.json b/examples/filters/ex_raids.json similarity index 100% rename from examples/Filters/ex_raids.json rename to examples/filters/ex_raids.json diff --git a/examples/Filters/feebas.json b/examples/filters/feebas.json similarity index 100% rename from examples/Filters/feebas.json rename to examples/filters/feebas.json diff --git a/examples/Filters/ferroseed.json b/examples/filters/ferroseed.json similarity index 100% rename from examples/Filters/ferroseed.json rename to examples/filters/ferroseed.json diff --git a/examples/Filters/gible.json b/examples/filters/gible.json similarity index 100% rename from examples/Filters/gible.json rename to examples/filters/gible.json diff --git a/examples/Filters/golett.json b/examples/filters/golett.json similarity index 100% rename from examples/Filters/golett.json rename to examples/filters/golett.json diff --git a/examples/Filters/gyms.json b/examples/filters/gyms.json similarity index 100% rename from examples/Filters/gyms.json rename to examples/filters/gyms.json diff --git a/examples/Filters/heatmor.json b/examples/filters/heatmor.json similarity index 100% rename from examples/Filters/heatmor.json rename to examples/filters/heatmor.json diff --git a/examples/Filters/invasions.json b/examples/filters/invasions.json similarity index 100% rename from examples/Filters/invasions.json rename to examples/filters/invasions.json diff --git a/examples/Filters/lake_trio.json b/examples/filters/lake_trio.json similarity index 100% rename from examples/Filters/lake_trio.json rename to examples/filters/lake_trio.json diff --git a/examples/Filters/lapras.json b/examples/filters/lapras.json similarity index 100% rename from examples/Filters/lapras.json rename to examples/filters/lapras.json diff --git a/examples/Filters/larvitar.json b/examples/filters/larvitar.json similarity index 100% rename from examples/Filters/larvitar.json rename to examples/filters/larvitar.json diff --git a/examples/Filters/legendary_raids.json b/examples/filters/legendary_raids.json similarity index 100% rename from examples/Filters/legendary_raids.json rename to examples/filters/legendary_raids.json diff --git a/examples/Filters/litwick.json b/examples/filters/litwick.json similarity index 100% rename from examples/Filters/litwick.json rename to examples/filters/litwick.json diff --git a/examples/Filters/lunatone.json b/examples/filters/lunatone.json similarity index 100% rename from examples/Filters/lunatone.json rename to examples/filters/lunatone.json diff --git a/examples/Filters/lures.json b/examples/filters/lures.json similarity index 100% rename from examples/Filters/lures.json rename to examples/filters/lures.json diff --git a/examples/Filters/magikarp.json b/examples/filters/magikarp.json similarity index 100% rename from examples/Filters/magikarp.json rename to examples/filters/magikarp.json diff --git a/examples/Filters/mawile_raids.json b/examples/filters/mawile_raids.json similarity index 100% rename from examples/Filters/mawile_raids.json rename to examples/filters/mawile_raids.json diff --git a/examples/Filters/pvp1500cp.json b/examples/filters/pvp1500cp.json similarity index 100% rename from examples/Filters/pvp1500cp.json rename to examples/filters/pvp1500cp.json diff --git a/examples/Filters/pvp1500cp_rank1.json b/examples/filters/pvp1500cp_rank1.json similarity index 100% rename from examples/Filters/pvp1500cp_rank1.json rename to examples/filters/pvp1500cp_rank1.json diff --git a/examples/Filters/pvp1500cp_rank5.json b/examples/filters/pvp1500cp_rank5.json similarity index 100% rename from examples/Filters/pvp1500cp_rank5.json rename to examples/filters/pvp1500cp_rank5.json diff --git a/examples/Filters/pvp2500cp.json b/examples/filters/pvp2500cp.json similarity index 100% rename from examples/Filters/pvp2500cp.json rename to examples/filters/pvp2500cp.json diff --git a/examples/Filters/pvp2500cp_rank1.json b/examples/filters/pvp2500cp_rank1.json similarity index 100% rename from examples/Filters/pvp2500cp_rank1.json rename to examples/filters/pvp2500cp_rank1.json diff --git a/examples/Filters/pvp2500cp_rank5.json b/examples/filters/pvp2500cp_rank5.json similarity index 100% rename from examples/Filters/pvp2500cp_rank5.json rename to examples/filters/pvp2500cp_rank5.json diff --git a/examples/Filters/quests.json b/examples/filters/quests.json similarity index 100% rename from examples/Filters/quests.json rename to examples/filters/quests.json diff --git a/examples/Filters/quests_absol.json b/examples/filters/quests_absol.json similarity index 100% rename from examples/Filters/quests_absol.json rename to examples/filters/quests_absol.json diff --git a/examples/Filters/quests_aerodactyl.json b/examples/filters/quests_aerodactyl.json similarity index 100% rename from examples/Filters/quests_aerodactyl.json rename to examples/filters/quests_aerodactyl.json diff --git a/examples/Filters/quests_chansey.json b/examples/filters/quests_chansey.json similarity index 100% rename from examples/Filters/quests_chansey.json rename to examples/filters/quests_chansey.json diff --git a/examples/Filters/quests_dratini.json b/examples/filters/quests_dratini.json similarity index 100% rename from examples/Filters/quests_dratini.json rename to examples/filters/quests_dratini.json diff --git a/examples/Filters/quests_golden_pinap.json b/examples/filters/quests_golden_pinap.json similarity index 100% rename from examples/Filters/quests_golden_pinap.json rename to examples/filters/quests_golden_pinap.json diff --git a/examples/Filters/quests_lapras.json b/examples/filters/quests_lapras.json similarity index 100% rename from examples/Filters/quests_lapras.json rename to examples/filters/quests_lapras.json diff --git a/examples/Filters/quests_larvitar.json b/examples/filters/quests_larvitar.json similarity index 100% rename from examples/Filters/quests_larvitar.json rename to examples/filters/quests_larvitar.json diff --git a/examples/Filters/quests_nincada.json b/examples/filters/quests_nincada.json similarity index 100% rename from examples/Filters/quests_nincada.json rename to examples/filters/quests_nincada.json diff --git a/examples/Filters/quests_rare.json b/examples/filters/quests_rare.json similarity index 100% rename from examples/Filters/quests_rare.json rename to examples/filters/quests_rare.json diff --git a/examples/Filters/quests_seel.json b/examples/filters/quests_seel.json similarity index 100% rename from examples/Filters/quests_seel.json rename to examples/filters/quests_seel.json diff --git a/examples/Filters/quests_spinda.json b/examples/filters/quests_spinda.json similarity index 100% rename from examples/Filters/quests_spinda.json rename to examples/filters/quests_spinda.json diff --git a/examples/Filters/raids.json b/examples/filters/raids.json similarity index 100% rename from examples/Filters/raids.json rename to examples/filters/raids.json diff --git a/examples/Filters/ralts.json b/examples/filters/ralts.json similarity index 100% rename from examples/Filters/ralts.json rename to examples/filters/ralts.json diff --git a/examples/Filters/shieldon.json b/examples/filters/shieldon.json similarity index 100% rename from examples/Filters/shieldon.json rename to examples/filters/shieldon.json diff --git a/examples/Filters/shinx_raids.json b/examples/filters/shinx_raids.json similarity index 100% rename from examples/Filters/shinx_raids.json rename to examples/filters/shinx_raids.json diff --git a/examples/Filters/shiny_raids.json b/examples/filters/shiny_raids.json similarity index 100% rename from examples/Filters/shiny_raids.json rename to examples/filters/shiny_raids.json diff --git a/examples/Filters/skorupi.json b/examples/filters/skorupi.json similarity index 100% rename from examples/Filters/skorupi.json rename to examples/filters/skorupi.json diff --git a/examples/Filters/snorlax.json b/examples/filters/snorlax.json similarity index 100% rename from examples/Filters/snorlax.json rename to examples/filters/snorlax.json diff --git a/examples/Filters/solrock.json b/examples/filters/solrock.json similarity index 100% rename from examples/Filters/solrock.json rename to examples/filters/solrock.json diff --git a/examples/Filters/unown.json b/examples/filters/unown.json similarity index 100% rename from examples/Filters/unown.json rename to examples/filters/unown.json diff --git a/examples/Filters/wailmer.json b/examples/filters/wailmer.json similarity index 100% rename from examples/Filters/wailmer.json rename to examples/filters/wailmer.json diff --git a/examples/Filters/weather.json b/examples/filters/weather.json similarity index 100% rename from examples/Filters/weather.json rename to examples/filters/weather.json diff --git a/examples/Filters/xl_karp.json b/examples/filters/xl_karp.json similarity index 100% rename from examples/Filters/xl_karp.json rename to examples/filters/xl_karp.json diff --git a/examples/Filters/xs_rat.json b/examples/filters/xs_rat.json similarity index 100% rename from examples/Filters/xs_rat.json rename to examples/filters/xs_rat.json diff --git a/examples/Geofences/Paris.txt b/examples/geofences/Paris.txt similarity index 100% rename from examples/Geofences/Paris.txt rename to examples/geofences/Paris.txt diff --git a/examples/Geofences/ParisLondon.txt b/examples/geofences/ParisLondon.txt similarity index 100% rename from examples/Geofences/ParisLondon.txt rename to examples/geofences/ParisLondon.txt diff --git a/examples/Templates/gyms.example.json b/examples/templates/gyms.example.json similarity index 66% rename from examples/Templates/gyms.example.json rename to examples/templates/gyms.example.json index 7a5b4d64..d75d285b 100644 --- a/examples/Templates/gyms.example.json +++ b/examples/templates/gyms.example.json @@ -1,7 +1,7 @@ { "staticmap_url": "{{tilemaps_url}}", "markers": [{ - "url": "{{marker}}", + "url": "https://raw.githubusercontent.com/versx/WhMgr-Assets/master/original/gyms/gym_{{team_id}}.png", "height": "32", "width": "32", "x_offset": 0, diff --git a/examples/templates/invasions.example.json b/examples/templates/invasions.example.json new file mode 100644 index 00000000..4f836643 --- /dev/null +++ b/examples/templates/invasions.example.json @@ -0,0 +1,20 @@ +{ + "staticmap_url": "{{tilemaps_url}}", + "markers": [{ + "url": "https://raw.githubusercontent.com/versx/WhMgr-Assets/master/original/misc/pokestop_invasion.png", + "height": "32", + "width": "32", + "x_offset": 0, + "y_offset": 0, + "latitude": "{{lat}}", + "longitude": "{{lon}}" + },{ + "url": "{{marker}}", + "height": "32", + "width": "32", + "x_offset": 0, + "y_offset": -25, + "latitude": "{{lat}}", + "longitude": "{{lon}}" + }] +} \ No newline at end of file diff --git a/examples/templates/lures.example.json b/examples/templates/lures.example.json new file mode 100644 index 00000000..ab10f766 --- /dev/null +++ b/examples/templates/lures.example.json @@ -0,0 +1,20 @@ +{ + "staticmap_url": "{{tilemaps_url}}", + "markers": [{ + "url": "https://raw.githubusercontent.com/versx/WhMgr-Assets/master/original/misc/pokestop.png", + "height": "32", + "width": "32", + "x_offset": 0, + "y_offset": 0, + "latitude": "{{lat}}", + "longitude": "{{lon}}" + },{ + "url": "{{marker}}", + "height": "32", + "width": "32", + "x_offset": 0, + "y_offset": -25, + "latitude": "{{lat}}", + "longitude": "{{lon}}" + }] +} \ No newline at end of file diff --git a/examples/Templates/nests.example.json b/examples/templates/nests.example.json similarity index 100% rename from examples/Templates/nests.example.json rename to examples/templates/nests.example.json diff --git a/examples/Templates/pokemon.example.json b/examples/templates/pokemon.example.json similarity index 92% rename from examples/Templates/pokemon.example.json rename to examples/templates/pokemon.example.json index 0ef1b633..bdb2c717 100644 --- a/examples/Templates/pokemon.example.json +++ b/examples/templates/pokemon.example.json @@ -2,7 +2,7 @@ "staticmap_url": "{{tilemaps_url}}", "markers": [ { - "url": "https://raw.githubusercontent.com/versx/WhMgr-Assets/master/new/misc/grass.png", + "url": "https://raw.githubusercontent.com/versx/WhMgr-Assets/master/original/misc/grass.png", "height": "48", "width": "48", "x_offset": 0, diff --git a/examples/Templates/quests.example.json b/examples/templates/quests.example.json similarity index 100% rename from examples/Templates/quests.example.json rename to examples/templates/quests.example.json diff --git a/examples/templates/raids.example.json b/examples/templates/raids.example.json new file mode 100644 index 00000000..3048e02d --- /dev/null +++ b/examples/templates/raids.example.json @@ -0,0 +1,20 @@ +{ + "staticmap_url": "{{tilemaps_url}}", + "markers": [{ + "url": "https://raw.githubusercontent.com/versx/WhMgr-Assets/master/original/gyms/gym_{{team_id}}.png", + "height": "32", + "width": "32", + "x_offset": 0, + "y_offset": 0, + "latitude": "{{lat}}", + "longitude": "{{lon}}" + },{ + "url": "{{pkmn_img_url}}", + "height": "32", + "width": "32", + "x_offset": 0, + "y_offset": -25, + "latitude": "{{lat}}", + "longitude": "{{lon}}" + }] +} \ No newline at end of file diff --git a/examples/Templates/weather.example.json b/examples/templates/weather.example.json similarity index 82% rename from examples/Templates/weather.example.json rename to examples/templates/weather.example.json index cd1613d6..ae39196e 100644 --- a/examples/Templates/weather.example.json +++ b/examples/templates/weather.example.json @@ -2,8 +2,8 @@ "staticmap_url": "{{tilemaps_url}}", "markers": [{ "url": "{{weather_img_url}}", - "height": "32", - "width": "32", + "height": "48", + "width": "48", "x_offset": 0, "y_offset": 0, "latitude": "{{lat}}", diff --git a/src/Alarms/Alerts/AlertMessage.cs b/src/Alarms/Alerts/AlertMessage.cs index 8d566539..78a756ec 100644 --- a/src/Alarms/Alerts/AlertMessage.cs +++ b/src/Alarms/Alerts/AlertMessage.cs @@ -11,7 +11,7 @@ public class AlertMessage : Dictionary AlertMessageType.Pokemon, new AlertMessageSettings { AvatarUrl = "", - Content = " (//) L
**Despawn:** ( left)
**Details:** CP: IV: LV:
**Types:** | **Size:** <#has_weather> | <#is_weather_boosted> (Boosted)
**Moveset:**
<#near_pokestop>**Near Pokestop:** []()
<#is_ditto>**Catch Pokemon:**
<#has_capture_rates> % % %
<#is_pvp>
**[[Google Maps]()] [[Apple Maps]()] [[Waze Maps]()]**", + Content = " (//) L
**Despawn:** ( left)
**Details:** CP: IV: LV:
**Types:** | **Size:** <#has_weather> | <#is_weather_boosted> (Boosted)
**Moveset:**
<#near_pokestop>**Near Pokestop:** []()
<#is_ditto>**Catch Pokemon:**
<#has_capture_rates> % % %
<#is_pvp>
**[[Google]()] [[Apple]()] [[Waze]()] [[Scanner]()]**", IconUrl = "", Title = "", Url = "", @@ -28,7 +28,7 @@ public class AlertMessage : Dictionary AlertMessageType.PokemonMissingStats, new AlertMessageSettings { AvatarUrl = "", - Content = "
**Despawn:** ( left)
**Types:**
<#near_pokestop>**Near Pokestop:** []()
**[[Google Maps]()] [[Apple Maps]()] [[Waze Maps]()]**", + Content = "
**Despawn:** ( left)
**Types:**
<#near_pokestop>**Near Pokestop:** []()
**[[Google]()] [[Apple]()] [[Waze]()] [[Scanner]()]**", IconUrl = "", Title = "", Url = "", @@ -45,7 +45,7 @@ public class AlertMessage : Dictionary AlertMessageType.Gyms, new AlertMessageSettings { AvatarUrl = "", - Content = "<#team_changed>Gym changed from to
<#in_battle>Gym is under attack!
**Slots Available:**
<#is_ex> Gym!**[[Google Maps]()] [[Apple Maps]()] [[Waze Maps]()]**", + Content = "<#team_changed>Gym changed from to
<#in_battle>Gym is under attack!
**Slots Available:**
<#is_ex> Gym!**[[Google]()] [[Apple]()] [[Waze]()] [[Scanner]()]**", IconUrl = "", Title = ": ", Url = "", @@ -62,7 +62,7 @@ public class AlertMessage : Dictionary AlertMessageType.Raids, new AlertMessageSettings { AvatarUrl = "", - Content = " Raid Ends: ( left)
**Perfect CP:** / :white_sun_rain_cloud:
**Worst CP:** / :white_sun_rain_cloud:
**Types:** | **Level:** | **Team:**
**Moveset:**
**Weaknesses:**
<#is_ex> Gym!
**[[Google Maps]()] [[Apple Maps]()] [[Waze Maps]()]**", + Content = " Raid Ends: ( left)
**Perfect CP:** / :white_sun_rain_cloud:
**Worst CP:** / :white_sun_rain_cloud:
**Types:** | **Level:** | **Team:**
**Moveset:**
**Weaknesses:**
<#is_ex> Gym!
**[[Google]()] [[Apple]()] [[Waze]()] [[Scanner]()]**", IconUrl = "", Title = ": ", Url = "", @@ -79,7 +79,7 @@ public class AlertMessage : Dictionary AlertMessageType.Eggs, new AlertMessageSettings { AvatarUrl = "", - Content = "Hatches: ()
**Ends:** ( left)
**Team:**
<#is_ex> Gym!
**[[Google Maps]()] [[Apple Maps]()] [[Waze Maps]()]**", + Content = "Hatches: ()
**Ends:** ( left)
**Team:**
<#is_ex> Gym!
**[[Google]()] [[Apple]()] [[Waze]()] [[Scanner]()]**", IconUrl = "", Title = ": ", Url = "", @@ -96,7 +96,7 @@ public class AlertMessage : Dictionary AlertMessageType.Pokestops, new AlertMessageSettings { AvatarUrl = "", - Content = "<#has_lure>**Lure Expires** ( left)
**Lure Type:**
<#has_invasion>**Expires:** ( left)
**Type:** | **Gender:**

**[[Google Maps]()] [[Apple Maps]()] [[Waze Maps]()]**", + Content = "<#has_lure>**Lure Expires** ( left)
**Lure Type:**
<#has_invasion>**Expires:** ( left)
**Type:** | **Gender:**

**[[Google]()] [[Apple]()] [[Waze]()] [[Scanner]()]**", IconUrl = "", Title = ": ", Url = "", @@ -113,7 +113,7 @@ public class AlertMessage : Dictionary AlertMessageType.Quests, new AlertMessageSettings { AvatarUrl = "", - Content = "**Quest:**
<#has_quest_conditions>**Condition(s):**
**Reward:**
**[[Google Maps]()] [[Apple Maps]()] [[Waze Maps]()]**", + Content = "**Quest:**
<#has_quest_conditions>**Condition(s):**
**Reward:**
**[[Google]()] [[Apple]()] [[Waze]()] [[Scanner]()]**", IconUrl = "", Title = ": ", Url = "", @@ -130,7 +130,7 @@ public class AlertMessage : Dictionary AlertMessageType.Invasions, new AlertMessageSettings { AvatarUrl = "", - Content = "<#has_invasion>**Expires:** ( left)
**Type:** | **Gender:**

**[[Google Maps]()] [[Apple Maps]()] [[Waze Maps]()]**", + Content = "<#has_invasion>**Expires:** ( left)
**Type:** | **Gender:**

**[[Google]()] [[Apple]()] [[Waze]()] [[Scanner]()]**", IconUrl = "", Title = ": ", Url = "", @@ -147,7 +147,7 @@ public class AlertMessage : Dictionary AlertMessageType.Lures, new AlertMessageSettings { AvatarUrl = "", - Content = "<#has_lure>**Lure Expires:** ( left)
**Lure Type:**
**[[Google Maps]()] [[Apple Maps]()] [[Waze Maps]()]**", + Content = "<#has_lure>**Lure Expires:** ( left)
**Lure Type:**
**[[Google]()] [[Apple]()] [[Waze]()] [[Scanner]()]**", IconUrl = "", Title = ": ", Url = "", @@ -164,7 +164,7 @@ public class AlertMessage : Dictionary AlertMessageType.Nests, new AlertMessageSettings { AvatarUrl = "", - Content = "**Pokemon:**
**Average Spawns:** /h | **Types:**
**[[Google Maps]()] [[Apple Maps]()] [[Waze Maps]()]**", + Content = "**Pokemon:**
**Average Spawns:** /h | **Types:**
**[[Google]()] [[Apple]()] [[Waze]()] [[Scanner]()]**", IconUrl = "", Title = ": ", Url = "", diff --git a/src/Alarms/Alerts/AlertMessageSettings.cs b/src/Alarms/Alerts/AlertMessageSettings.cs index d640c9b3..4dea46d0 100644 --- a/src/Alarms/Alerts/AlertMessageSettings.cs +++ b/src/Alarms/Alerts/AlertMessageSettings.cs @@ -8,7 +8,7 @@ public class AlertMessageSettings { /// - /// Gets or sets the Discord message content. + /// Gets or sets the Discord message content within the embed message. /// [JsonProperty("content")] public string Content { get; set; } diff --git a/src/Alarms/Models/AlarmObject.cs b/src/Alarms/Models/AlarmObject.cs index 294f20cf..4120d362 100644 --- a/src/Alarms/Models/AlarmObject.cs +++ b/src/Alarms/Models/AlarmObject.cs @@ -40,6 +40,12 @@ public class AlarmObject [JsonProperty("name")] public string Name { get; set; } + /// + /// Gets or sets the Discord message content outside of the embed message. (above it, can contain role/user mentions, DTS, etc) + /// + [JsonProperty("description")] + public string Description { get; set; } + /// /// Gets or sets the filters file to load /// @@ -64,12 +70,6 @@ public class AlarmObject [JsonProperty("webhook")] public string Webhook { get; set; } - /// - /// Gets or sets the Discord mentions string to use in the Discord message description - /// - [JsonProperty("mentions")] - public string Mentions { get; set; } - /// /// Instantiate a new class /// diff --git a/src/Bot.cs b/src/Bot.cs index af782067..dd0c969a 100644 --- a/src/Bot.cs +++ b/src/Bot.cs @@ -32,6 +32,8 @@ // TODO: List all subscriptions with info command // TODO: Manage subscriptions via DM again // TODO: Multiple discord bot tokens per server + // TODO: Only start database migrator if subscriptions are enabled + // TODO: Check nests again public class Bot { @@ -41,7 +43,6 @@ public class Bot private readonly WebhookController _whm; private WhConfig _whConfig; private readonly SubscriptionProcessor _subProcessor; - private readonly Dictionary _gyms; private static readonly IEventLogger _logger = EventLogger.GetLogger("BOT"); @@ -58,7 +59,6 @@ public Bot(WhConfig whConfig) _logger.Trace($"WhConfig [Servers={whConfig.Servers.Count}, Port={whConfig.WebhookPort}]"); _servers = new Dictionary(); _whConfig = whConfig; - _gyms = new Dictionary(); _whm = new WebhookController(_whConfig); // Set translation language @@ -495,6 +495,7 @@ private async void OnPokemonAlarmTriggered(object sender, AlarmEventTriggeredEve { Username = eb.Username, AvatarUrl = eb.IconUrl, + Content = eb.Description, Embeds = eb.Embeds }.Build(); NetUtil.SendWebhook(e.Alarm.Webhook, jsonEmbed); @@ -541,6 +542,7 @@ private void OnRaidAlarmTriggered(object sender, AlarmEventTriggeredEventArgs { eb } + Username = eb.Username, + AvatarUrl = eb.IconUrl, + Content = eb.Description, + Embeds = eb.Embeds }.Build(); NetUtil.SendWebhook(e.Alarm.Webhook, jsonEmbed); Statistics.Instance.QuestAlarmsSent++; @@ -624,6 +627,7 @@ private void OnPokestopAlarmTriggered(object sender, AlarmEventTriggeredEventArg { Username = eb.Username ?? Translator.Instance.Translate("UNKNOWN_POKESTOP"), AvatarUrl = eb.IconUrl, + Content = eb.Description, Embeds = eb.Embeds }.Build(); NetUtil.SendWebhook(e.Alarm.Webhook, jsonEmbed); @@ -671,28 +675,26 @@ private void OnGymDetailsAlarmTriggered(object sender, AlarmEventTriggeredEventA try { - if (!_gyms.ContainsKey(gymDetails.GymId)) - { - _gyms.Add(gymDetails.GymId, gymDetails); - } - - var oldGym = _gyms[gymDetails.GymId]; - var changed = oldGym.Team != gymDetails.Team;// || /*oldGym.InBattle != gymDetails.InBattle ||*/ gymDetails.InBattle; + var oldGym = _whm.Gyms[gymDetails.GymId]; + var changed = oldGym.Team != gymDetails.Team || gymDetails.InBattle || oldGym.SlotsAvailable != gymDetails.SlotsAvailable; if (!changed) return; var client = _servers[e.GuildId]; - var eb = gymDetails.GenerateGymMessage(e.GuildId, client, _whConfig, e.Alarm, oldGym, loc?.Name ?? e.Alarm.Name); + var eb = gymDetails.GenerateGymMessage(e.GuildId, client, _whConfig, e.Alarm, _whm.Gyms[gymDetails.GymId], loc?.Name ?? e.Alarm.Name); var name = gymDetails.GymName; var jsonEmbed = new DiscordWebhookMessage { Username = eb.Username, AvatarUrl = eb.IconUrl, + Content = eb.Description, Embeds = eb.Embeds }.Build(); NetUtil.SendWebhook(e.Alarm.Webhook, jsonEmbed); Statistics.Instance.GymAlarmsSent++; - _gyms[gymDetails.GymId] = gymDetails; + + // Gym team changed, set gym in gym cache + _whm.SetGym(gymDetails.GymId, gymDetails); Statistics.Instance.GymAlarmsSent++; } @@ -731,10 +733,14 @@ private void OnWeatherAlarmTriggered(object sender, AlarmEventTriggeredEventArgs { Username = eb.Username, AvatarUrl = eb.IconUrl, + Content = eb.Description, Embeds = eb.Embeds }.Build(); NetUtil.SendWebhook(e.Alarm.Webhook, jsonEmbed); + // Weather changed, set weather in weather cache + _whm.SetWeather(weather.Id, weather.GameplayCondition); + Statistics.Instance.WeatherAlarmsSent++; } catch (Exception ex) @@ -945,8 +951,15 @@ private async Task PostShinyStats(DiscordClient client, DiscordServerConfig serv _logger.Debug($"Posting shiny stats for guild {client.Guilds[guildId].Name} ({guildId}) in channel {server.ShinyStats.ChannelId}"); // Subtract an hour to make sure it shows yesterday's date. await statsChannel.SendMessageAsync(Translator.Instance.Translate("SHINY_STATS_TITLE").FormatText(DateTime.Now.Subtract(TimeSpan.FromHours(1)).ToLongDateString())); + Thread.Sleep(500); await statsChannel.SendMessageAsync(Translator.Instance.Translate("SHINY_STATS_NEWLINE")); var stats = await ShinyStats.GetShinyStats(_whConfig.Database.Scanner.ToString()); + if (stats == null) + { + _logger.Error($"Failed to get list of shiny stats for guild {guildId}, skipping..."); + return; + } + var sorted = stats.Keys.ToList(); sorted.Sort(); @@ -969,6 +982,7 @@ private async Task PostShinyStats(DiscordClient client, DiscordServerConfig serv { await statsChannel.SendMessageAsync(Translator.Instance.Translate("SHINY_STATS_MESSAGE_WITH_RATIO").FormatText(pkmn.Name, pokemon, pkmnStats.Shiny.ToString("N0"), pkmnStats.Total.ToString("N0"), chance)); } + Thread.Sleep(500); } var total = stats[0]; diff --git a/src/Commands/Nests.cs b/src/Commands/Nests.cs index ea1cbd74..4301cb58 100644 --- a/src/Commands/Nests.cs +++ b/src/Commands/Nests.cs @@ -79,8 +79,7 @@ public async Task PostNestsAsync(CommandContext ctx) try { - var pkmnImage = nest.PokemonId.GetPokemonIcon(0, 0, _dep.WhConfig, _dep.WhConfig.IconStyles[server.IconStyle]); - var eb = GenerateNestMessage(ctx.Guild.Id, ctx.Client, nest, pkmnImage); + var eb = GenerateNestMessage(ctx.Guild.Id, ctx.Client, nest); var geofences = _dep.Whm.Geofences.Values.ToList(); var geofence = GeofenceService.GetGeofence(geofences, new Location(nest.Latitude, nest.Longitude)); if (geofence == null) @@ -101,23 +100,25 @@ public async Task PostNestsAsync(CommandContext ctx) } } - public DiscordEmbed GenerateNestMessage(ulong guildId, DiscordClient client, Nest nest, string pokemonImageUrl) + public DiscordEmbed GenerateNestMessage(ulong guildId, DiscordClient client, Nest nest) { var alertMessageType = AlertMessageType.Nests; var alertMessage = /*alarm?.Alerts[alertMessageType] ??*/ AlertMessage.Defaults[alertMessageType]; // TODO: Add nestAlert config option + var server = _dep.WhConfig.Servers[guildId]; + var pokemonImageUrl = nest.PokemonId.GetPokemonIcon(0, 0, _dep.WhConfig, server.IconStyle); var properties = GetProperties(nest, pokemonImageUrl); var eb = new DiscordEmbedBuilder { Title = DynamicReplacementEngine.ReplaceText(alertMessage.Title, properties), Url = DynamicReplacementEngine.ReplaceText(alertMessage.Url, properties), ImageUrl = DynamicReplacementEngine.ReplaceText(alertMessage.ImageUrl, properties), - ThumbnailUrl = pokemonImageUrl, + ThumbnailUrl = DynamicReplacementEngine.ReplaceText(alertMessage.IconUrl, properties), Description = DynamicReplacementEngine.ReplaceText(alertMessage.Content, properties), Color = DiscordColor.Green, Footer = new DiscordEmbedBuilder.EmbedFooter { - Text = $"{(client.Guilds.ContainsKey(guildId) ? client.Guilds[guildId]?.Name : Strings.Creator)} | {DateTime.Now}", - IconUrl = client.Guilds.ContainsKey(guildId) ? client.Guilds[guildId]?.IconUrl : string.Empty + Text = DynamicReplacementEngine.ReplaceText(alertMessage.Footer?.Text ?? client.Guilds[guildId]?.Name ?? DateTime.Now.ToString(), properties), + IconUrl = DynamicReplacementEngine.ReplaceText(alertMessage.Footer?.IconUrl ?? client.Guilds[guildId]?.IconUrl ?? string.Empty, properties) } }; return eb.Build(); @@ -136,11 +137,13 @@ public IReadOnlyDictionary GetProperties(Nest nest, string pokem var gmapsLink = string.Format(Strings.GoogleMaps, nest.Latitude, nest.Longitude); var appleMapsLink = string.Format(Strings.AppleMaps, nest.Latitude, nest.Longitude); var wazeMapsLink = string.Format(Strings.WazeMaps, nest.Latitude, nest.Longitude); + var scannerMapsLink = string.Format(_dep.WhConfig.Urls.ScannerMap, nest.Latitude, nest.Longitude); var templatePath = Path.Combine(_dep.WhConfig.StaticMaps.TemplatesFolder, _dep.WhConfig.StaticMaps.NestsTemplateFile); - var staticMapLink = Utils.GetStaticMapsUrl(templatePath, _dep.WhConfig.Urls.StaticMap, nest.Latitude, nest.Longitude, pkmnImage, _dep.OsmManager.GetNest(nest.Name)?.FirstOrDefault()); + var staticMapLink = Utils.GetStaticMapsUrl(templatePath, _dep.WhConfig.Urls.StaticMap, nest.Latitude, nest.Longitude, pkmnImage, null, _dep.OsmManager.GetNest(nest.Name)?.FirstOrDefault()); var geofences = _dep.Whm.Geofences.Values.ToList(); var geofence = GeofenceService.GetGeofence(geofences, new Location(nest.Latitude, nest.Longitude)); var city = geofence?.Name ?? "Unknown"; + var googleAddress = Utils.GetGoogleAddress(city, nest.Latitude, nest.Longitude, _dep.WhConfig.GoogleMapsKey); var dict = new Dictionary { @@ -170,6 +173,9 @@ public IReadOnlyDictionary GetProperties(Nest nest, string pokem { "gmaps_url", gmapsLink }, { "applemaps_url", appleMapsLink }, { "wazemaps_url", wazeMapsLink }, + { "scanmaps_url", scannerMapsLink }, + + { "address", googleAddress?.Address }, { "date_time", DateTime.Now.ToString() }, diff --git a/src/Commands/Notifications.cs b/src/Commands/Notifications.cs index 1907e961..8eefe779 100644 --- a/src/Commands/Notifications.cs +++ b/src/Commands/Notifications.cs @@ -137,6 +137,34 @@ public async Task SetDistanceAsync(CommandContext ctx, _dep.SubscriptionProcessor.Manager.ReloadSubscriptions(); } + [ + Command("set-number"), + Description("Set the phone number to receive text message notifications for ultra rare pokemon.") + ] + public async Task SetPhoneNumberAsync(CommandContext ctx, + [Description("10 digit phone number to receive text message alerts for")] string phoneNumber) + { + if (!await CanExecute(ctx)) + return; + + // Check if user is in list of acceptable users to receive Pokemon text message notifications + if (!_dep.WhConfig.Twilio.UserIds.Contains(ctx.User.Id)) + return; + + var subscription = _dep.SubscriptionProcessor.Manager.GetUserSubscriptions(ctx.Guild.Id, ctx.User.Id); + if (subscription == null) + { + await ctx.RespondEmbed(Translator.Instance.Translate("MSG_USER_NOT_SUBSCRIBED").FormatText(ctx.User.Username), DiscordColor.Red); + return; + } + + subscription.PhoneNumber = phoneNumber; + subscription.Save(); + + await ctx.RespondEmbed(Translator.Instance.Translate("NOTIFY_PHONE_NUMBER_SET").FormatText(ctx.User.Username, phoneNumber)); + _dep.SubscriptionProcessor.Manager.ReloadSubscriptions(); + } + [ Command("expire"), Aliases("expires"), @@ -607,7 +635,7 @@ public async Task RaidMeAsync(CommandContext ctx, var pokemonNames = validation.Valid.Select(x => MasterFile.Instance.Pokedex[x.Key].Name + (string.IsNullOrEmpty(x.Value) ? string.Empty : "-" + x.Value)); await ctx.RespondEmbed(Translator.Instance.Translate("SUCCESS_RAID_SUBSCRIPTIONS_SUBSCRIBE").FormatText( ctx.User.Username, - string.Join("**, **", pokemonNames), + string.Compare(poke, Strings.All, true) == 0 ? Strings.All : string.Join("**, **", pokemonNames), string.IsNullOrEmpty(city) ? Translator.Instance.Translate("SUBSCRIPTIONS_FROM_ALL_CITIES") : Translator.Instance.Translate("SUBSCRIPTIONS_FROM_CITY").FormatText(city)) @@ -701,7 +729,7 @@ await ctx.RespondEmbed(Translator.Instance.Translate("ERROR_NO_RAID_SUBSCRIPTION var pokemonNames = validation.Valid.Select(x => MasterFile.Instance.Pokedex[x.Key].Name + (string.IsNullOrEmpty(x.Value) ? string.Empty : "-" + x.Value)); await ctx.RespondEmbed(Translator.Instance.Translate("SUCCESS_RAID_SUBSCRIPTIONS_UNSUBSCRIBE").FormatText( ctx.User.Username, - string.Join("**, **", pokemonNames), + string.Compare(poke, Strings.All, true) == 0 ? Strings.All : string.Join("**, **", pokemonNames), string.IsNullOrEmpty(city) ? Translator.Instance.Translate("SUBSCRIPTIONS_FROM_ALL_CITIES") : Translator.Instance.Translate("SUBSCRIPTIONS_FROM_CITY").FormatText(city)) @@ -968,9 +996,10 @@ public async Task InvMeAsync(CommandContext ctx, } subscription.Save(); + var valid = validation.Valid.Keys.Select(x => MasterFile.GetPokemon(x, 0).Name); await ctx.RespondEmbed(Translator.Instance.Translate("SUCCESS_INVASION_SUBSCRIPTIONS_SUBSCRIBE").FormatText( ctx.User.Username, - string.Join(", ", validation.Valid.Keys.Select(x => MasterFile.GetPokemon(x, 0).Name)), + string.Compare(poke, Strings.All, true) == 0 ? Strings.All : string.Join(", ", valid), string.IsNullOrEmpty(city) ? Translator.Instance.Translate("SUBSCRIPTIONS_FROM_ALL_CITIES") : Translator.Instance.Translate("SUBSCRIPTIONS_FROM_CITY").FormatText(city)) @@ -1057,9 +1086,10 @@ await ctx.RespondEmbed(Translator.Instance.Translate("ERROR_NO_INVASION_SUBSCRIP } } + var valid = validation.Valid.Keys.Select(x => MasterFile.GetPokemon(x, 0).Name); await ctx.RespondEmbed(Translator.Instance.Translate("SUCCESS_INVASION_SUBSCRIPTIONS_UNSUBSCRIBE").FormatText( ctx.User.Username, - string.Join(", ", validation.Valid.Keys.Select(x => MasterFile.GetPokemon(x, 0).Name)), + string.Compare(poke, Strings.All, true) == 0 ? Strings.All : string.Join(", ", valid), string.IsNullOrEmpty(city) ? Translator.Instance.Translate("SUBSCRIPTIONS_FROM_ALL_CITIES") : Translator.Instance.Translate("SUBSCRIPTIONS_FROM_CITY").FormatText(city)) diff --git a/src/Configuration/UrlConfig.cs b/src/Configuration/UrlConfig.cs index 2e3ea247..4b9d5734 100644 --- a/src/Configuration/UrlConfig.cs +++ b/src/Configuration/UrlConfig.cs @@ -12,5 +12,11 @@ public class UrlConfig /// [JsonProperty("staticMap")] public string StaticMap { get; set; } + + /// + /// Gets or sets the scanner map url + /// + [JsonProperty("scannerMap")] + public string ScannerMap { get; set; } } } \ No newline at end of file diff --git a/src/Configuration/WhConfig.cs b/src/Configuration/WhConfig.cs index 2b16d87a..36fda4cd 100644 --- a/src/Configuration/WhConfig.cs +++ b/src/Configuration/WhConfig.cs @@ -86,6 +86,12 @@ public class WhConfig [JsonProperty("twilio")] public TwilioConfig Twilio { get; set; } + /// + /// Gets or sets the Google maps key for location lookup + /// + [JsonProperty("gmapsKey")] + public string GoogleMapsKey { get; set; } + /// /// Gets or sets whether to log incoming webhook data to a file /// diff --git a/src/Data/Subscriptions/SubscriptionProcessor.cs b/src/Data/Subscriptions/SubscriptionProcessor.cs index 588aed09..3bc2cc0d 100644 --- a/src/Data/Subscriptions/SubscriptionProcessor.cs +++ b/src/Data/Subscriptions/SubscriptionProcessor.cs @@ -547,7 +547,10 @@ public async Task ProcessQuestSubscription(QuestData quest) } var embed = quest.GenerateQuestMessage(user.GuildId, client, _whConfig, null, loc.Name); - _queue.Enqueue(new NotificationItem(user, member, embed, questName, loc.Name)); + foreach (var emb in embed.Embeds) + { + _queue.Enqueue(new NotificationItem(user, member, emb, questName, loc.Name)); + } Statistics.Instance.SubscriptionQuestsSent++; Thread.Sleep(5); @@ -749,7 +752,7 @@ private void ProcessQueue() _whConfig.Servers[item.Subscription.GuildId].OwnerId == item.Member.Id) { // Send text message (max 160 characters) - if (IsUltraRare(_whConfig.Twilio, item.Pokemon)) + if (item.Pokemon != null && IsUltraRare(_whConfig.Twilio, item.Pokemon)) { var result = Utils.SendSmsMessage(StripEmbed(item), _whConfig.Twilio, item.Subscription.PhoneNumber); if (!result) diff --git a/src/Geofence/Location.cs b/src/Geofence/Location.cs index f2ea7a7c..a3878aa9 100644 --- a/src/Geofence/Location.cs +++ b/src/Geofence/Location.cs @@ -5,6 +5,16 @@ /// public class Location { + /// + /// Gets the address for the location + /// + public string Address { get; set; } + + /// + /// Gets the city of the address + /// + public string City { get; } + /// /// Gets the geocoordinate latitude /// @@ -26,6 +36,21 @@ public Location(double lat, double lng) Longitude = lng; } + /// + /// Instantiates a new class + /// + /// Address of geocoordinates + /// City of address + /// Geocoordinate latitude + /// Geocoordinate longitude + public Location(string address, string city, double lat, double lng) + { + Address = address; + City = city; + Latitude = lat; + Longitude = lng; + } + /// /// Returns the string representation of class /// diff --git a/src/Net/Models/GymDetailsData.cs b/src/Net/Models/GymDetailsData.cs index ef87ae4b..0e000b43 100644 --- a/src/Net/Models/GymDetailsData.cs +++ b/src/Net/Models/GymDetailsData.cs @@ -3,52 +3,85 @@ using System; using System.Collections.Generic; using System.IO; - + using System.Linq; using DSharpPlus; using DSharpPlus.Entities; using Newtonsoft.Json; + using ServiceStack.DataAnnotations; + using ServiceStack.OrmLite; using WhMgr.Alarms.Alerts; using WhMgr.Alarms.Models; using WhMgr.Configuration; using WhMgr.Data; + using WhMgr.Diagnostics; using WhMgr.Utilities; /// /// RealDeviceMap Gym Details webhook model class. /// + [Alias("gym")] public sealed class GymDetailsData { + private static readonly IEventLogger _logger = EventLogger.GetLogger("GYMDETAILSDATA"); + public const string WebhookHeader = "gym_details"; #region Properties - [JsonProperty("id")] + [ + JsonProperty("id"), + Alias("id") + ] public string GymId { get; set; } - [JsonProperty("name")] + [ + JsonProperty("name"), + Alias("name") + ] public string GymName { get; set; } = "Unknown"; - [JsonProperty("url")] + [ + JsonProperty("url"), + Alias("name") + ] public string Url { get; set; } - [JsonProperty("latitude")] + [ + JsonProperty("latitude"), + Alias("lat") + ] public double Latitude { get; set; } - [JsonProperty("longitude")] + [ + JsonProperty("longitude"), + Alias("lon") + ] public double Longitude { get; set; } - [JsonProperty("team")] + [ + JsonProperty("team"), + Alias("team_id") + ] public PokemonTeam Team { get; set; } = PokemonTeam.Neutral; - [JsonProperty("slots_available")] + [ + JsonProperty("slots_available"), + Alias("availble_slots") // TODO: Typflo + ] public ushort SlotsAvailable { get; set; } - [JsonProperty("sponsor_id")] + [ + JsonProperty("sponsor_id"), + Alias("sponsor_id") + ] public bool SponsorId { get; set; } - [JsonProperty("in_battle")] + [ + JsonProperty("in_battle"), + Alias("in_battle") + ] public bool InBattle { get; set; } #endregion @@ -58,30 +91,27 @@ public DiscordEmbedNotification GenerateGymMessage(ulong guildId, DiscordClient var alertType = AlertMessageType.Gyms; var alert = alarm?.Alerts[alertType] ?? AlertMessage.Defaults[alertType]; var properties = GetProperties(client.Guilds[guildId], whConfig, city, oldGym); - var mention = DynamicReplacementEngine.ReplaceText(alarm.Mentions, properties); - var description = DynamicReplacementEngine.ReplaceText(alert.Content, properties); - var footerText = DynamicReplacementEngine.ReplaceText(alert.Footer?.Text ?? client.Guilds[guildId]?.Name ?? $"{Strings.Creator} | {DateTime.Now}", properties); - var footerIconUrl = DynamicReplacementEngine.ReplaceText(alert.Footer?.IconUrl ?? client.Guilds[guildId]?.IconUrl ?? string.Empty, properties); var eb = new DiscordEmbedBuilder { Title = DynamicReplacementEngine.ReplaceText(alert.Title, properties), Url = DynamicReplacementEngine.ReplaceText(alert.Url, properties), ImageUrl = DynamicReplacementEngine.ReplaceText(alert.ImageUrl, properties), ThumbnailUrl = DynamicReplacementEngine.ReplaceText(alert.IconUrl, properties), - Description = mention + description, + Description = DynamicReplacementEngine.ReplaceText(alert.Content, properties), Color = Team == PokemonTeam.Mystic ? DiscordColor.Blue : Team == PokemonTeam.Valor ? DiscordColor.Red : Team == PokemonTeam.Instinct ? DiscordColor.Yellow : DiscordColor.LightGray, Footer = new DiscordEmbedBuilder.EmbedFooter { - Text = footerText, - IconUrl = footerIconUrl + Text = DynamicReplacementEngine.ReplaceText(alert.Footer?.Text ?? client.Guilds[guildId]?.Name ?? DateTime.Now.ToString(), properties), + IconUrl = DynamicReplacementEngine.ReplaceText(alert.Footer?.IconUrl ?? client.Guilds[guildId]?.IconUrl ?? string.Empty, properties) } }; var username = DynamicReplacementEngine.ReplaceText(alert.Username, properties); var iconUrl = DynamicReplacementEngine.ReplaceText(alert.AvatarUrl, properties); - return new DiscordEmbedNotification(username, iconUrl, new List { eb.Build() }); + var description = DynamicReplacementEngine.ReplaceText(alarm?.Description, properties); + return new DiscordEmbedNotification(username, iconUrl, description, new List { eb.Build() }); } private IReadOnlyDictionary GetProperties(DiscordGuild guild, WhConfig whConfig, string city, GymDetailsData oldGym) @@ -90,20 +120,21 @@ private IReadOnlyDictionary GetProperties(DiscordGuild guild, Wh var exEmoji = exEmojiId > 0 ? string.Format(Strings.EmojiSchema, "ex", exEmojiId): "EX"; var teamEmojiId = MasterFile.Instance.Emojis[Team.ToString().ToLower()]; var teamEmoji = teamEmojiId > 0 ? string.Format(Strings.EmojiSchema, Team.ToString().ToLower(), teamEmojiId) : Team.ToString(); - var oldTeamEmojiId = MasterFile.Instance.Emojis[oldGym.Team.ToString().ToLower()]; - var oldTeamEmoji = oldTeamEmojiId > 0 ? string.Format(Strings.EmojiSchema, oldGym.Team.ToString().ToLower(), oldTeamEmojiId) : oldGym.Team.ToString(); + var oldTeamEmojiId = MasterFile.Instance.Emojis[oldGym?.Team.ToString().ToLower()]; + var oldTeamEmoji = oldTeamEmojiId > 0 ? string.Format(Strings.EmojiSchema, oldGym?.Team.ToString().ToLower(), oldTeamEmojiId) : oldGym?.Team.ToString(); var gmapsLink = string.Format(Strings.GoogleMaps, Latitude, Longitude); var appleMapsLink = string.Format(Strings.AppleMaps, Latitude, Longitude); var wazeMapsLink = string.Format(Strings.WazeMaps, Latitude, Longitude); - // TODO: Use team icon for gym - var gymImage = "https://static.thenounproject.com/png/727778-200.png"; + var scannerMapsLink = string.Format(whConfig.Urls.ScannerMap, Latitude, Longitude); var templatePath = Path.Combine(whConfig.StaticMaps.TemplatesFolder, whConfig.StaticMaps.GymsTemplateFile); - var staticMapLink = Utils.GetStaticMapsUrl(templatePath, whConfig.Urls.StaticMap.Replace("/15/", "/11/"), Latitude, Longitude, gymImage); + var staticMapLink = Utils.GetStaticMapsUrl(templatePath, whConfig.Urls.StaticMap.Replace("/15/", "/11/"), Latitude, Longitude, /*TODO: Add team image*/string.Empty, Team); //var staticMapLink = string.Format(whConfig.Urls.StaticMap, Latitude, Longitude);//whConfig.Urls.StaticMap.Gyms.Enabled ? string.Format(whConfig.Urls.StaticMap.Gyms.Url, Latitude, Longitude) : string.Empty var gmapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? gmapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, gmapsLink); var appleMapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? appleMapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, appleMapsLink); var wazeMapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? wazeMapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, wazeMapsLink); + var scannerMapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? scannerMapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, scannerMapsLink); + var googleAddress = Utils.GetGoogleAddress(city, Latitude, Longitude, whConfig.GoogleMapsKey); //var staticMapLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? staticMapLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, staticMapLink); const string defaultMissingValue = "?"; @@ -122,7 +153,11 @@ private IReadOnlyDictionary GetProperties(DiscordGuild guild, Wh { "under_attack", Convert.ToString(InBattle) }, { "is_ex", Convert.ToString(SponsorId) }, { "ex_emoji", exEmoji }, - { "slots_available", SlotsAvailable.ToString("N0") }, + { "slots_available", SlotsAvailable == 0 + ? "Full" + : SlotsAvailable == 6 + ? "Empty" + : SlotsAvailable.ToString("N0") }, //Location properties { "geofence", city ?? defaultMissingValue }, @@ -136,6 +171,9 @@ private IReadOnlyDictionary GetProperties(DiscordGuild guild, Wh { "gmaps_url", gmapsLocationLink }, { "applemaps_url", appleMapsLocationLink }, { "wazemaps_url", wazeMapsLocationLink }, + { "scanmaps_url", scannerMapsLocationLink }, + + { "address", googleAddress?.Address }, // Discord Guild properties { "guild_name", guild?.Name }, @@ -148,5 +186,27 @@ private IReadOnlyDictionary GetProperties(DiscordGuild guild, Wh }; return dict; } + + internal static Dictionary GetGyms(string connectionString = "") + { + if (string.IsNullOrEmpty(connectionString)) + return null; + + try + { + using (var db = DataAccessLayer.CreateFactory(connectionString).Open()) + { + var gyms = db.LoadSelect(); + var dict = gyms?.ToDictionary(x => x.GymId, x => x); + return dict; + } + } + catch (Exception ex) + { + _logger.Error(ex); + } + + return null; + } } } \ No newline at end of file diff --git a/src/Net/Models/PokemonData.cs b/src/Net/Models/PokemonData.cs index caba0712..5d36a892 100644 --- a/src/Net/Models/PokemonData.cs +++ b/src/Net/Models/PokemonData.cs @@ -397,27 +397,24 @@ public async Task GeneratePokemonMessage(ulong guildId var server = whConfig.Servers[guildId]; var pokemonImageUrl = Id.GetPokemonIcon(FormId, Costume, whConfig, server.IconStyle); var properties = await GetProperties(client.Guilds[guildId], whConfig, city, pokemonImageUrl); - var mention = DynamicReplacementEngine.ReplaceText(alarm?.Mentions ?? string.Empty, properties); - var description = DynamicReplacementEngine.ReplaceText(alert.Content, properties); - var footerText = DynamicReplacementEngine.ReplaceText(alert.Footer?.Text ?? client.Guilds[guildId]?.Name ?? $"{Strings.Creator} | {DateTime.Now}", properties); - var footerIconUrl = DynamicReplacementEngine.ReplaceText(alert.Footer?.IconUrl ?? client.Guilds[guildId]?.IconUrl ?? string.Empty, properties); var eb = new DiscordEmbedBuilder { Title = DynamicReplacementEngine.ReplaceText(alert.Title, properties), Url = DynamicReplacementEngine.ReplaceText(alert.Url, properties), ImageUrl = DynamicReplacementEngine.ReplaceText(alert.ImageUrl, properties), ThumbnailUrl = DynamicReplacementEngine.ReplaceText(alert.IconUrl, properties), - Description = mention + description, + Description = DynamicReplacementEngine.ReplaceText(alert.Content, properties), Color = IV.BuildColor(), Footer = new DiscordEmbedBuilder.EmbedFooter { - Text = footerText, - IconUrl = footerIconUrl + Text = DynamicReplacementEngine.ReplaceText(alert.Footer?.Text ?? client.Guilds[guildId]?.Name ?? DateTime.Now.ToString(), properties), + IconUrl = DynamicReplacementEngine.ReplaceText(alert.Footer?.IconUrl ?? client.Guilds[guildId]?.IconUrl ?? string.Empty, properties) } }; var username = DynamicReplacementEngine.ReplaceText(alert.Username, properties); var iconUrl = DynamicReplacementEngine.ReplaceText(alert.AvatarUrl, properties); - return await Task.FromResult(new DiscordEmbedNotification(username, iconUrl, new List { eb.Build() })); + var description = DynamicReplacementEngine.ReplaceText(alarm?.Description, properties); + return await Task.FromResult(new DiscordEmbedNotification(username, iconUrl, description, new List { eb.Build() })); } #endregion @@ -459,11 +456,14 @@ private async Task> GetProperties(DiscordGui 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 templatePath = Path.Combine(whConfig.StaticMaps.TemplatesFolder, whConfig.StaticMaps.PokemonTemplateFile); - var staticMapLink = Utils.GetStaticMapsUrl(templatePath, whConfig.Urls.StaticMap, Latitude, Longitude, pokemonImageUrl); + var staticMapLink = Utils.GetStaticMapsUrl(templatePath, whConfig.Urls.StaticMap, Latitude, Longitude, pokemonImageUrl, null); var gmapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? gmapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, gmapsLink); var appleMapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? appleMapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, appleMapsLink); var wazeMapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? wazeMapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, wazeMapsLink); + var scannerMapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? scannerMapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, scannerMapsLink); + var googleAddress = Utils.GetGoogleAddress(city, Latitude, Longitude, whConfig.GoogleMapsKey); //var staticMapLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? staticMapLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, staticMapLink); var pokestop = Pokestop.Pokestops.ContainsKey(PokestopId) ? Pokestop.Pokestops[PokestopId] : null; @@ -560,6 +560,9 @@ private async Task> GetProperties(DiscordGui { "gmaps_url", gmapsLocationLink }, { "applemaps_url", appleMapsLocationLink }, { "wazemaps_url", wazeMapsLocationLink }, + { "scanmaps_url", scannerMapsLocationLink }, + + { "address", googleAddress?.Address }, // Pokestop properties { "near_pokestop", Convert.ToString(pokestop != null) }, @@ -694,12 +697,15 @@ public class DiscordEmbedNotification public string IconUrl { get; set; } + public string Description { get; set; } + public List Embeds { get; set; } - public DiscordEmbedNotification(string username, string iconUrl, List embeds) + public DiscordEmbedNotification(string username, string iconUrl, string description, List embeds) { Username = username; IconUrl = iconUrl; + Description = description; Embeds = embeds; } } diff --git a/src/Net/Models/PokestopData.cs b/src/Net/Models/PokestopData.cs index e9e87cb3..d6324c70 100644 --- a/src/Net/Models/PokestopData.cs +++ b/src/Net/Models/PokestopData.cs @@ -107,17 +107,13 @@ public DiscordEmbedNotification GeneratePokestopMessage(ulong guildId, DiscordCl var alertType = HasInvasion ? AlertMessageType.Invasions : HasLure ? AlertMessageType.Lures : AlertMessageType.Pokestops; var alert = alarm?.Alerts[alertType] ?? AlertMessage.Defaults[alertType]; var properties = GetProperties(client.Guilds[guildId], whConfig, city); - var mention = DynamicReplacementEngine.ReplaceText(alarm?.Mentions ?? string.Empty, properties); - var description = DynamicReplacementEngine.ReplaceText(alert.Content, properties); - var footerText = DynamicReplacementEngine.ReplaceText(alert.Footer?.Text ?? client.Guilds[guildId]?.Name ?? $"{Strings.Creator} | {DateTime.Now}", properties); - var footerIconUrl = DynamicReplacementEngine.ReplaceText(alert.Footer?.IconUrl ?? client.Guilds[guildId]?.IconUrl ?? string.Empty, properties); var eb = new DiscordEmbedBuilder { Title = DynamicReplacementEngine.ReplaceText(alert.Title, properties), Url = DynamicReplacementEngine.ReplaceText(alert.Url, properties), ImageUrl = DynamicReplacementEngine.ReplaceText(alert.ImageUrl, properties), ThumbnailUrl = DynamicReplacementEngine.ReplaceText(alert.IconUrl, properties), - Description = mention + description, + Description = DynamicReplacementEngine.ReplaceText(alert.Content, properties), Color = HasInvasion ? DiscordColor.Red : HasLure ? (LureType == PokestopLureType.Normal ? DiscordColor.HotPink : LureType == PokestopLureType.Glacial ? DiscordColor.CornflowerBlue @@ -126,13 +122,14 @@ public DiscordEmbedNotification GeneratePokestopMessage(ulong guildId, DiscordCl : DiscordColor.CornflowerBlue) : DiscordColor.CornflowerBlue, Footer = new DiscordEmbedBuilder.EmbedFooter { - Text = footerText, - IconUrl = footerIconUrl + Text = DynamicReplacementEngine.ReplaceText(alert.Footer?.Text ?? client.Guilds[guildId]?.Name ?? DateTime.Now.ToString(), properties), + IconUrl = DynamicReplacementEngine.ReplaceText(alert.Footer?.IconUrl ?? client.Guilds[guildId]?.IconUrl ?? string.Empty, properties) } }; var username = DynamicReplacementEngine.ReplaceText(alert.Username, properties); var iconUrl = DynamicReplacementEngine.ReplaceText(alert.AvatarUrl, properties); - return new DiscordEmbedNotification(username, iconUrl, new List { eb.Build() }); + var description = DynamicReplacementEngine.ReplaceText(alarm?.Description, properties); + return new DiscordEmbedNotification(username, iconUrl, description, new List { eb.Build() }); } #endregion @@ -147,11 +144,14 @@ private IReadOnlyDictionary GetProperties(DiscordGuild guild, Wh 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 templatePath = Path.Combine(whConfig.StaticMaps.TemplatesFolder, HasInvasion ? whConfig.StaticMaps.InvasionsTemplateFile : HasLure ? whConfig.StaticMaps.LuresTemplateFile : whConfig.StaticMaps.LuresTemplateFile); - var staticMapLink = Utils.GetStaticMapsUrl(templatePath, whConfig.Urls.StaticMap, Latitude, Longitude, imageUrl); + var staticMapLink = Utils.GetStaticMapsUrl(templatePath, whConfig.Urls.StaticMap, Latitude, Longitude, imageUrl, null); var gmapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? gmapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, gmapsLink); var appleMapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? appleMapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, appleMapsLink); var wazeMapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? wazeMapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, wazeMapsLink); + var scannerMapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? scannerMapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, scannerMapsLink); + var googleAddress = Utils.GetGoogleAddress(city, Latitude, Longitude, whConfig.GoogleMapsKey); //var staticMapLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? staticMapLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, staticMapLink); var invasion = MasterFile.Instance.GruntTypes.ContainsKey(GruntType) ? MasterFile.Instance.GruntTypes[GruntType] : null; var leaderString = Translator.Instance.Translate("grunt_" + Convert.ToInt32(GruntType)); @@ -189,6 +189,7 @@ private IReadOnlyDictionary GetProperties(DiscordGuild guild, Wh { "gmaps_url", gmapsLocationLink }, { "applemaps_url", appleMapsLocationLink }, { "wazemaps_url", wazeMapsLocationLink }, + { "scanmaps_url", scannerMapsLocationLink }, //Pokestop properties { "pokestop_id", PokestopId ?? defaultMissingValue }, @@ -197,6 +198,8 @@ private IReadOnlyDictionary GetProperties(DiscordGuild guild, Wh { "lure_img_url", lureImageUrl }, { "invasion_img_url", invasionImageUrl }, + { "address", googleAddress?.Address }, + // Discord Guild properties { "guild_name", guild?.Name }, { "guild_img_url", guild?.IconUrl }, diff --git a/src/Net/Models/QuestData.cs b/src/Net/Models/QuestData.cs index 8d862aae..e8b97525 100644 --- a/src/Net/Models/QuestData.cs +++ b/src/Net/Models/QuestData.cs @@ -79,30 +79,29 @@ public QuestData() Conditions = new List(); } - public DiscordEmbed GenerateQuestMessage(ulong guildId, DiscordClient client, WhConfig whConfig, AlarmObject alarm, string city) + public DiscordEmbedNotification GenerateQuestMessage(ulong guildId, DiscordClient client, WhConfig whConfig, AlarmObject alarm, string city) { var alertType = AlertMessageType.Quests; var alert = alarm?.Alerts[alertType] ?? AlertMessage.Defaults[alertType]; var properties = GetProperties(client.Guilds[guildId], whConfig, city, this.GetQuestIcon(whConfig, whConfig.Servers[guildId].IconStyle)); - var mention = DynamicReplacementEngine.ReplaceText(alarm?.Mentions ?? string.Empty, properties); - var description = DynamicReplacementEngine.ReplaceText(alert.Content, properties); - var footerText = DynamicReplacementEngine.ReplaceText(alert.Footer?.Text ?? client.Guilds[guildId]?.Name ?? $"{Strings.Creator} | {DateTime.Now}", properties); - var footerIconUrl = DynamicReplacementEngine.ReplaceText(alert.Footer?.IconUrl ?? client.Guilds[guildId]?.IconUrl ?? string.Empty, properties); var eb = new DiscordEmbedBuilder { Title = DynamicReplacementEngine.ReplaceText(alert.Title, properties), Url = DynamicReplacementEngine.ReplaceText(alert.Url, properties), ImageUrl = DynamicReplacementEngine.ReplaceText(alert.ImageUrl, properties), ThumbnailUrl = DynamicReplacementEngine.ReplaceText(alert.IconUrl, properties), - Description = mention + description, + Description = DynamicReplacementEngine.ReplaceText(alert.Content, properties), Color = DiscordColor.Orange, Footer = new DiscordEmbedBuilder.EmbedFooter { - Text = footerText, - IconUrl = footerIconUrl + Text = DynamicReplacementEngine.ReplaceText(alert.Footer?.Text ?? client.Guilds[guildId]?.Name ?? DateTime.Now.ToString(), properties), + IconUrl = DynamicReplacementEngine.ReplaceText(alert.Footer?.IconUrl ?? client.Guilds[guildId]?.IconUrl ?? string.Empty, properties) } }; - return eb.Build(); + var username = DynamicReplacementEngine.ReplaceText(alert.Username, properties); + var iconUrl = DynamicReplacementEngine.ReplaceText(alert.AvatarUrl, properties); + var description = DynamicReplacementEngine.ReplaceText(alarm?.Description, properties); + return new DiscordEmbedNotification(username, iconUrl, description, new List { eb.Build() }); } private IReadOnlyDictionary GetProperties(DiscordGuild guild, WhConfig whConfig, string city, string questRewardImageUrl) @@ -113,11 +112,14 @@ private IReadOnlyDictionary GetProperties(DiscordGuild guild, Wh 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 templatePath = Path.Combine(whConfig.StaticMaps.TemplatesFolder, whConfig.StaticMaps.QuestsTemplateFile); - var staticMapLink = Utils.GetStaticMapsUrl(templatePath, whConfig.Urls.StaticMap, Latitude, Longitude, questRewardImageUrl); + var staticMapLink = Utils.GetStaticMapsUrl(templatePath, whConfig.Urls.StaticMap, Latitude, Longitude, questRewardImageUrl, null); var gmapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? gmapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, gmapsLink); var appleMapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? appleMapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, appleMapsLink); var wazeMapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? wazeMapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, wazeMapsLink); + var scannerMapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? scannerMapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, scannerMapsLink); + var googleAddress = Utils.GetGoogleAddress(city, Latitude, Longitude, whConfig.GoogleMapsKey); //var staticMapLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? staticMapLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, staticMapLink); const string defaultMissingValue = "?"; @@ -144,6 +146,9 @@ private IReadOnlyDictionary GetProperties(DiscordGuild guild, Wh { "gmaps_url", gmapsLocationLink }, { "applemaps_url", appleMapsLocationLink }, { "wazemaps_url", wazeMapsLocationLink }, + { "scanmaps_url", scannerMapsLocationLink }, + + { "address", googleAddress?.Address }, //Pokestop properties { "pokestop_id", PokestopId ?? defaultMissingValue }, diff --git a/src/Net/Models/RaidData.cs b/src/Net/Models/RaidData.cs index 03e3d074..7fb515b0 100644 --- a/src/Net/Models/RaidData.cs +++ b/src/Net/Models/RaidData.cs @@ -147,27 +147,24 @@ public DiscordEmbedNotification GenerateRaidMessage(ulong guildId, DiscordClient this.GetRaidEggIcon(whConfig, server.IconStyle) : PokemonId.GetPokemonIcon(Form, 0, whConfig, server.IconStyle); var properties = GetProperties(client.Guilds[guildId], whConfig, city, raidImageUrl); - var mention = DynamicReplacementEngine.ReplaceText(alarm?.Mentions ?? string.Empty, properties); - var description = DynamicReplacementEngine.ReplaceText(alert.Content, properties); - var footerText = DynamicReplacementEngine.ReplaceText(alert.Footer?.Text ?? client.Guilds[guildId]?.Name ?? $"{Strings.Creator} | {DateTime.Now}", properties); - var footerIconUrl = DynamicReplacementEngine.ReplaceText(alert.Footer?.IconUrl ?? client.Guilds[guildId]?.IconUrl ?? string.Empty, properties); var eb = new DiscordEmbedBuilder { Title = DynamicReplacementEngine.ReplaceText(alert.Title, properties), Url = DynamicReplacementEngine.ReplaceText(alert.Url, properties), ImageUrl = DynamicReplacementEngine.ReplaceText(alert.ImageUrl, properties), ThumbnailUrl = DynamicReplacementEngine.ReplaceText(alert.IconUrl, properties), - Description = mention + description, + Description = DynamicReplacementEngine.ReplaceText(alert.Content, properties), Color = Level.BuildRaidColor(), Footer = new DiscordEmbedBuilder.EmbedFooter { - Text = footerText, - IconUrl = footerIconUrl + Text = DynamicReplacementEngine.ReplaceText(alert.Footer?.Text ?? client.Guilds[guildId]?.Name ?? DateTime.Now.ToString(), properties), + IconUrl = DynamicReplacementEngine.ReplaceText(alert.Footer?.IconUrl ?? client.Guilds[guildId]?.IconUrl ?? string.Empty, properties) } }; var username = DynamicReplacementEngine.ReplaceText(alert.Username, properties); var iconUrl = DynamicReplacementEngine.ReplaceText(alert.AvatarUrl, properties); - return new DiscordEmbedNotification(username, iconUrl, new List { eb.Build() }); + var description = DynamicReplacementEngine.ReplaceText(alarm?.Description, properties); + return new DiscordEmbedNotification(username, iconUrl, description, new List { eb.Build() }); } private IReadOnlyDictionary GetProperties(DiscordGuild guild, WhConfig whConfig, string city, string raidImageUrl) @@ -198,11 +195,14 @@ private IReadOnlyDictionary GetProperties(DiscordGuild guild, Wh 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 templatePath = Path.Combine(whConfig.StaticMaps.TemplatesFolder, whConfig.StaticMaps.RaidsTemplateFile); - var staticMapLink = Utils.GetStaticMapsUrl(templatePath, whConfig.Urls.StaticMap, Latitude, Longitude, raidImageUrl); + var staticMapLink = Utils.GetStaticMapsUrl(templatePath, whConfig.Urls.StaticMap, Latitude, Longitude, raidImageUrl, Team); var gmapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? gmapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, gmapsLink); var appleMapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? appleMapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, appleMapsLink); var wazeMapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? wazeMapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, wazeMapsLink); + var scannerMapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? scannerMapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, scannerMapsLink); + var googleAddress = Utils.GetGoogleAddress(city, Latitude, Longitude, whConfig.GoogleMapsKey); //var staticMapLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? staticMapLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, staticMapLink); const string defaultMissingValue = "?"; @@ -257,6 +257,9 @@ private IReadOnlyDictionary GetProperties(DiscordGuild guild, Wh { "gmaps_url", gmapsLocationLink }, { "applemaps_url", appleMapsLocationLink }, { "wazemaps_url", wazeMapsLocationLink }, + { "scanmaps_url", scannerMapsLocationLink }, + + { "address", googleAddress?.Address }, //Gym properties { "gym_id", GymId }, diff --git a/src/Net/Models/WeatherData.cs b/src/Net/Models/WeatherData.cs index d0bf77b4..2b0cf94c 100644 --- a/src/Net/Models/WeatherData.cs +++ b/src/Net/Models/WeatherData.cs @@ -115,27 +115,24 @@ public DiscordEmbedNotification GenerateWeatherMessage(ulong guildId, DiscordCli var server = whConfig.Servers[guildId]; var weatherImageUrl = this.GetWeatherIcon(whConfig, server.IconStyle); var properties = GetProperties(client.Guilds[guildId], whConfig, city, weatherImageUrl); - var mention = DynamicReplacementEngine.ReplaceText(alarm.Mentions, properties); - var description = DynamicReplacementEngine.ReplaceText(alert.Content, properties); - var footerText = DynamicReplacementEngine.ReplaceText(alert.Footer?.Text ?? client.Guilds[guildId]?.Name ?? $"{Strings.Creator} | {DateTime.Now}", properties); - var footerIconUrl = DynamicReplacementEngine.ReplaceText(alert.Footer?.IconUrl ?? client.Guilds[guildId]?.IconUrl ?? string.Empty, properties); var eb = new DiscordEmbedBuilder { Title = DynamicReplacementEngine.ReplaceText(alert.Title, properties), Url = DynamicReplacementEngine.ReplaceText(alert.Url, properties), ImageUrl = DynamicReplacementEngine.ReplaceText(alert.ImageUrl, properties), ThumbnailUrl = DynamicReplacementEngine.ReplaceText(alert.IconUrl, properties), - Description = mention + description, + Description = DynamicReplacementEngine.ReplaceText(alert.Content, properties), Color = GameplayCondition.BuildWeatherColor(), Footer = new DiscordEmbedBuilder.EmbedFooter { - Text = footerText, - IconUrl = footerIconUrl + Text = DynamicReplacementEngine.ReplaceText(alert.Footer?.Text ?? client.Guilds[guildId]?.Name ?? DateTime.Now.ToString(), properties), + IconUrl = DynamicReplacementEngine.ReplaceText(alert.Footer?.IconUrl ?? client.Guilds[guildId]?.IconUrl ?? string.Empty, properties) } }; var username = DynamicReplacementEngine.ReplaceText(alert.Username, properties); var iconUrl = DynamicReplacementEngine.ReplaceText(alert.AvatarUrl, properties); - return new DiscordEmbedNotification(username, iconUrl, new List { eb.Build() }); + var description = DynamicReplacementEngine.ReplaceText(alarm?.Description, properties); + return new DiscordEmbedNotification(username, iconUrl, description, new List { eb.Build() }); } private IReadOnlyDictionary GetProperties(DiscordGuild guild, WhConfig whConfig, string city, string weatherImageUrl) @@ -147,11 +144,14 @@ private IReadOnlyDictionary GetProperties(DiscordGuild guild, Wh 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 templatePath = Path.Combine(whConfig.StaticMaps.TemplatesFolder, whConfig.StaticMaps.WeatherTemplateFile); - var staticMapLink = Utils.GetStaticMapsUrl(templatePath, whConfig.Urls.StaticMap.Replace("/15/", "/11/"), Latitude, Longitude, weatherImageUrl); + var staticMapLink = Utils.GetStaticMapsUrl(templatePath, whConfig.Urls.StaticMap.Replace("/15/", "/11/"), Latitude, Longitude, weatherImageUrl, null); var gmapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? gmapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, gmapsLink); var appleMapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? appleMapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, appleMapsLink); var wazeMapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? wazeMapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, wazeMapsLink); + var scannerMapsLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? scannerMapsLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, scannerMapsLink); + var googleAddress = Utils.GetGoogleAddress(city, Latitude, Longitude, whConfig.GoogleMapsKey); //var staticMapLocationLink = string.IsNullOrEmpty(whConfig.ShortUrlApiUrl) ? staticMapLink : NetUtil.CreateShortUrl(whConfig.ShortUrlApiUrl, staticMapLink); const string defaultMissingValue = "?"; @@ -187,6 +187,9 @@ private IReadOnlyDictionary GetProperties(DiscordGuild guild, Wh { "gmaps_url", gmapsLocationLink }, { "applemaps_url", appleMapsLocationLink }, { "wazemaps_url", wazeMapsLocationLink }, + { "scanmaps_url", scannerMapsLocationLink }, + + { "address", googleAddress?.Address }, // Discord Guild properties { "guild_name", guild?.Name }, diff --git a/src/Net/Webhooks/WebhookController.cs b/src/Net/Webhooks/WebhookController.cs index 63f8ea1f..87414774 100644 --- a/src/Net/Webhooks/WebhookController.cs +++ b/src/Net/Webhooks/WebhookController.cs @@ -31,8 +31,8 @@ public class WebhookController private readonly Dictionary _alarms; private readonly IReadOnlyDictionary _servers; private readonly WhConfig _config; - private readonly Dictionary _gyms; private readonly Dictionary _weather; + private Dictionary _gyms; #endregion @@ -46,7 +46,17 @@ public class WebhookController /// /// Gyms cache /// - public IReadOnlyDictionary Gyms => _gyms; + public IReadOnlyDictionary Gyms + { + get + { + if (_gyms == null) + { + _gyms = GymDetailsData.GetGyms(Data.DataAccessLayer.ScannerConnectionString); + } + return _gyms; + } + } /// /// Weather cells cache @@ -800,16 +810,6 @@ private void ProcessGymDetails(GymDetailsData gymDetails) continue; } - if (!_gyms.ContainsKey(gymDetails.GymId)) - { - _gyms.Add(gymDetails.GymId, gymDetails); - } - - var oldGym = _gyms[gymDetails.GymId]; - var changed = oldGym.Team != gymDetails.Team;// || /*oldGym.InBattle != gymDetails.InBattle ||*/ gymDetails.InBattle; - if (!changed) - return; - if ((alarm.Filters?.Gyms?.UnderAttack ?? false) && !gymDetails.InBattle) { //_logger.Info($"[{alarm.Name}] Skipping gym details GymId={gymDetails.GymId}, GymName{gymDetails.GymName}, not under attack."); @@ -822,6 +822,20 @@ private void ProcessGymDetails(GymDetailsData gymDetails) continue; } + if (!_gyms.ContainsKey(gymDetails.GymId)) + { + _gyms.Add(gymDetails.GymId, gymDetails); + //OnGymDetailsAlarmTriggered(gymDetails, alarm, guildId); + //continue; + } + + /* + var oldGym = _gyms[gymDetails.GymId]; + var changed = oldGym.Team != gymDetails.Team || gymDetails.InBattle; + if (!changed) + return; + */ + OnGymDetailsAlarmTriggered(gymDetails, alarm, guildId); } } @@ -891,6 +905,16 @@ private void ProcessWeather(WeatherData weather) #endregion + public void SetGym(string id, GymDetailsData gymDetails) + { + _gyms[id] = gymDetails; + } + + public void SetWeather(long id, WeatherType type) + { + _weather[id] = type; + } + #region Geofence Utilities /// diff --git a/src/Utilities/Utils.cs b/src/Utilities/Utils.cs index a082823b..e5b39055 100644 --- a/src/Utilities/Utils.cs +++ b/src/Utilities/Utils.cs @@ -2,30 +2,42 @@ { using System; using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net; + using System.Text; using Newtonsoft.Json; - + using Newtonsoft.Json.Linq; using Twilio; using Twilio.Rest.Api.V2010.Account; using WhMgr.Configuration; + using WhMgr.Diagnostics; + using WhMgr.Geofence; + using WhMgr.Net.Models; using WhMgr.Osm; using WhMgr.Osm.Models; public static class Utils { - public static string GetStaticMapsUrl(string templateFileName, string staticMapUrl, double latitude, double longitude, string markerImageUrl, OsmFeature feature = null, MultiPolygon multiPolygon = null) + private static readonly IEventLogger _logger = EventLogger.GetLogger("UTILS"); + + // TODO: Provide better way for replacement values + public static string GetStaticMapsUrl(string templateFileName, string staticMapUrl, double latitude, double longitude, string markerImageUrl, PokemonTeam? team, OsmFeature feature = null, MultiPolygon multiPolygon = null) { var staticMapData = Renderer.Parse(templateFileName, new { lat = latitude, lon = longitude, + team = team?.ToString(), + team_id = Convert.ToInt32(team ?? 0), marker = markerImageUrl, pkmn_img_url = markerImageUrl, quest_reward_img_url = markerImageUrl, weather_img_url = markerImageUrl, tilemaps_url = staticMapUrl - }); + }); ; StaticMapConfig staticMap = JsonConvert.DeserializeObject(staticMapData); var url = string.Format(staticMapUrl, latitude, longitude); @@ -67,5 +79,36 @@ public static bool SendSmsMessage(string body, TwilioConfig config, string toPho //Console.WriteLine($"Response: {message}"); return message.ErrorCode == null; } + + public static Location GetGoogleAddress(string city, double lat, double lng, string gmapsKey) + { + var apiKey = string.IsNullOrEmpty(gmapsKey) ? string.Empty : $"&key={gmapsKey}"; + var url = $"https://maps.googleapis.com/maps/api/geocode/json?latlng={lat},{lng}&sensor=true{apiKey}"; + var unknown = "Unknown"; + try + { + var request = (HttpWebRequest)WebRequest.Create(url); + var response = request.GetResponse(); + using (var responseStream = response.GetResponseStream()) + { + var reader = new StreamReader(responseStream, Encoding.UTF8); + var data = reader.ReadToEnd(); + var parseJson = JObject.Parse(data); + var status = Convert.ToString(parseJson["status"]); + if (string.Compare(status, "OK", true) != 0) + return null; + + var result = parseJson["results"].FirstOrDefault(); + var address = Convert.ToString(result["formatted_address"]); + //var area = Convert.ToString(result["address_components"][2]["long_name"]); + return new Location(address, city ?? unknown, lat, lng); + } + } + catch (Exception ex) + { + _logger.Error(ex); + } + return null; + } } } \ No newline at end of file diff --git a/static/locale/en.json b/static/locale/en.json index ff9eaaad..e90efa5c 100644 --- a/static/locale/en.json +++ b/static/locale/en.json @@ -19,6 +19,7 @@ "NOTIFY_INVALID_COORDINATES": "{0} Unable not parse {1} as valid coordinates.", "NOTIFY_DISTANCE_SET": "{0} Notifications only within a {1} meter radius of location {2},{3} will be sent.", + "NOTIFY_PHONE_NUMBER_SET": "{0} Text message notifications for ultra rare Pokemon will be sent to {1}.", "NOTIFY_INVALID_IV_VALUES": "{0} {1} is not a valid value. (Example: `0-15-6`)", "NOTIFY_INVALID_ATTACK_VALUE": "{0} {1} is not a valid attack value. Must be between `0-15`.",