This commit is contained in:
bignutty 2024-11-17 01:52:18 +01:00
parent 79b096efb4
commit fcb25fc639
7 changed files with 563 additions and 6 deletions

View file

@ -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.`))
}
},
};

View file

@ -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 <query>',
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.`))
}
},
};

View file

@ -34,6 +34,8 @@ const Api = Object.freeze({
SEARCH_GOOGLE: '/search/google', SEARCH_GOOGLE: '/search/google',
SEARCH_GOOGLE_IMAGES: '/search/google-images', SEARCH_GOOGLE_IMAGES: '/search/google-images',
SEARCH_LYRICS: '/search/lyrics', SEARCH_LYRICS: '/search/lyrics',
SEARCH_MAPS: '/search/maps',
SEARCH_MAPS_SUPPLEMENTAL: '/search/maps-supplemental',
SEARCH_QUORA: '/search/quora', SEARCH_QUORA: '/search/quora',
SEARCH_QUORA_RESULT: '/search/quora-result', SEARCH_QUORA_RESULT: '/search/quora-result',
SEARCH_REDDIT: '/search/reddit', SEARCH_REDDIT: '/search/reddit',

View file

@ -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){ module.exports.google = async function(context, query, nsfw){
return await request(Api.SEARCH_GOOGLE, "GET", {}, { return await request(Api.SEARCH_GOOGLE, "GET", {}, {
q: query, 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){ module.exports.quora = async function(context, query){
return await request(Api.SEARCH_QUORA, "GET", {}, { return await request(Api.SEARCH_QUORA, "GET", {}, {
q: query q: query

View file

@ -54,6 +54,7 @@ module.exports.BADGE_ICONS = Object.freeze({
"staff": "<:b:1263605715688231035>" "staff": "<:b:1263605715688231035>"
}) })
// TODO: Load icon configuration from server
module.exports.ICONS = Object.freeze({ module.exports.ICONS = Object.freeze({
"brand": "<:ico_brand_2024:1263593574478643221>", "brand": "<:ico_brand_2024:1263593574478643221>",
"flask_incognito": "<:ico_flask_incognito:1263608937459224688>", "flask_incognito": "<:ico_flask_incognito:1263608937459224688>",
@ -218,6 +219,102 @@ module.exports.ICONS = Object.freeze({
"weather_warning_frost": "<:ico_weather_frost:1269680515145797652>", "weather_warning_frost": "<:ico_weather_frost:1269680515145797652>",
"weather_warning_flood": "<:ico_weather_flood:1269680505519865982>", "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>", "information": "<:ico_information:1263590946226835510>",
"question": "<:ico_question:1263590898906697800>", "question": "<:ico_question:1263590898906697800>",
"warning": "<:ico_warning:1263590857072967832>", "warning": "<:ico_warning:1263590857072967832>",

View file

@ -5,6 +5,19 @@ module.exports.icon = function(icon){
return ICONS[icon].replace(/:[a-z1-9_]*:/, ':i:') 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){ module.exports.weatherIcon = function(icon){
if(!ICONS["weather_" + icon]) return ICONS["calendar"].replace(/:[a-z1-9_]*:/, ':i:') if(!ICONS["weather_" + icon]) return ICONS["calendar"].replace(/:[a-z1-9_]*:/, ':i:')
return ICONS["weather_" + icon].replace(/:[a-z1-9_]*:/, ':i:') return ICONS["weather_" + icon].replace(/:[a-z1-9_]*:/, ':i:')

View file

@ -68,6 +68,10 @@ const Statics = Object.freeze({
file: "brands/googlefinance.png", file: "brands/googlefinance.png",
revision: 0 revision: 0
}, },
googlemaps: {
file: "brands/maps.png",
revision: 0
},
googletranslate: { googletranslate: {
file: "brands/googletranslate.png", file: "brands/googletranslate.png",
revision: 0 revision: 0
@ -222,6 +226,7 @@ module.exports.STATICS = Object.freeze({
genius: staticAsset(Statics.brands.genius), genius: staticAsset(Statics.brands.genius),
google: staticAsset(Statics.brands.google), google: staticAsset(Statics.brands.google),
googlefinance: staticAsset(Statics.brands.googlefinance), googlefinance: staticAsset(Statics.brands.googlefinance),
googlemaps: staticAsset(Statics.brands.googlemaps),
googletranslate: staticAsset(Statics.brands.googletranslate), googletranslate: staticAsset(Statics.brands.googletranslate),
emojipedia: staticAsset(Statics.brands.emojipedia), emojipedia: staticAsset(Statics.brands.emojipedia),
inferkit: staticAsset(Statics.brands.inferkit), inferkit: staticAsset(Statics.brands.inferkit),