adding freight web and holding
This commit is contained in:
@@ -16,7 +16,8 @@
|
||||
"@projectdysnomia/dysnomia": "github:projectdysnomia/dysnomia#dev",
|
||||
"@star-kitten/discord": "workspace:^0.0.0",
|
||||
"@star-kitten/eve": "workspace:^0.0.0",
|
||||
"@star-kitten/util": "workspace:^0.0.0"
|
||||
"@star-kitten/util": "workspace:^0.0.0",
|
||||
"mkdirp": "^3.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "bunx dotenvx run -f .env.development -- bun run --watch src/main.ts",
|
||||
|
||||
@@ -1,90 +1,90 @@
|
||||
import { Constants, type ChatInputApplicationCommandStructure } from '@projectdysnomia/dysnomia';
|
||||
import { appraiseItems, type Appraisal } from '@star-kitten/eve/third-party/janice.js';
|
||||
import { isModalSubmit } from '@star-kitten/discord/commands';
|
||||
import { componentHasIdPrefix, isModalLabel, isModalSelect, isModalTextInput } from '@star-kitten/discord/components';
|
||||
import type { CommandContext, ExecutableInteraction } from '@star-kitten/discord/commands';
|
||||
import { PageType, usePages } from '@star-kitten/discord/pages';
|
||||
import { renderAppraisal } from './renderAppraisal';
|
||||
import { renderAppraisalModal } from './renderAppraisalModal';
|
||||
|
||||
const definition: ChatInputApplicationCommandStructure = {
|
||||
type: Constants.ApplicationCommandTypes.CHAT_INPUT,
|
||||
name: 'appraise',
|
||||
nameLocalizations: {
|
||||
de: 'bewerten',
|
||||
'es-ES': 'tasar',
|
||||
fr: 'estimer',
|
||||
ja: '査定',
|
||||
ko: '감정',
|
||||
ru: 'оценить',
|
||||
'zh-CN': '评估',
|
||||
},
|
||||
description: 'Evaluate the worth of your space junk',
|
||||
descriptionLocalizations: {
|
||||
de: 'Bewerten Sie den Wert Ihres Weltraumschrotts',
|
||||
'es-ES': 'Evalúa el valor de tu chatarra espacial',
|
||||
fr: 'Évaluez la valeur de vos déchets spatiaux',
|
||||
ja: 'あなたの宇宙のガラクタの価値を評価します',
|
||||
ko: '우주 쓰레기의 가치를 평가하십시오',
|
||||
ru: 'Оцените стоимость вашего космического мусора',
|
||||
'zh-CN': '评估您宇宙垃圾的价值',
|
||||
},
|
||||
};
|
||||
|
||||
export interface AppraisalState {
|
||||
appraisal?: Appraisal;
|
||||
}
|
||||
|
||||
async function execute(interaction: ExecutableInteraction, ctx: CommandContext) {
|
||||
return await usePages<AppraisalState>(
|
||||
{
|
||||
pages: {
|
||||
appraiseModal: {
|
||||
key: 'appraiseModal',
|
||||
type: PageType.MODAL,
|
||||
render: async () => renderAppraisalModal(interaction),
|
||||
},
|
||||
appraisalResult: {
|
||||
key: 'appraisalResult',
|
||||
render: async (pageCtx) => {
|
||||
if (!isModalSubmit(interaction)) {
|
||||
throw new Error('Expected a modal submit interaction for appraisalResult page');
|
||||
}
|
||||
let marketId = 2; // Default to Jita
|
||||
let items = '';
|
||||
|
||||
interaction.data.components.forEach((comp) => {
|
||||
if (isModalLabel(comp)) {
|
||||
if (isModalSelect(comp.component) && componentHasIdPrefix(comp.component, `market`)) {
|
||||
marketId = Number.parseInt(comp.component.values[0]) || marketId;
|
||||
} else if (isModalTextInput(comp.component) && componentHasIdPrefix(comp.component, `input`)) {
|
||||
items = comp.component.value || items;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const appraisal = await appraiseItems(items, marketId);
|
||||
pageCtx.state.data.appraisal = appraisal;
|
||||
return renderAppraisal(appraisal, pageCtx, interaction);
|
||||
},
|
||||
},
|
||||
share: {
|
||||
key: 'share',
|
||||
type: PageType.FOLLOWUP,
|
||||
followUpFlags: Constants.MessageFlags.IS_COMPONENTS_V2,
|
||||
render: async (pageCtx) => renderAppraisal(pageCtx.state.data.appraisal!, pageCtx, interaction),
|
||||
},
|
||||
},
|
||||
initialPage: 'appraiseModal',
|
||||
timeout: 300, // 5 minutes
|
||||
ephemeral: true,
|
||||
},
|
||||
interaction,
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
|
||||
export default {
|
||||
definition,
|
||||
execute,
|
||||
};
|
||||
import { Constants, type ChatInputApplicationCommandStructure } from '@projectdysnomia/dysnomia';
|
||||
import { appraiseItems, type Appraisal } from '@star-kitten/eve/third-party/janice.js';
|
||||
import { isModalSubmit } from '@star-kitten/discord/commands';
|
||||
import { componentHasIdPrefix, isModalLabel, isModalSelect, isModalTextInput } from '@star-kitten/discord/components';
|
||||
import type { CommandContext, ExecutableInteraction } from '@star-kitten/discord/commands';
|
||||
import { PageType, usePages } from '@star-kitten/discord/pages';
|
||||
import { renderAppraisal } from './renderAppraisal';
|
||||
import { renderAppraisalModal } from './renderAppraisalModal';
|
||||
|
||||
const definition: ChatInputApplicationCommandStructure = {
|
||||
type: Constants.ApplicationCommandTypes.CHAT_INPUT,
|
||||
name: 'appraise',
|
||||
nameLocalizations: {
|
||||
de: 'bewerten',
|
||||
'es-ES': 'tasar',
|
||||
fr: 'estimer',
|
||||
ja: '査定',
|
||||
ko: '감정',
|
||||
ru: 'оценить',
|
||||
'zh-CN': '评估',
|
||||
},
|
||||
description: 'Evaluate the worth of your space junk',
|
||||
descriptionLocalizations: {
|
||||
de: 'Bewerten Sie den Wert Ihres Weltraumschrotts',
|
||||
'es-ES': 'Evalúa el valor de tu chatarra espacial',
|
||||
fr: 'Évaluez la valeur de vos déchets spatiaux',
|
||||
ja: 'あなたの宇宙のガラクタの価値を評価します',
|
||||
ko: '우주 쓰레기의 가치를 평가하십시오',
|
||||
ru: 'Оцените стоимость вашего космического мусора',
|
||||
'zh-CN': '评估您宇宙垃圾的价值',
|
||||
},
|
||||
};
|
||||
|
||||
export interface AppraisalState {
|
||||
appraisal?: Appraisal;
|
||||
}
|
||||
|
||||
async function execute(interaction: ExecutableInteraction, ctx: CommandContext) {
|
||||
return await usePages<AppraisalState>(
|
||||
{
|
||||
pages: {
|
||||
appraiseModal: {
|
||||
key: 'appraiseModal',
|
||||
type: PageType.MODAL,
|
||||
render: async () => renderAppraisalModal(interaction),
|
||||
},
|
||||
appraisalResult: {
|
||||
key: 'appraisalResult',
|
||||
render: async (pageCtx) => {
|
||||
if (!isModalSubmit(interaction)) {
|
||||
throw new Error('Expected a modal submit interaction for appraisalResult page');
|
||||
}
|
||||
let marketId = 2; // Default to Jita
|
||||
let items = '';
|
||||
|
||||
interaction.data.components.forEach((comp) => {
|
||||
if (isModalLabel(comp)) {
|
||||
if (isModalSelect(comp.component) && componentHasIdPrefix(comp.component, `market`)) {
|
||||
marketId = Number.parseInt(comp.component.values[0]) || marketId;
|
||||
} else if (isModalTextInput(comp.component) && componentHasIdPrefix(comp.component, `input`)) {
|
||||
items = comp.component.value || items;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const appraisal = await appraiseItems(items, marketId);
|
||||
pageCtx.state.data.appraisal = appraisal;
|
||||
return renderAppraisal(appraisal, pageCtx, interaction);
|
||||
},
|
||||
},
|
||||
share: {
|
||||
key: 'share',
|
||||
type: PageType.FOLLOWUP,
|
||||
followUpFlags: Constants.MessageFlags.IS_COMPONENTS_V2,
|
||||
render: async (pageCtx) => renderAppraisal(pageCtx.state.data.appraisal!, pageCtx, interaction),
|
||||
},
|
||||
},
|
||||
initialPage: 'appraiseModal',
|
||||
timeout: 300, // 5 minutes
|
||||
ephemeral: true,
|
||||
},
|
||||
interaction,
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
|
||||
export default {
|
||||
definition,
|
||||
execute,
|
||||
};
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import type { ExecutableInteraction } from '@star-kitten/discord';
|
||||
import { createActionRow, createButton, createContainer, createTextDisplay } from '@star-kitten/discord/components';
|
||||
import type { PageContext } from '@star-kitten/discord/pages';
|
||||
import { type Appraisal } from '@star-kitten/eve/third-party/janice.js';
|
||||
import { formatNumberToShortForm } from '@star-kitten/util/text.js';
|
||||
import type { AppraisalState } from './appraise.command';
|
||||
|
||||
export function renderAppraisal(
|
||||
appraisal: Appraisal,
|
||||
pageCtx: PageContext<AppraisalState>,
|
||||
interaction: ExecutableInteraction,
|
||||
) {
|
||||
const formatter = new Intl.NumberFormat(interaction.locale || 'en-US', {
|
||||
maximumFractionDigits: 2,
|
||||
minimumFractionDigits: 2,
|
||||
});
|
||||
|
||||
const container = createContainer(
|
||||
{
|
||||
accent_color: 0x1da57a,
|
||||
},
|
||||
createTextDisplay(`
|
||||
# [Appraisal ${appraisal.id} @ ${appraisal.market.name}](https://janice.e-351.com/a/${appraisal.id})
|
||||
### Buy: \`${formatter.format(appraisal.effectivePrices.totalBuyPrice)}\` ISK
|
||||
### Split: \`${formatter.format(appraisal.effectivePrices.totalSplitPrice)}\` ISK
|
||||
### Sell: \`${formatter.format(appraisal.effectivePrices.totalSellPrice)}\` ISK
|
||||
-# Volume: ${formatter.format(appraisal.totalPackagedVolume)} m³
|
||||
\`\`\`
|
||||
Buy: Sell: Qty: Item:
|
||||
${appraisal.items.map((i) => `${formatNumberToShortForm(i.effectivePrices.buyPrice).padEnd(10)}${formatNumberToShortForm(i.effectivePrices.sellPrice).padEnd(10)}${formatNumberToShortForm(i.amount).padEnd(10)}${i.itemType.name}`).join('\n')}
|
||||
\`\`\`
|
||||
-# https://janice.e-351.com/a/${appraisal.id}\n\n
|
||||
`),
|
||||
);
|
||||
|
||||
if (pageCtx.state.currentPage !== 'share') {
|
||||
container.components.push(
|
||||
createActionRow(
|
||||
createButton('Share in Channel', 'share', {
|
||||
disabled: !interaction.channel?.id,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 1,
|
||||
components: [container],
|
||||
};
|
||||
}
|
||||
79
packages/eve-bot/src/commands/appraise/renderAppraisal.tsx
Normal file
79
packages/eve-bot/src/commands/appraise/renderAppraisal.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import type { ExecutableInteraction } from '@star-kitten/discord';
|
||||
import * as StarKitten from '@star-kitten/discord';
|
||||
import { createActionRow, createButton, createContainer, createTextDisplay } from '@star-kitten/discord/components';
|
||||
import type { PageContext } from '@star-kitten/discord/pages';
|
||||
import { type Appraisal } from '@star-kitten/eve/third-party/janice.js';
|
||||
import { formatNumberToShortForm } from '@star-kitten/util/text.js';
|
||||
import type { AppraisalState } from './appraise.command';
|
||||
|
||||
export function renderAppraisal(
|
||||
appraisal: Appraisal,
|
||||
pageCtx: PageContext<AppraisalState>,
|
||||
interaction: ExecutableInteraction,
|
||||
): StarKitten.Component {
|
||||
const formatter = new Intl.NumberFormat(interaction.locale || 'en-US', {
|
||||
maximumFractionDigits: 2,
|
||||
minimumFractionDigits: 2,
|
||||
});
|
||||
|
||||
// const container = createContainer(
|
||||
// {
|
||||
// accent_color: 0x1da57a,
|
||||
// },
|
||||
// createTextDisplay(`
|
||||
// # [Appraisal ${appraisal.id} @ ${appraisal.market.name}](https://janice.e-351.com/a/${appraisal.id})
|
||||
// ### Buy: \`${formatter.format(appraisal.effectivePrices.totalBuyPrice)}\` ISK
|
||||
// ### Split: \`${formatter.format(appraisal.effectivePrices.totalSplitPrice)}\` ISK
|
||||
// ### Sell: \`${formatter.format(appraisal.effectivePrices.totalSellPrice)}\` ISK
|
||||
// -# Volume: ${formatter.format(appraisal.totalPackagedVolume)} m³
|
||||
// \`\`\`
|
||||
// Buy: Sell: Qty: Item:
|
||||
// ${appraisal.items.map((i) => `${formatNumberToShortForm(i.effectivePrices.buyPrice).padEnd(10)}${formatNumberToShortForm(i.effectivePrices.sellPrice).padEnd(10)}${formatNumberToShortForm(i.amount).padEnd(10)}${i.itemType.name}`).join('\n')}
|
||||
// \`\`\`
|
||||
// -# https://janice.e-351.com/a/${appraisal.id}\n\n
|
||||
// `),
|
||||
// );
|
||||
|
||||
// if (pageCtx.state.currentPage !== 'share') {
|
||||
// container.components.push(
|
||||
// createActionRow(
|
||||
// createButton('Share in Channel', 'share', {
|
||||
// disabled: !interaction.channel?.id,
|
||||
// }),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
// return {
|
||||
// type: 1,
|
||||
// components: [container],
|
||||
// };
|
||||
return (
|
||||
<container accent={0x1da57a}>
|
||||
<textDisplay
|
||||
content={`
|
||||
# [Appraisal ${appraisal.id} @ ${appraisal.market.name}](https://janice.e-351.com/a/${appraisal.id})
|
||||
### Buy: \`${formatter.format(appraisal.effectivePrices.totalBuyPrice)}\` ISK
|
||||
### Split: \`${formatter.format(appraisal.effectivePrices.totalSplitPrice)}\` ISK
|
||||
### Sell: \`${formatter.format(appraisal.effectivePrices.totalSellPrice)}\` ISK
|
||||
-# Volume: ${formatter.format(appraisal.totalPackagedVolume)} m³
|
||||
\`\`\`
|
||||
Buy: Sell: Qty: Item:
|
||||
${appraisal.items.map((i) => `${formatNumberToShortForm(i.effectivePrices.buyPrice).padEnd(10)}${formatNumberToShortForm(i.effectivePrices.sellPrice).padEnd(10)}${formatNumberToShortForm(i.amount).padEnd(10)}${i.itemType.name}`).join('\n')}
|
||||
\`\`\`
|
||||
-# https://janice.e-351.com/a/${appraisal.id}\n\n
|
||||
`}
|
||||
/>
|
||||
{pageCtx.state.currentPage !== 'share' ? (
|
||||
<actionRow>
|
||||
<button
|
||||
customId="share"
|
||||
label="Share in Channel"
|
||||
disabled={!interaction.channel?.id}
|
||||
style={StarKitten.ButtonStyle.PRIMARY}
|
||||
/>
|
||||
</actionRow>
|
||||
) : undefined}
|
||||
</container>
|
||||
);
|
||||
}
|
||||
@@ -1,37 +1,37 @@
|
||||
import type { Interaction } from '@projectdysnomia/dysnomia';
|
||||
import { createModalLabel, createStringSelect, createTextInput } from '@star-kitten/discord/components';
|
||||
import { markets } from '@star-kitten/eve/third-party/janice.js';
|
||||
|
||||
export function renderAppraisalModal(interaction: Interaction) {
|
||||
return {
|
||||
// next page to render will be appraisalResult
|
||||
custom_id: `appraisalResult`,
|
||||
title: 'Appraise Items',
|
||||
components: [
|
||||
createModalLabel(
|
||||
'Select your market (default: Jita)',
|
||||
createStringSelect(
|
||||
'market',
|
||||
{
|
||||
placeholder: 'Select a market',
|
||||
},
|
||||
...markets.map((m) => ({
|
||||
label: m.name,
|
||||
value: m.id.toString(),
|
||||
default: m.id === 2, // Jita
|
||||
})),
|
||||
),
|
||||
),
|
||||
createModalLabel(
|
||||
'Enter items to appraise',
|
||||
createTextInput('input', {
|
||||
isParagraph: true,
|
||||
placeholder: `Enter list of items to be appraised.
|
||||
Tritanium 22222
|
||||
Pyerite 8000
|
||||
Mexallon 2444`,
|
||||
}),
|
||||
),
|
||||
],
|
||||
};
|
||||
}
|
||||
import type { Interaction } from '@projectdysnomia/dysnomia';
|
||||
import { createModalLabel, createStringSelect, createTextInput } from '@star-kitten/discord/components';
|
||||
import { markets } from '@star-kitten/eve/third-party/janice.js';
|
||||
|
||||
export function renderAppraisalModal(interaction: Interaction) {
|
||||
return {
|
||||
// next page to render will be appraisalResult
|
||||
custom_id: `appraisalResult`,
|
||||
title: 'Appraise Items',
|
||||
components: [
|
||||
createModalLabel(
|
||||
'Select your market (default: Jita)',
|
||||
createStringSelect(
|
||||
'market',
|
||||
{
|
||||
placeholder: 'Select a market',
|
||||
},
|
||||
...markets.map((m) => ({
|
||||
label: m.name,
|
||||
value: m.id.toString(),
|
||||
default: m.id === 2, // Jita
|
||||
})),
|
||||
),
|
||||
),
|
||||
createModalLabel(
|
||||
'Enter items to appraise',
|
||||
createTextInput('input', {
|
||||
isParagraph: true,
|
||||
placeholder: `Enter list of items to be appraised.
|
||||
Tritanium 22222
|
||||
Pyerite 8000
|
||||
Mexallon 2444`,
|
||||
}),
|
||||
),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,140 +1,140 @@
|
||||
import { renderSubroutes, type Page } from '@star-kitten/discord/pages';
|
||||
import type { SearchState } from '../search.command';
|
||||
import {
|
||||
ButtonStyle,
|
||||
createContainer,
|
||||
createSection,
|
||||
createSeparator,
|
||||
createTextDisplay,
|
||||
createThumbnail,
|
||||
Padding,
|
||||
} from '@star-kitten/discord/components';
|
||||
import {
|
||||
getGroup,
|
||||
getType,
|
||||
getUnit,
|
||||
renderUnit,
|
||||
typeGetAttribute,
|
||||
typeHasAnyAttribute,
|
||||
type Type,
|
||||
} from '@star-kitten/eve/models';
|
||||
import { attributeOrdering } from '@star-kitten/eve';
|
||||
import { searchActionRow } from './helpers';
|
||||
import { toTitleCase } from '@star-kitten/util/text.js';
|
||||
|
||||
enum Images {
|
||||
ATTRIBUTES = 'https://iili.io/KTbaMR2.md.webp',
|
||||
DEFENSES = 'https://iili.io/KTbSVoX.md.webp',
|
||||
FITTING = 'https://iili.io/KufiFYG.md.webp',
|
||||
FACILITIES = 'https://iili.io/KufikGt.md.webp',
|
||||
}
|
||||
|
||||
const attributeCategoryMap = {
|
||||
structure: 'UI/Fitting/Structure',
|
||||
armor: 'UI/Common/Armor',
|
||||
shield: 'UI/Common/Shield',
|
||||
ewar: 'UI/Common/EWarResistances',
|
||||
capacitor: 'UI/Fitting/FittingWindow/Capacitor',
|
||||
targeting: 'UI/Fitting/FittingWindow/Targeting',
|
||||
facilities: 'UI/InfoWindow/SharedFacilities',
|
||||
fighters: 'UI/InfoWindow/FighterFacilities',
|
||||
on_death: 'UI/InfoWindow/OnDeath',
|
||||
jump_drive: 'UI/InfoWindow/JumpDriveSystems',
|
||||
propulsion: 'UI/Compare/Propulsion',
|
||||
};
|
||||
|
||||
const groupedCategories = [
|
||||
// defenses
|
||||
['shield', 'armor', 'structure', 'ewar'],
|
||||
// fittings
|
||||
['capacitor', 'targeting', 'propulsion'],
|
||||
// facilities
|
||||
['facilities', 'fighters', 'on_death', 'jump_drive'],
|
||||
];
|
||||
|
||||
function getAttributeOrdering(type: Type) {
|
||||
const group = getGroup(type.group_id);
|
||||
switch (group.category_id) {
|
||||
case 11:
|
||||
return attributeOrdering['11'];
|
||||
case 87:
|
||||
return attributeOrdering['87'];
|
||||
default:
|
||||
return attributeOrdering.default;
|
||||
}
|
||||
}
|
||||
|
||||
const bannerMap = {
|
||||
shield: Images.DEFENSES,
|
||||
armor: Images.DEFENSES,
|
||||
structure: Images.DEFENSES,
|
||||
ewar: Images.DEFENSES,
|
||||
|
||||
capacitor: Images.FITTING,
|
||||
targeting: Images.FITTING,
|
||||
propulsion: Images.FITTING,
|
||||
|
||||
facilities: Images.FACILITIES,
|
||||
fighters: Images.FACILITIES,
|
||||
on_death: Images.FACILITIES,
|
||||
jump_drive: Images.FACILITIES,
|
||||
};
|
||||
|
||||
const page: Page<SearchState> = {
|
||||
key: 'attributes',
|
||||
render: (context) => {
|
||||
const type = getType(context.state.data.type_id);
|
||||
const ordering = getAttributeOrdering(type);
|
||||
|
||||
return {
|
||||
components: [
|
||||
createContainer(
|
||||
{},
|
||||
createSection(
|
||||
createThumbnail(`https://images.evetech.net/types/${type.type_id}/icon`),
|
||||
createTextDisplay(`# [${type.name.en}](https://everef.net/types/${type.type_id})\n## Attributes`),
|
||||
),
|
||||
...renderSubroutes(
|
||||
context,
|
||||
'attributes',
|
||||
groupedCategories.map((group) =>
|
||||
group.map((cat) => {
|
||||
const attrCat = ordering[attributeCategoryMap[cat]];
|
||||
const attrs = attrCat.groupedCategories
|
||||
? attrCat.groupedCategories.map(([name, id]) => id).concat(attrCat.normalAttributes) || []
|
||||
: attrCat.normalAttributes;
|
||||
if (!typeHasAnyAttribute(type, attrs)) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
label: toTitleCase(cat.replace('_', ' ')),
|
||||
value: cat,
|
||||
banner: bannerMap[cat],
|
||||
};
|
||||
}),
|
||||
),
|
||||
(currentRoute) => {
|
||||
const lines: string[] = [];
|
||||
const attrCat = ordering[attributeCategoryMap[currentRoute]];
|
||||
const attrs = attrCat.groupedCategories
|
||||
? attrCat.groupedCategories.map(([name, id]) => id).concat(attrCat.normalAttributes) || []
|
||||
: attrCat.normalAttributes;
|
||||
attrs.map((attrId) => {
|
||||
const attr = typeGetAttribute(type, attrId);
|
||||
if (!attr) return;
|
||||
const unit = attr.attribute.unit_id ? renderUnit(getUnit(attr.attribute.unit_id), attr.value) : '';
|
||||
lines.push(`${attr.attribute.display_name.en.padEnd(24)} ${unit}`);
|
||||
});
|
||||
return createTextDisplay('```\n' + lines.join('\n') + '\n```');
|
||||
},
|
||||
{ style: ButtonStyle.SECONDARY },
|
||||
),
|
||||
createSeparator(Padding.LARGE),
|
||||
searchActionRow('attributes'),
|
||||
),
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default page;
|
||||
import { renderSubroutes, type Page } from '@star-kitten/discord/pages';
|
||||
import type { SearchState } from '../search.command';
|
||||
import {
|
||||
ButtonStyle,
|
||||
createContainer,
|
||||
createSection,
|
||||
createSeparator,
|
||||
createTextDisplay,
|
||||
createThumbnail,
|
||||
Padding,
|
||||
} from '@star-kitten/discord/components';
|
||||
import {
|
||||
getGroup,
|
||||
getType,
|
||||
getUnit,
|
||||
renderUnit,
|
||||
typeGetAttribute,
|
||||
typeHasAnyAttribute,
|
||||
type Type,
|
||||
} from '@star-kitten/eve/models';
|
||||
import { attributeOrdering } from '@star-kitten/eve';
|
||||
import { searchActionRow } from './helpers';
|
||||
import { toTitleCase } from '@star-kitten/util/text.js';
|
||||
|
||||
enum Images {
|
||||
ATTRIBUTES = 'https://iili.io/KTbaMR2.md.webp',
|
||||
DEFENSES = 'https://iili.io/KTbSVoX.md.webp',
|
||||
FITTING = 'https://iili.io/KufiFYG.md.webp',
|
||||
FACILITIES = 'https://iili.io/KufikGt.md.webp',
|
||||
}
|
||||
|
||||
const attributeCategoryMap = {
|
||||
structure: 'UI/Fitting/Structure',
|
||||
armor: 'UI/Common/Armor',
|
||||
shield: 'UI/Common/Shield',
|
||||
ewar: 'UI/Common/EWarResistances',
|
||||
capacitor: 'UI/Fitting/FittingWindow/Capacitor',
|
||||
targeting: 'UI/Fitting/FittingWindow/Targeting',
|
||||
facilities: 'UI/InfoWindow/SharedFacilities',
|
||||
fighters: 'UI/InfoWindow/FighterFacilities',
|
||||
on_death: 'UI/InfoWindow/OnDeath',
|
||||
jump_drive: 'UI/InfoWindow/JumpDriveSystems',
|
||||
propulsion: 'UI/Compare/Propulsion',
|
||||
};
|
||||
|
||||
const groupedCategories = [
|
||||
// defenses
|
||||
['shield', 'armor', 'structure', 'ewar'],
|
||||
// fittings
|
||||
['capacitor', 'targeting', 'propulsion'],
|
||||
// facilities
|
||||
['facilities', 'fighters', 'on_death', 'jump_drive'],
|
||||
];
|
||||
|
||||
function getAttributeOrdering(type: Type) {
|
||||
const group = getGroup(type.group_id);
|
||||
switch (group.category_id) {
|
||||
case 11:
|
||||
return attributeOrdering['11'];
|
||||
case 87:
|
||||
return attributeOrdering['87'];
|
||||
default:
|
||||
return attributeOrdering.default;
|
||||
}
|
||||
}
|
||||
|
||||
const bannerMap = {
|
||||
shield: Images.DEFENSES,
|
||||
armor: Images.DEFENSES,
|
||||
structure: Images.DEFENSES,
|
||||
ewar: Images.DEFENSES,
|
||||
|
||||
capacitor: Images.FITTING,
|
||||
targeting: Images.FITTING,
|
||||
propulsion: Images.FITTING,
|
||||
|
||||
facilities: Images.FACILITIES,
|
||||
fighters: Images.FACILITIES,
|
||||
on_death: Images.FACILITIES,
|
||||
jump_drive: Images.FACILITIES,
|
||||
};
|
||||
|
||||
const page: Page<SearchState> = {
|
||||
key: 'attributes',
|
||||
render: (context) => {
|
||||
const type = getType(context.state.data.type_id);
|
||||
const ordering = getAttributeOrdering(type);
|
||||
|
||||
return {
|
||||
components: [
|
||||
createContainer(
|
||||
{},
|
||||
createSection(
|
||||
createThumbnail(`https://images.evetech.net/types/${type.type_id}/icon`),
|
||||
createTextDisplay(`# [${type.name.en}](https://everef.net/types/${type.type_id})\n## Attributes`),
|
||||
),
|
||||
...renderSubroutes(
|
||||
context,
|
||||
'attributes',
|
||||
groupedCategories.map((group) =>
|
||||
group.map((cat) => {
|
||||
const attrCat = ordering[attributeCategoryMap[cat]];
|
||||
const attrs = attrCat.groupedCategories
|
||||
? attrCat.groupedCategories.map(([name, id]) => id).concat(attrCat.normalAttributes) || []
|
||||
: attrCat.normalAttributes;
|
||||
if (!typeHasAnyAttribute(type, attrs)) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
label: toTitleCase(cat.replace('_', ' ')),
|
||||
value: cat,
|
||||
banner: bannerMap[cat],
|
||||
};
|
||||
}),
|
||||
),
|
||||
(currentRoute) => {
|
||||
const lines: string[] = [];
|
||||
const attrCat = ordering[attributeCategoryMap[currentRoute]];
|
||||
const attrs = attrCat.groupedCategories
|
||||
? attrCat.groupedCategories.map(([name, id]) => id).concat(attrCat.normalAttributes) || []
|
||||
: attrCat.normalAttributes;
|
||||
attrs.map((attrId) => {
|
||||
const attr = typeGetAttribute(type, attrId);
|
||||
if (!attr) return;
|
||||
const unit = attr.attribute.unit_id ? renderUnit(getUnit(attr.attribute.unit_id), attr.value) : '';
|
||||
lines.push(`${attr.attribute.display_name.en.padEnd(24)} ${unit}`);
|
||||
});
|
||||
return createTextDisplay('```\n' + lines.join('\n') + '\n```');
|
||||
},
|
||||
{ style: ButtonStyle.SECONDARY },
|
||||
),
|
||||
createSeparator(Padding.LARGE),
|
||||
searchActionRow('attributes'),
|
||||
),
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default page;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { createActionRow, createButton } from '@star-kitten/discord/components';
|
||||
|
||||
export function searchActionRow(pageKey: string) {
|
||||
return createActionRow(
|
||||
createButton('Main', 'main', { disabled: pageKey === 'main' }),
|
||||
createButton('Attributes', 'attributes', { disabled: pageKey === 'attributes' }),
|
||||
createButton('Fittings', 'fittings', { disabled: pageKey === 'fittings' }),
|
||||
createButton('Skills', 'skills', { disabled: pageKey === 'skills' }),
|
||||
createButton('Industry', 'industry', { disabled: pageKey === 'industry' }),
|
||||
);
|
||||
}
|
||||
import { createActionRow, createButton } from '@star-kitten/discord/components';
|
||||
|
||||
export function searchActionRow(pageKey: string) {
|
||||
return createActionRow(
|
||||
createButton('Main', 'main', { disabled: pageKey === 'main' }),
|
||||
createButton('Attributes', 'attributes', { disabled: pageKey === 'attributes' }),
|
||||
createButton('Fittings', 'fittings', { disabled: pageKey === 'fittings' }),
|
||||
createButton('Skills', 'skills', { disabled: pageKey === 'skills' }),
|
||||
createButton('Industry', 'industry', { disabled: pageKey === 'industry' }),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,89 +1,89 @@
|
||||
import type { Page } from '@star-kitten/discord/pages';
|
||||
import type { SearchState } from '../search.command';
|
||||
import {
|
||||
createContainer,
|
||||
createMediaGallery,
|
||||
createSection,
|
||||
createTextDisplay,
|
||||
createThumbnail,
|
||||
createURLButton,
|
||||
} from '@star-kitten/discord/components';
|
||||
import { getRoleBonuses, getSkillBonuses, getType } from '@star-kitten/eve/models/type.js';
|
||||
import { cleanText } from '@star-kitten/eve/utils/markdown.js';
|
||||
import { typeSearch } from '@star-kitten/eve/utils/typeSearch.js';
|
||||
import { isApplicationCommand } from '@star-kitten/discord';
|
||||
import { fetchPrice } from '@star-kitten/eve/third-party/evetycoon.js';
|
||||
import { formatNumberToShortForm } from '@star-kitten/util/text.js';
|
||||
import { searchActionRow } from './helpers';
|
||||
|
||||
const page: Page<SearchState> = {
|
||||
key: 'main',
|
||||
render: async (context) => {
|
||||
if (!context.state.data.type_id && isApplicationCommand(context.interaction)) {
|
||||
const typeName = context.interaction.data.options?.find((opt) => opt.name === 'name')?.value;
|
||||
const found = await typeSearch(typeName as string);
|
||||
|
||||
if (!found) {
|
||||
return {
|
||||
components: [createTextDisplay(`No item found for: ${typeName}`)],
|
||||
};
|
||||
}
|
||||
|
||||
context.state.data.type_id = found.type_id;
|
||||
}
|
||||
|
||||
const type = getType(context.state.data.type_id);
|
||||
|
||||
const skillBonuses = getSkillBonuses(type);
|
||||
const roleBonuses = getRoleBonuses(type);
|
||||
const price = await fetchPrice(type.type_id);
|
||||
|
||||
return {
|
||||
components: [
|
||||
createContainer(
|
||||
{},
|
||||
createSection(
|
||||
createThumbnail(`https://images.evetech.net/types/${type.type_id}/icon`),
|
||||
createTextDisplay(`
|
||||
# [${type.name.en}](https://everef.net/types/${type.type_id})
|
||||
|
||||
${skillBonuses
|
||||
.map((bonus) => {
|
||||
return `## Bonus per level of ${bonus.skill.name.en}
|
||||
${bonus.bonuses
|
||||
.sort((a, b) => a.importance - b.importance)
|
||||
.map((b) => `${b.bonus}${b.unit?.display_name ?? '-'} ${cleanText(b.bonus_text.en)}`)
|
||||
.join('\n')}`;
|
||||
})
|
||||
.join('\n')}
|
||||
${
|
||||
roleBonuses.length > 0
|
||||
? `\n## Role Bonuses
|
||||
${roleBonuses
|
||||
.sort((a, b) => a.importance - b.importance)
|
||||
.map((b) => `${b.bonus ?? ''}${b.unit?.display_name ?? '-'} ${cleanText(b.bonus_text.en)}`)
|
||||
.join('\n')}`
|
||||
: ''
|
||||
}
|
||||
`),
|
||||
),
|
||||
createMediaGallery({
|
||||
url: 'https://iili.io/KTPCFRt.md.webp',
|
||||
}),
|
||||
// createSeparator(Padding.LARGE),
|
||||
createSection(
|
||||
createURLButton('View on EVE Tycoon', `https://evetycoon.com/market/${type.type_id}`),
|
||||
createTextDisplay(
|
||||
`## Buy: ${price ? formatNumberToShortForm(price.buyAvgFivePercent) : '--'} ISK
|
||||
## Sell: ${price ? formatNumberToShortForm(price.sellAvgFivePercent) : '--'} ISK`,
|
||||
),
|
||||
),
|
||||
createTextDisplay(`-# Type Id: ${type.type_id}`),
|
||||
searchActionRow('main'),
|
||||
),
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default page;
|
||||
import type { Page } from '@star-kitten/discord/pages';
|
||||
import type { SearchState } from '../search.command';
|
||||
import {
|
||||
createContainer,
|
||||
createMediaGallery,
|
||||
createSection,
|
||||
createTextDisplay,
|
||||
createThumbnail,
|
||||
createURLButton,
|
||||
} from '@star-kitten/discord/components';
|
||||
import { getRoleBonuses, getSkillBonuses, getType } from '@star-kitten/eve/models/type.js';
|
||||
import { cleanText } from '@star-kitten/eve/utils/markdown.js';
|
||||
import { typeSearch } from '@star-kitten/eve/utils/typeSearch.js';
|
||||
import { isApplicationCommand } from '@star-kitten/discord';
|
||||
import { fetchPrice } from '@star-kitten/eve/third-party/evetycoon.js';
|
||||
import { formatNumberToShortForm } from '@star-kitten/util/text.js';
|
||||
import { searchActionRow } from './helpers';
|
||||
|
||||
const page: Page<SearchState> = {
|
||||
key: 'main',
|
||||
render: async (context) => {
|
||||
if (!context.state.data.type_id && isApplicationCommand(context.interaction)) {
|
||||
const typeName = context.interaction.data.options?.find((opt) => opt.name === 'name')?.value;
|
||||
const found = await typeSearch(typeName as string);
|
||||
|
||||
if (!found) {
|
||||
return {
|
||||
components: [createTextDisplay(`No item found for: ${typeName}`)],
|
||||
};
|
||||
}
|
||||
|
||||
context.state.data.type_id = found.type_id;
|
||||
}
|
||||
|
||||
const type = getType(context.state.data.type_id);
|
||||
|
||||
const skillBonuses = getSkillBonuses(type);
|
||||
const roleBonuses = getRoleBonuses(type);
|
||||
const price = await fetchPrice(type.type_id);
|
||||
|
||||
return {
|
||||
components: [
|
||||
createContainer(
|
||||
{},
|
||||
createSection(
|
||||
createThumbnail(`https://images.evetech.net/types/${type.type_id}/icon`),
|
||||
createTextDisplay(`
|
||||
# [${type.name.en}](https://everef.net/types/${type.type_id})
|
||||
|
||||
${skillBonuses
|
||||
.map((bonus) => {
|
||||
return `## Bonus per level of ${bonus.skill.name.en}
|
||||
${bonus.bonuses
|
||||
.sort((a, b) => a.importance - b.importance)
|
||||
.map((b) => `${b.bonus}${b.unit?.display_name ?? '-'} ${cleanText(b.bonus_text.en)}`)
|
||||
.join('\n')}`;
|
||||
})
|
||||
.join('\n')}
|
||||
${
|
||||
roleBonuses.length > 0
|
||||
? `\n## Role Bonuses
|
||||
${roleBonuses
|
||||
.sort((a, b) => a.importance - b.importance)
|
||||
.map((b) => `${b.bonus ?? ''}${b.unit?.display_name ?? '-'} ${cleanText(b.bonus_text.en)}`)
|
||||
.join('\n')}`
|
||||
: ''
|
||||
}
|
||||
`),
|
||||
),
|
||||
createMediaGallery({
|
||||
url: 'https://iili.io/KTPCFRt.md.webp',
|
||||
}),
|
||||
// createSeparator(Padding.LARGE),
|
||||
createSection(
|
||||
createURLButton('View on EVE Tycoon', `https://evetycoon.com/market/${type.type_id}`),
|
||||
createTextDisplay(
|
||||
`## Buy: ${price ? formatNumberToShortForm(price.buyAvgFivePercent) : '--'} ISK
|
||||
## Sell: ${price ? formatNumberToShortForm(price.sellAvgFivePercent) : '--'} ISK`,
|
||||
),
|
||||
),
|
||||
createTextDisplay(`-# Type Id: ${type.type_id}`),
|
||||
searchActionRow('main'),
|
||||
),
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default page;
|
||||
|
||||
@@ -1,69 +1,69 @@
|
||||
import {
|
||||
createChatCommand,
|
||||
isAutocomplete,
|
||||
stringOption,
|
||||
type CommandContext,
|
||||
type ExecutableInteraction,
|
||||
} from '@star-kitten/discord';
|
||||
import { usePages } from '@star-kitten/discord/pages';
|
||||
import { initializeTypeSearch, typeSearchAutoComplete } from '@star-kitten/eve/utils/typeSearch.js';
|
||||
|
||||
import main from './pages/main';
|
||||
import attributes from './pages/attributes';
|
||||
|
||||
let now = Date.now();
|
||||
console.debug('Initializing type search...');
|
||||
await initializeTypeSearch().catch((e) => {
|
||||
console.error('Failed to initialize type search', e);
|
||||
process.exit(1);
|
||||
});
|
||||
console.debug(`Type search initialized. Took ${Date.now() - now}ms`);
|
||||
|
||||
export interface SearchState {
|
||||
type_id: number;
|
||||
}
|
||||
|
||||
export default createChatCommand(
|
||||
{
|
||||
name: 'search',
|
||||
description: 'Search for a type',
|
||||
options: [
|
||||
stringOption({
|
||||
name: 'name',
|
||||
description: 'The type name to search for',
|
||||
autocomplete: true,
|
||||
required: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
execute,
|
||||
);
|
||||
|
||||
async function execute(interaction: ExecutableInteraction, ctx: CommandContext) {
|
||||
if (isAutocomplete(interaction)) {
|
||||
const focusedOption = interaction.data.options?.find((opt) => opt.focused);
|
||||
if (focusedOption?.name === 'name') {
|
||||
const value = focusedOption.value as string;
|
||||
const results = await typeSearchAutoComplete(value);
|
||||
if (results) {
|
||||
await interaction.result(results);
|
||||
} else {
|
||||
await interaction.result([]);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
usePages<SearchState>(
|
||||
{
|
||||
pages: {
|
||||
main,
|
||||
attributes,
|
||||
},
|
||||
initialPage: 'main',
|
||||
ephemeral: false,
|
||||
},
|
||||
interaction,
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
import {
|
||||
createChatCommand,
|
||||
isAutocomplete,
|
||||
stringOption,
|
||||
type CommandContext,
|
||||
type ExecutableInteraction,
|
||||
} from '@star-kitten/discord';
|
||||
import { usePages } from '@star-kitten/discord/pages';
|
||||
import { initializeTypeSearch, typeSearchAutoComplete } from '@star-kitten/eve/utils/typeSearch.js';
|
||||
|
||||
import main from './pages/main';
|
||||
import attributes from './pages/attributes';
|
||||
|
||||
let now = Date.now();
|
||||
console.debug('Initializing type search...');
|
||||
await initializeTypeSearch().catch((e) => {
|
||||
console.error('Failed to initialize type search', e);
|
||||
process.exit(1);
|
||||
});
|
||||
console.debug(`Type search initialized. Took ${Date.now() - now}ms`);
|
||||
|
||||
export interface SearchState {
|
||||
type_id: number;
|
||||
}
|
||||
|
||||
export default createChatCommand(
|
||||
{
|
||||
name: 'search',
|
||||
description: 'Search for a type',
|
||||
options: [
|
||||
stringOption({
|
||||
name: 'name',
|
||||
description: 'The type name to search for',
|
||||
autocomplete: true,
|
||||
required: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
execute,
|
||||
);
|
||||
|
||||
async function execute(interaction: ExecutableInteraction, ctx: CommandContext) {
|
||||
if (isAutocomplete(interaction)) {
|
||||
const focusedOption = interaction.data.options?.find((opt) => opt.focused);
|
||||
if (focusedOption?.name === 'name') {
|
||||
const value = focusedOption.value as string;
|
||||
const results = await typeSearchAutoComplete(value);
|
||||
if (results) {
|
||||
await interaction.result(results);
|
||||
} else {
|
||||
await interaction.result([]);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
usePages<SearchState>(
|
||||
{
|
||||
pages: {
|
||||
main,
|
||||
attributes,
|
||||
},
|
||||
initialPage: 'main',
|
||||
ephemeral: false,
|
||||
},
|
||||
interaction,
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { startDiscordBot } from '@star-kitten/discord';
|
||||
|
||||
startDiscordBot();
|
||||
import { startDiscordBot } from '@star-kitten/discord';
|
||||
|
||||
startDiscordBot();
|
||||
|
||||
26
packages/eve-bot/src/test.tsx
Normal file
26
packages/eve-bot/src/test.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
export function renderAppraisal() {
|
||||
const formatter = new Intl.NumberFormat('en-US', {
|
||||
maximumFractionDigits: 2,
|
||||
minimumFractionDigits: 2,
|
||||
});
|
||||
const world = 'world';
|
||||
const rand = Math.random() * 1000;
|
||||
const pageCtx = { state: { currentPage: 'home' } };
|
||||
|
||||
let jsx = (
|
||||
<actionRow>
|
||||
<container accent={0x1da57a}>
|
||||
<textDisplay content={`Hello ${world}`} />
|
||||
{pageCtx.state.currentPage !== 'share' ? (
|
||||
<actionRow>
|
||||
<button customId="share" label="Share in Channel" disabled={rand < 500} />
|
||||
</actionRow>
|
||||
) : undefined}
|
||||
</container>
|
||||
</actionRow>
|
||||
);
|
||||
|
||||
console.log(jsx);
|
||||
}
|
||||
|
||||
renderAppraisal();
|
||||
@@ -1,38 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": ["ESNext"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"composite": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "@star-kitten/discord",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": false,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
|
||||
// Paths
|
||||
"paths": {
|
||||
"@*": ["./src/*"]
|
||||
},
|
||||
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
|
||||
"typeRoots": ["src/types", "./node_modules/@types"]
|
||||
},
|
||||
"include": ["src", "types"],
|
||||
|
||||
Reference in New Issue
Block a user