From fcb25fc6397df9e8dc169952c7d1f63a4dedbcbe Mon Sep 17 00:00:00 2001 From: bignutty <3515180-bignutty@users.noreply.gitlab.com> Date: Sun, 17 Nov 2024 01:52:18 +0100 Subject: [PATCH] add maps --- commands/interaction/slash/search/maps.js | 219 ++++++++++++++++++++++ commands/message/search/maps.js | 209 +++++++++++++++++++++ labscore/api/endpoints.js | 2 + labscore/api/index.js | 24 ++- labscore/constants.js | 97 ++++++++++ labscore/utils/markdown.js | 13 ++ labscore/utils/statics.js | 5 + 7 files changed, 563 insertions(+), 6 deletions(-) create mode 100644 commands/interaction/slash/search/maps.js create mode 100644 commands/message/search/maps.js diff --git a/commands/interaction/slash/search/maps.js b/commands/interaction/slash/search/maps.js new file mode 100644 index 0000000..21f6756 --- /dev/null +++ b/commands/interaction/slash/search/maps.js @@ -0,0 +1,219 @@ +const { maps, mapsSupplemental } = require('#api'); + +const { createEmbed } = require('#utils/embed'); +const { acknowledge } = require('#utils/interactions'); +const { link, icon, iconAsEmojiObject } = require('#utils/markdown'); +const { editOrReply } = require('#utils/message') +const { STATICS, STATIC_ASSETS } = require('#utils/statics'); +const { ApplicationCommandOptionTypes } = require('detritus-client/lib/constants'); + +// TODO: Turn this into a general purpose permissions constant +const { Components } = require('detritus-client/lib/utils'); + +function renderPlaceCard(context, place) { + let cards = [createEmbed("defaultNoFooter", context, { + author: { + iconUrl: place.style.icon.url, + name: place.title, + url: place.url + }, + description: `${place.address.full}`, + url: place.url, + color: parseInt(place.style.color.substring(1, 7), 16) + })] + + if (place.display_type) { + cards[0].description = `-# ${place.display_type}\n\n` + cards[0].description + } + + if (place.ratings?.score) { + let ratingString = ""; + + ratingString += icon("maps_star_half").repeat(Math.floor(place.ratings.score)) + + if (place.ratings.score < 5) { + if ((place.ratings.score - Math.floor(place.ratings.score)) >= 0.5) ratingString += icon("maps_star_half") + else ratingString += icon("maps_star_empty"); + ratingString += icon("maps_star_empty").repeat(5 - Math.ceil(place.ratings.score)) + } + + cards[0].description += `\n\n> -# ${ratingString} **${place.ratings.score}** (${link(place.ratings.url, place.ratings.reviews.toLocaleString("en-US"), "Amount of user reviews")})` + } + + if (place.description) { + cards[0].description += `\n\n${place.description}` + } + + + if (place.photos?.length) { + + cards[0].image = { + url: place.photos.shift() + } + + if (place.photos.length) { + for (const p of place.photos) { + cards.push({ + url: place.url, + image: { + url: p + } + }) + } + } + } + + return cards; +} + +module.exports = { + name: 'maps', + description: 'Search for places on Google Maps.', + contexts: [ + 0, + 1, + 2 + ], + integrationTypes: [ + 1 + ], + options: [ + { + name: 'query', + description: 'Google Maps search query.', + type: ApplicationCommandOptionTypes.TEXT, + required: true + }, + { + name: 'incognito', + description: 'Makes the response only visible to you.', + type: ApplicationCommandOptionTypes.BOOLEAN, + required: false, + default: false + } + ], + run: async (context, args) => { + await acknowledge(context, args.incognito); + + try { + let search = await maps(context, args.query) + search = search.response.body + + + // Create initial response page + let embeds = []; + + let mapCard = createEmbed("default", context, { + image: { + url: search.assets.map + }, + footer: { + iconUrl: STATICS.googlemaps, + text: `Map Data ©2024 Google • ${context.application.name}` + } + }) + + embeds.push(mapCard) + + let components = []; + + // Instant Place Result + if (search.place) { + embeds = [...embeds, ...renderPlaceCard(context, search.place)] + } else { + let supplementalCache = {} + + components = new Components({ + timeout: 100000, + run: async (ctx) => { + if (ctx.userId !== context.userId) return await ctx.respond(InteractionCallbackTypes.DEFERRED_UPDATE_MESSAGE); + + try { + // Disable component and update the default + + let value = ctx.data.values[0]; + + let searchSupplemental; + + for (let i = 0; i < components.components[0].components[0].options.length; i++) { + let c = components.components[0].components[0]; + + components.components[0].components[0].options[i].default = (components.components[0].components[0].options[i].value == value) + components.components[0].components[0].options[i].emoji = iconAsEmojiObject(`maps_${search.places[i].place.icon}_pin`) + + // make the selected pin red + if(c.options[i].value === value) components.components[0].components[0].options[i].emoji = iconAsEmojiObject(`maps_location_pin`); + } + + if (!supplementalCache[value]) { + components.components[0].components[0].disabled = true; + + await ctx.editOrRespond({ + embeds: [mapCard, createEmbed("defaultNoFooter", context, { + image: { + url: STATIC_ASSETS.chat_loading + } + })], + components + }) + + searchSupplemental = await mapsSupplemental(context, search.places[parseInt(value)].supplemental_key) + + searchSupplemental = searchSupplemental.response.body + supplementalCache[value] = searchSupplemental + + components.components[0].components[0].disabled = false; + } else { + searchSupplemental = supplementalCache[value] + } + + mapCard = createEmbed("default", context, { + image: { + url: searchSupplemental.assets.map + }, + footer: { + iconUrl: STATICS.googlemaps, + text: `Map Data ©2024 Google • ${context.application.name}` + } + }) + + await ctx.editOrRespond({ + embeds: [mapCard, ...renderPlaceCard(context, searchSupplemental.place)], + components + }) + } catch (e) { + console.log(e) + components.components[0].components[0].disabled = false; + await ctx.editOrRespond({ + embeds: [mapCard, createEmbed("error", context, "Something went wrong trying to view this place.")], + components + }) + } + } + }) + + components.addSelectMenu({ + type: 3, + custom_id: "place-picker", + placeholder: "Select a place", + defaultValues: [], + options: search.places.map((p, i) => ({ + label: p.place.name, + value: `${i}`, + emoji: icon(`maps_${p.place.icon}_pin`), + description: p.place.address + })) + }) + } + + return await editOrReply(context, { + embeds, + components + }) + } catch (e) { + if (e.response?.body?.status && e.response.body.status == 2 && e.response.body.message) return editOrReply(context, createEmbed("warning", context, e.response.body.message)) + console.log(JSON.stringify(e.raw) || e) + return editOrReply(context, createEmbed("error", context, `Something went wrong.`)) + } + }, +}; \ No newline at end of file diff --git a/commands/message/search/maps.js b/commands/message/search/maps.js new file mode 100644 index 0000000..98b1c68 --- /dev/null +++ b/commands/message/search/maps.js @@ -0,0 +1,209 @@ +const { maps, mapsSupplemental } = require('#api'); + +const { createEmbed } = require('#utils/embed') +const { smallIconPill, link, icon, iconAsEmojiObject } = require('#utils/markdown'); +const { editOrReply } = require('#utils/message') +const { STATICS, STATIC_ASSETS } = require('#utils/statics') + +// TODO: Turn this into a general purpose permissions constant +const { Permissions } = require("detritus-client/lib/constants"); +const { Components } = require('detritus-client/lib/utils'); +const { description } = require('../../interaction/slash/search/google'); + +function renderPlaceCard(context, place) { + let cards = [createEmbed("defaultNoFooter", context, { + author: { + iconUrl: place.style.icon.url, + name: place.title, + url: place.url + }, + description: `${place.address.full}`, + url: place.url, + color: parseInt(place.style.color.substring(1, 7), 16) + })] + + if (place.display_type) { + cards[0].description = `-# ${place.display_type}\n\n` + cards[0].description + } + + if (place.ratings?.score) { + let ratingString = ""; + + ratingString += icon("maps_star_half").repeat(Math.floor(place.ratings.score)) + + if (place.ratings.score < 5) { + if ((place.ratings.score - Math.floor(place.ratings.score)) >= 0.5) ratingString += icon("maps_star_half") + else ratingString += icon("maps_star_empty"); + ratingString += icon("maps_star_empty").repeat(5 - Math.ceil(place.ratings.score)) + } + + cards[0].description += `\n\n> -# ${ratingString} **${place.ratings.score}** (${link(place.ratings.url, place.ratings.reviews.toLocaleString("en-US"), "Amount of user reviews")})` + } + + if (place.description) { + cards[0].description += `\n\n${place.description}` + } + + + if (place.photos?.length) { + + cards[0].image = { + url: place.photos.shift() + } + + if (place.photos.length) { + for (const p of place.photos) { + cards.push({ + url: place.url, + image: { + url: p + } + }) + } + } + } + + return cards; +} + +module.exports = { + name: 'maps', + aliases: ['m','map','googlemaps','gm'], + label: 'query', + metadata: { + description: 'Searches for places on Google Maps.', + description_short: 'Search google maps', + examples: ['m Empire State Building'], + category: 'search', + usage: 'maps ', + slashCommand: "maps" + }, + permissionsClient: [Permissions.EMBED_LINKS, Permissions.SEND_MESSAGES, Permissions.USE_EXTERNAL_EMOJIS, Permissions.READ_MESSAGE_HISTORY, Permissions.READ_MESSAGE_HISTORY], + run: async (context, args) => { + context.triggerTyping(); + if (!args.query) return editOrReply(context, createEmbed("warning", context, `Missing Parameter (query).`)) + try { + let search = await maps(context, args.query) + search = search.response.body + + + // Create initial response page + let embeds = []; + + let mapCard = createEmbed("default", context, { + image: { + url: search.assets.map + }, + footer: { + iconUrl: STATICS.googlemaps, + text: `Map Data ©2024 Google • ${context.application.name}` + } + }) + + embeds.push(mapCard) + + let components = []; + + // Instant Place Result + if (search.place) { + embeds = [...embeds, ...renderPlaceCard(context, search.place)] + } else { + let supplementalCache = {} + + components = new Components({ + timeout: 100000, + onTimeout: async()=>{ + if(!context.response.deleted) await editOrReply({ embeds: context.response.embeds }) + }, + run: async (ctx) => { + if (ctx.userId !== context.userId) return await ctx.respond(InteractionCallbackTypes.DEFERRED_UPDATE_MESSAGE); + + try { + // Disable component and update the default + + let value = ctx.data.values[0]; + + let searchSupplemental; + + for (let i = 0; i < components.components[0].components[0].options.length; i++) { + let c = components.components[0].components[0]; + + components.components[0].components[0].options[i].default = (components.components[0].components[0].options[i].value == value) + components.components[0].components[0].options[i].emoji = iconAsEmojiObject(`maps_${search.places[i].place.icon}_pin`) + + // make the selected pin red + if(c.options[i].value === value) components.components[0].components[0].options[i].emoji = iconAsEmojiObject(`maps_location_pin`); + } + + if (!supplementalCache[value]) { + components.components[0].components[0].disabled = true; + + await ctx.editOrRespond({ + embeds: [mapCard, createEmbed("defaultNoFooter", context, { + image: { + url: STATIC_ASSETS.chat_loading + } + })], + components + }) + + searchSupplemental = await mapsSupplemental(context, search.places[parseInt(value)].supplemental_key) + + searchSupplemental = searchSupplemental.response.body + supplementalCache[value] = searchSupplemental + + components.components[0].components[0].disabled = false; + } else { + searchSupplemental = supplementalCache[value] + } + + mapCard = createEmbed("default", context, { + image: { + url: searchSupplemental.assets.map + }, + footer: { + iconUrl: STATICS.googlemaps, + text: `Map Data ©2024 Google • ${context.application.name}` + } + }) + + await ctx.editOrRespond({ + embeds: [mapCard, ...renderPlaceCard(context, searchSupplemental.place)], + components + }) + } catch (e) { + console.log(e) + components.components[0].components[0].disabled = false; + await ctx.editOrRespond({ + embeds: [mapCard, createEmbed("error", context, "Something went wrong trying to view this place.")], + components + }) + } + } + }) + + components.addSelectMenu({ + type: 3, + custom_id: "place-picker", + placeholder: "Select a place", + defaultValues: [], + options: search.places.map((p, i) => ({ + label: p.place.name, + value: `${i}`, + emoji: icon(`maps_${p.place.icon}_pin`), + description: p.place.address + })) + }) + } + + return await editOrReply(context, { + embeds, + components + }) + } catch (e) { + if (e.response?.body?.status && e.response.body.status == 2 && e.response.body.message) return editOrReply(context, createEmbed("warning", context, e.response.body.message)) + console.log(JSON.stringify(e.raw) || e) + return editOrReply(context, createEmbed("error", context, `Something went wrong.`)) + } + }, +}; \ No newline at end of file diff --git a/labscore/api/endpoints.js b/labscore/api/endpoints.js index 481fd6d..7094215 100644 --- a/labscore/api/endpoints.js +++ b/labscore/api/endpoints.js @@ -34,6 +34,8 @@ const Api = Object.freeze({ SEARCH_GOOGLE: '/search/google', SEARCH_GOOGLE_IMAGES: '/search/google-images', SEARCH_LYRICS: '/search/lyrics', + SEARCH_MAPS: '/search/maps', + SEARCH_MAPS_SUPPLEMENTAL: '/search/maps-supplemental', SEARCH_QUORA: '/search/quora', SEARCH_QUORA_RESULT: '/search/quora-result', SEARCH_REDDIT: '/search/reddit', diff --git a/labscore/api/index.js b/labscore/api/index.js index df8d195..b30d76a 100644 --- a/labscore/api/index.js +++ b/labscore/api/index.js @@ -104,12 +104,6 @@ module.exports.googleVisionWebDetection = async function(context, url){ }) } -module.exports.lyrics = async function(context, query){ - return await request(Api.SEARCH_LYRICS, "GET", {}, { - q: query - }) -} - module.exports.google = async function(context, query, nsfw){ return await request(Api.SEARCH_GOOGLE, "GET", {}, { q: query, @@ -124,6 +118,24 @@ module.exports.googleImages = async function(context, query, nsfw){ }) } +module.exports.lyrics = async function(context, query){ + return await request(Api.SEARCH_LYRICS, "GET", {}, { + q: query + }) +} + +module.exports.maps = async function(context, query){ + return await request(Api.SEARCH_MAPS, "GET", {}, { + q: query + }) +} + +module.exports.mapsSupplemental = async function(context, supplementalKey){ + return await request(Api.SEARCH_MAPS_SUPPLEMENTAL, "GET", {}, { + supplemental_key: supplementalKey + }) +} + module.exports.quora = async function(context, query){ return await request(Api.SEARCH_QUORA, "GET", {}, { q: query diff --git a/labscore/constants.js b/labscore/constants.js index 3cafe87..0d2223c 100644 --- a/labscore/constants.js +++ b/labscore/constants.js @@ -54,6 +54,7 @@ module.exports.BADGE_ICONS = Object.freeze({ "staff": "<:b:1263605715688231035>" }) +// TODO: Load icon configuration from server module.exports.ICONS = Object.freeze({ "brand": "<:ico_brand_2024:1263593574478643221>", "flask_incognito": "<:ico_flask_incognito:1263608937459224688>", @@ -218,6 +219,102 @@ module.exports.ICONS = Object.freeze({ "weather_warning_frost": "<:ico_weather_frost:1269680515145797652>", "weather_warning_flood": "<:ico_weather_flood:1269680505519865982>", + /* Maps Experience */ + + "maps_location_pin": "<:maps_red_pin:1307493148800581643>", + + "maps_star": "<:ico_star:1306748432496463903>", + "maps_star_half": "<:ico_star_half:1306748464037761064>", + "maps_star_empty": "<:ico_star_empty_alt:1306748453187096598>", + + // These icon names (maps_:type_pin) have to be synced + // with the server (search_service/utils/places). + "maps_airport_pin": "<:maps_airport_pin:1307489862349492294>", + "maps_atm_pin": "<:maps_atm_pin:1307491755188555898>", + "maps_bank_dollar_pin": "<:maps_bank_dollar_pin:1307491765493698630>", + "maps_bank_euro_pin": "<:maps_bank_euro_pin:1307491810687320175>", + "maps_bank_intl_pin": "<:maps_bank_intl_pin:1307491820749455380>", + "maps_bank_jp_pin": "<:maps_bank_jp_pin:1307491870703747142>", + "maps_bank_pound_pin": "<:maps_bank_pound_pin:1307491883941101729>", + "maps_bank_rmb_pin": "<:maps_bank_rmb_pin:1307491895836016700>", + "maps_bank_won_pin": "<:maps_bank_won_pin:1307491909559779438>", + "maps_bar_pin": "<:maps_bar_pin:1307491920880205936>", + "maps_bike_pin": "<:maps_bike_pin:1307491931168837643>", + "maps_boating_pin": "<:maps_boating_pin:1307491944410382416>", + "maps_bridge_pin": "<:maps_bridge_pin:1307491957861388288>", + "maps_cafe_pin": "<:maps_cafe_pin:1307491969110511646>", + "maps_camera_pin": "<:maps_camera_pin:1307491997090582608>", + "maps_camping_pin": "<:maps_camping_pin:1307492059736969268>", + "maps_car_rental_pin": "<:maps_car_rental_pin:1307492307406426194>", + "maps_cemetery_jp_pin": "<:maps_cemetery_jp_pin:1307492318873391245>", + "maps_cemetery_pin": "<:maps_cemetery_pin:1307492328801566741>", + "maps_city_office_jp_pin": "<:maps_city_office_jp_pin:1307492339471745054>", + "maps_civic_bldg_pin": "<:maps_civic_bldg_pin:1307492350766874705>", + "maps_civic_bldg_red_pin": "<:maps_civic_bldg_red_pin:1307492360883798046>", + "maps_civil_office_jp_pin": "<:maps_civil_office_jp_pin:1307492371566690457>", + "maps_convenience_pin": "<:maps_convenience_pin:1307492382593384509>", + "maps_dice_pin": "<:maps_dice_pin:1307492393494380594>", + "maps_dolphin_pin": "<:maps_dolphin_pin:1307492405636894822>", + "maps_dot_gray_pin": "<:maps_dot_gray_pin:1307492434980114492>", + "maps_dot_green_pin": "<:maps_dot_green_pin:1307492525841453157>", + "maps_dot_light_gray_pin": "<:maps_dot_light_gray_pin:1307492536067031060>", + "maps_dot_red_pin": "<:maps_dot_red_pin:1307492545793622036>", + "maps_ev_pin": "<:maps_ev_pin:1307492558217285652>", + "maps_event_venue_pin": "<:maps_event_venue_pin:1307492569927647293>", + "maps_ferriswheel_pin": "<:maps_ferriswheel_pin:1307492583135645770>", + "maps_fire_jp_pin": "<:maps_fire_jp_pin:1307492593587978251>", + "maps_fishing_pin": "<:maps_fishing_pin:1307492603637403708>", + "maps_flower_pin": "<:maps_flower_pin:1307492617017229342>", + "maps_gas_pin": "<:maps_gas_pin:1307492627561709649>", + "maps_glass_pin": "<:maps_glass_pin:1307492648109477960>", + "maps_golf_pin": "<:maps_golf_pin:1307492660591722570>", + "maps_government_cn_pin": "<:maps_government_cn_pin:1307492671157440522>", + "maps_hiking_pin": "<:maps_hiking_pin:1307492767307530262>", + "maps_historic_cn_pin": "<:maps_historic_cn_pin:1307492779211096204>", + "maps_historic_pin": "<:maps_historic_pin:1307492790988574750>", + "maps_hospital_pin": "<:maps_hospital_pin:1307492801147310111>", + "maps_hotspring_pin": "<:maps_hotspring_pin:1307492810987147299>", + "maps_library_pin": "<:maps_library_pin:1307492821879492770>", + "maps_lighthouse_pin": "<:maps_lighthouse_pin:1307492833837580338>", + "maps_lodging_pin": "<:maps_lodging_pin:1307492844952358922>", + "maps_medical_pin": "<:maps_medical_pin:1307492856872566896>", + "maps_monument_pin": "<:maps_monument_pin:1307492867627028511>", + "maps_mountain_pin": "<:maps_mountain_pin:1307492876674138243>", + "maps_movie_pin": "<:maps_movie_pin:1307492902561382410>", + "maps_museum_jp_pin": "<:maps_museum_jp_pin:1307492943220703352>", + "maps_museum_pin": "<:maps_museum_pin:1307493021268574349>", + "maps_note_pin": "<:maps_note_pin:1307493031485902950>", + "maps_palette_pin": "<:maps_palette_pin:1307493053287890964>", + "maps_parking_pin": "<:maps_parking_pin:1307493063786238042>", + "maps_paw_pin": "<:maps_paw_pin:1307493075609981020>", + "maps_pharmacy_pin": "<:maps_pharmacy_pin:1307493086586212532>", + "maps_police_jp_pin": "<:maps_police_jp_pin:1307493097726410802>", + "maps_police_pin": "<:maps_police_pin:1307493109365608528>", + "maps_postoffice_jp_pin": "<:maps_postoffice_jp_pin:1307493121340215316>", + "maps_postoffice_pin": "<:maps_postoffice_pin:1307493134158266429>", + "maps_relic_jp_pin": "<:maps_relic_jp_pin:1307493185194561577>", + "maps_resort_pin": "<:maps_resort_pin:1307493196099747911>", + "maps_restaurant_pin": "<:maps_restaurant_pin:1307493206493237348>", + "maps_restroom_pin": "<:maps_restroom_pin:1307493216916082698>", + "maps_school_pin": "<:maps_school_pin:1307493226734948393>", + "maps_shopping_pin": "<:maps_shopping_pin:1307493252462678107>", + "maps_shoppingcart_pin": "<:maps_shoppingcart_pin:1307493264848453652>", + "maps_stadium_pin": "<:maps_stadium_pin:1307493275812237372>", + "maps_street_pin": "<:maps_street_pin:1307493286067306576>", + "maps_theater_pin": "<:maps_theater_pin:1307493296096149505>", + "maps_transit_pin": "<:maps_transit_pin:1307493312693010492>", + "maps_tree_pin": "<:maps_tree_pin:1307493327658291281>", + "maps_worship_buddhist_pin": "<:maps_worship_buddhist_pin:1307493338156503131>", + "maps_worship_christian_pin": "<:maps_worship_christian_pin:1307493348688396408>", + "maps_worship_dharma_pin": "<:maps_worship_dharma_pin:1307493368028201020>", + "maps_worship_hindu_pin": "<:maps_worship_hindu_pin:1307493380053270608>", + "maps_worship_jain_pin": "<:maps_worship_jain_pin:1307493390987821117>", + "maps_worship_jewish_pin": "<:maps_worship_jewish_pin:1307493402396459091>", + "maps_worship_mormon_pin": "<:maps_worship_mormon_pin:1307493432834392087>", + "maps_worship_shinto_pin": "<:maps_worship_shinto_pin:1307493443693707356>", + "maps_worship_sikh_pin": "<:maps_worship_sikh_pin:1307493454053376020>", + "maps_worship_temple_pin": "<:maps_worship_temple_pin:1307493467793915924>", + "information": "<:ico_information:1263590946226835510>", "question": "<:ico_question:1263590898906697800>", "warning": "<:ico_warning:1263590857072967832>", diff --git a/labscore/utils/markdown.js b/labscore/utils/markdown.js index eed0d14..eb06dd0 100644 --- a/labscore/utils/markdown.js +++ b/labscore/utils/markdown.js @@ -5,6 +5,19 @@ module.exports.icon = function(icon){ return ICONS[icon].replace(/:[a-z1-9_]*:/, ':i:') } +module.exports.iconAsEmojiObject = function(icon){ + let i; + if(!ICONS[icon]) i = ICONS.question.replace(/:[a-z1-9_]*:/, ':i:'); + else i = ICONS[icon].replace(/:[a-z1-9_]*:/, ':i:'); + + return { + id: i.replace(/<:[a-z0-9_]*:([0-9]*)>/g,"$1"), + name: i, + animated: false, // TODO: fix this for animated emoji if we ever need them + } + +} + module.exports.weatherIcon = function(icon){ if(!ICONS["weather_" + icon]) return ICONS["calendar"].replace(/:[a-z1-9_]*:/, ':i:') return ICONS["weather_" + icon].replace(/:[a-z1-9_]*:/, ':i:') diff --git a/labscore/utils/statics.js b/labscore/utils/statics.js index c007107..84313fd 100644 --- a/labscore/utils/statics.js +++ b/labscore/utils/statics.js @@ -68,6 +68,10 @@ const Statics = Object.freeze({ file: "brands/googlefinance.png", revision: 0 }, + googlemaps: { + file: "brands/maps.png", + revision: 0 + }, googletranslate: { file: "brands/googletranslate.png", revision: 0 @@ -222,6 +226,7 @@ module.exports.STATICS = Object.freeze({ genius: staticAsset(Statics.brands.genius), google: staticAsset(Statics.brands.google), googlefinance: staticAsset(Statics.brands.googlefinance), + googlemaps: staticAsset(Statics.brands.googlemaps), googletranslate: staticAsset(Statics.brands.googletranslate), emojipedia: staticAsset(Statics.brands.emojipedia), inferkit: staticAsset(Statics.brands.inferkit),