noot
This commit is contained in:
parent
f6cade5065
commit
3f353f1630
@ -4,4 +4,5 @@
|
||||
|
||||
export * from "./database";
|
||||
export * from "./guild";
|
||||
export * from "./leaderboards";
|
||||
export * from "./players";
|
||||
|
34
ts/src/activities/leaderboards.ts
Normal file
34
ts/src/activities/leaderboards.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { c } from "#/di";
|
||||
import { WApi } from "#/lib/wynn/wapi";
|
||||
import { PG } from "#/services/pg";
|
||||
import { type } from "arktype";
|
||||
|
||||
export async function update_guild_levels() {
|
||||
const api = await c.getAsync(WApi)
|
||||
const ans = await api.get('/v3/leaderboards/guildLevel', {resultLimit: 1000})
|
||||
if(ans.status !== 200){
|
||||
throw new Error('Failed to get guild list from wapi')
|
||||
}
|
||||
const parsed = type({
|
||||
"[string]": {
|
||||
uuid: "string",
|
||||
name: "string",
|
||||
prefix: "string",
|
||||
xp: "number",
|
||||
level: "number",
|
||||
}
|
||||
}).assert(ans.data)
|
||||
const { db } = await c.getAsync(PG)
|
||||
await db.begin(async (sql) => {
|
||||
for(const [_, guild] of Object.entries(parsed)){
|
||||
await sql`insert into wynn_guild_info
|
||||
(uid, name, prefix, xp, level)
|
||||
values
|
||||
(${guild.uuid}, ${guild.name}, ${guild.prefix}, ${guild.xp}, ${guild.level})
|
||||
on conflict (uid) do update set
|
||||
xp = EXCLUDED.xp,
|
||||
level = EXCLUDED.level
|
||||
`
|
||||
}
|
||||
})
|
||||
}
|
@ -95,7 +95,6 @@ export const scrape_online_players = async()=>{
|
||||
name = EXCLUDED.name,
|
||||
server = EXCLUDED.server
|
||||
`
|
||||
log.info(`inserted ${playerName} with uuid ${uuid} on ${server}`)
|
||||
}catch(e) {
|
||||
log.warn(`failed to get uuid for ${playerName}`, {
|
||||
"err": e,
|
||||
|
@ -1,12 +1,10 @@
|
||||
import {bot} from "#/bot"
|
||||
import { ActivityTypes, ApplicationCommandOptionTypes, InteractionTypes } from "discordeno"
|
||||
import { InteractionHandler, MuxHandler, SlashHandler } from "./types"
|
||||
import { SlashCommandHandler } from "./slash_commands"
|
||||
import { InteractionType, MuxHandler, SlashHandler } from "./types"
|
||||
import { uuid4 } from "@temporalio/workflow"
|
||||
import { c } from "#/di"
|
||||
|
||||
|
||||
export const slashHandler: InteractionHandler = async (interaction) => {
|
||||
export const slashHandler = async (interaction: InteractionType, rootHandler: SlashHandler) => {
|
||||
if(!interaction.data) {
|
||||
return
|
||||
}
|
||||
@ -22,7 +20,6 @@ export const slashHandler: InteractionHandler = async (interaction) => {
|
||||
}
|
||||
}
|
||||
|
||||
const rootHandler = (await c.getAsync(SlashCommandHandler)).root()
|
||||
|
||||
let cur: SlashHandler | MuxHandler<any> = rootHandler
|
||||
for(let i = 0; i < commandPath.length; i++) {
|
||||
@ -47,10 +44,9 @@ export const slashHandler: InteractionHandler = async (interaction) => {
|
||||
return interaction.respond(`invokation exception: ${errId}`, {isPrivate: true})
|
||||
}
|
||||
}
|
||||
// ok now we need to go down the handler tree.
|
||||
}
|
||||
|
||||
export const events = {
|
||||
export const events = (rootHandler: SlashHandler) => {return {
|
||||
interactionCreate: async (interaction) => {
|
||||
if(interaction.acknowledged) {
|
||||
return
|
||||
@ -58,7 +54,7 @@ export const events = {
|
||||
if(interaction.type !== InteractionTypes.ApplicationCommand) {
|
||||
return
|
||||
}
|
||||
await slashHandler(interaction)
|
||||
await slashHandler(interaction, rootHandler)
|
||||
return
|
||||
},
|
||||
ready: async ({shardId}) => {
|
||||
@ -75,4 +71,4 @@ export const events = {
|
||||
],
|
||||
})
|
||||
}
|
||||
} as typeof bot.events
|
||||
} as typeof bot.events}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { formGuildLeaderboardMessage, formGuildOnlineMessage } from "#/bot/common/guild"
|
||||
import { formGuildInfoMessage, formGuildLeaderboardMessage, formGuildOnlineMessage } from "#/bot/common/guild"
|
||||
import { WYNN_GUILD_ID } from "#/constants"
|
||||
import { inject, injectable } from "@needle-di/core"
|
||||
import { SlashHandler } from "./types"
|
||||
import { PG } from "#/services/pg"
|
||||
import { ApplicationCommandOptionTypes, ApplicationCommandTypes, CreateApplicationCommand } from "discordeno"
|
||||
|
||||
@injectable()
|
||||
export class SlashCommandHandler {
|
||||
@ -10,12 +11,58 @@ export class SlashCommandHandler {
|
||||
public readonly db = inject(PG)
|
||||
) {
|
||||
}
|
||||
|
||||
commands(): CreateApplicationCommand[] {
|
||||
return [
|
||||
{
|
||||
name: `guild`,
|
||||
description: "guild commands",
|
||||
type: ApplicationCommandTypes.ChatInput,
|
||||
options: [
|
||||
{
|
||||
name: "leaderboard",
|
||||
description: "view the current leaderboard",
|
||||
type: ApplicationCommandOptionTypes.SubCommand,
|
||||
},
|
||||
{
|
||||
name: "info",
|
||||
description: "view guild information",
|
||||
type: ApplicationCommandOptionTypes.SubCommand,
|
||||
},
|
||||
{
|
||||
name: "online",
|
||||
description: "show online players",
|
||||
type: ApplicationCommandOptionTypes.SubCommand,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "admin",
|
||||
description: "admin commands",
|
||||
type: ApplicationCommandTypes.ChatInput,
|
||||
defaultMemberPermissions: [
|
||||
"ADMINISTRATOR",
|
||||
],
|
||||
options: [
|
||||
{
|
||||
name: "set_wynn_guild",
|
||||
description: "set the default wynncraft guild for the server",
|
||||
type: ApplicationCommandOptionTypes.SubCommand,
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
root(): SlashHandler {
|
||||
return {
|
||||
guild: {
|
||||
info: async (interaction) => {
|
||||
interaction.respond("TODO: guild info")
|
||||
const msg = await formGuildInfoMessage(
|
||||
WYNN_GUILD_ID,
|
||||
this.db.sql,
|
||||
)
|
||||
await interaction.respond(msg, {
|
||||
withResponse: true,
|
||||
})
|
||||
},
|
||||
online: async (interaction) => {
|
||||
const msg = await formGuildOnlineMessage(
|
||||
|
@ -5,6 +5,48 @@ import { TabWriter } from "#/lib/util/tabwriter"
|
||||
import * as md from 'ts-markdown-builder';
|
||||
|
||||
|
||||
export const formGuildInfoMessage = async (guild_id: string, sql:Sql): Promise<CreateMessageOptions & InteractionCallbackOptions> => {
|
||||
const result = await sql`
|
||||
|
||||
with ranked as (select
|
||||
uid,
|
||||
name,
|
||||
prefix,
|
||||
level,
|
||||
xp,
|
||||
territories,
|
||||
wars,
|
||||
rank() over (order by xp desc) as xp_rank
|
||||
from wynn_guild_info
|
||||
)
|
||||
select * from ranked
|
||||
where ranked.uid = ${guild_id}
|
||||
`
|
||||
|
||||
if(result.length == 0) {
|
||||
return {
|
||||
content: "no guild found",
|
||||
}
|
||||
}
|
||||
|
||||
const guild = result[0]
|
||||
|
||||
const output = [
|
||||
md.heading("[wip] guild info"),
|
||||
md.heading("overview"),
|
||||
`[${guild.prefix}] ${guild.name}
|
||||
level: ${guild.level}
|
||||
guild xp rank: ${guild.xp_rank === 1000 ? "1000+" : guild.xp_rank}
|
||||
xp: ${guild.xp}
|
||||
territories: ${guild.territories}
|
||||
wars: ${guild.wars}`,
|
||||
].join("\n\n")
|
||||
|
||||
|
||||
return {
|
||||
content: output,
|
||||
}
|
||||
}
|
||||
export const formGuildOnlineMessage = async (guild_id: string, sql:Sql): Promise<CreateMessageOptions & InteractionCallbackOptions> => {
|
||||
const result = await sql`select
|
||||
name,
|
||||
|
@ -1,90 +0,0 @@
|
||||
"use strict";
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
if (typeof b !== "function" && b !== null)
|
||||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __generator = (this && this.__generator) || function (thisArg, body) {
|
||||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
||||
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
||||
function verb(n) { return function (v) { return step([n, v]); }; }
|
||||
function step(op) {
|
||||
if (f) throw new TypeError("Generator is already executing.");
|
||||
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
||||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
||||
if (y = 0, t) op = [op[0] & 2, t.value];
|
||||
switch (op[0]) {
|
||||
case 0: case 1: t = op; break;
|
||||
case 4: _.label++; return { value: op[1], done: false };
|
||||
case 5: _.label++; y = op[1]; op = [0]; continue;
|
||||
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
||||
default:
|
||||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
||||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
||||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
||||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
||||
if (t[2]) _.ops.pop();
|
||||
_.trys.pop(); continue;
|
||||
}
|
||||
op = body.call(thisArg, _);
|
||||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
||||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
||||
}
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.BotCommand = void 0;
|
||||
var clipanion_1 = require("clipanion");
|
||||
var BotCommands = require("#/slashcommands");
|
||||
// di
|
||||
require("#/services/pg");
|
||||
var constants_1 = require("#/constants");
|
||||
var bot_1 = require("#/bot");
|
||||
var handler_1 = require("#/bot/botevent/handler");
|
||||
var BotCommand = /** @class */ (function (_super) {
|
||||
__extends(BotCommand, _super);
|
||||
function BotCommand() {
|
||||
return _super !== null && _super.apply(this, arguments) || this;
|
||||
}
|
||||
BotCommand.prototype.execute = function () {
|
||||
return __awaiter(this, void 0, void 0, function () {
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0:
|
||||
bot_1.bot.events = handler_1.events;
|
||||
console.log('registring slash commands');
|
||||
return [4 /*yield*/, bot_1.bot.rest.upsertGuildApplicationCommands(constants_1.DISCORD_GUILD_ID, Object.values(BotCommands))];
|
||||
case 1:
|
||||
_a.sent();
|
||||
console.log('connecting bot to gateway');
|
||||
return [4 /*yield*/, bot_1.bot.start()];
|
||||
case 2:
|
||||
_a.sent();
|
||||
console.log('bot connected');
|
||||
return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
BotCommand.paths = [['bot']];
|
||||
return BotCommand;
|
||||
}(clipanion_1.Command));
|
||||
exports.BotCommand = BotCommand;
|
@ -1,20 +1,29 @@
|
||||
import { Command } from 'clipanion';
|
||||
|
||||
import * as BotCommands from "#/slashcommands";
|
||||
|
||||
// di
|
||||
import "#/services/pg"
|
||||
import { DISCORD_GUILD_ID } from '#/constants';
|
||||
import { bot } from '#/bot';
|
||||
import { events } from '#/bot/botevent/handler';
|
||||
import { SlashCommandHandler } from '#/bot/botevent/slash_commands';
|
||||
import { c } from '#/di';
|
||||
import { config } from '#/config';
|
||||
|
||||
|
||||
export class BotCommand extends Command {
|
||||
static paths = [['bot']];
|
||||
async execute() {
|
||||
bot.events = events
|
||||
if(!config.DISCORD_TOKEN) {
|
||||
throw new Error('no discord token found. bot cant start');
|
||||
}
|
||||
const rootHandler = await c.getAsync(SlashCommandHandler)
|
||||
bot.events = events(rootHandler.root())
|
||||
console.log('registring slash commands');
|
||||
await bot.rest.upsertGuildApplicationCommands(DISCORD_GUILD_ID, Object.values(BotCommands))
|
||||
await bot.rest.upsertGuildApplicationCommands(DISCORD_GUILD_ID, rootHandler.commands()).catch(console.error)
|
||||
await bot.rest.upsertGuildApplicationCommands("547828454972850196", rootHandler.commands()).catch(console.error)
|
||||
|
||||
|
||||
console.log('connecting bot to gateway');
|
||||
await bot.start();
|
||||
console.log('bot connected');
|
||||
|
@ -12,7 +12,7 @@ import { config } from '#/config';
|
||||
import * as activities from '../activities';
|
||||
import path from 'path';
|
||||
import { Client, ScheduleNotFoundError, ScheduleOptions, ScheduleOverlapPolicy } from '@temporalio/client';
|
||||
import { workflowSyncAllGuilds, workflowSyncGuilds, workflowSyncOnline } from '#/workflows';
|
||||
import { workflowSyncAllGuilds, workflowSyncGuilds, workflowSyncOnline, workflowSyncGuildLeaderboardInfo } from '#/workflows';
|
||||
import { PG } from '#/services/pg';
|
||||
|
||||
|
||||
@ -34,6 +34,22 @@ const schedules: ScheduleOptions[] = [
|
||||
}]
|
||||
},
|
||||
},
|
||||
{
|
||||
scheduleId: "update_guild_leaderboards",
|
||||
action: {
|
||||
type: 'startWorkflow',
|
||||
workflowType: workflowSyncGuildLeaderboardInfo,
|
||||
taskQueue: 'wynn-worker',
|
||||
},
|
||||
policies: {
|
||||
overlap: ScheduleOverlapPolicy.SKIP,
|
||||
},
|
||||
spec: {
|
||||
intervals: [{
|
||||
every: '5 minutes',
|
||||
}]
|
||||
},
|
||||
},
|
||||
{
|
||||
scheduleId: "update-all-guilds",
|
||||
action: {
|
||||
|
@ -79,8 +79,11 @@ create table if not exists wynn_guild_season_results (
|
||||
value jsonb not null,
|
||||
primary key (discord_guild, name)
|
||||
)`
|
||||
})
|
||||
}),
|
||||
|
||||
migration("add-guild-xp", async (sql)=>{
|
||||
await sql`alter table wynn_guild_info add xp bigint not null default 0;`
|
||||
}),
|
||||
]
|
||||
|
||||
|
||||
|
@ -1,20 +0,0 @@
|
||||
|
||||
import {ApplicationCommandOptionTypes, ApplicationCommandTypes, CreateApplicationCommand } from "discordeno";
|
||||
|
||||
|
||||
|
||||
export const AdminCommands: CreateApplicationCommand = {
|
||||
name: "admin",
|
||||
description: "admin commands",
|
||||
type: ApplicationCommandTypes.ChatInput,
|
||||
defaultMemberPermissions: [
|
||||
"ADMINISTRATOR",
|
||||
],
|
||||
options: [
|
||||
{
|
||||
name: "set_wynn_guild",
|
||||
description: "set the default wynncraft guild for the server",
|
||||
type: ApplicationCommandOptionTypes.SubCommand,
|
||||
},
|
||||
],
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
import {ApplicationCommandOptionTypes, ApplicationCommandTypes, CreateApplicationCommand } from "discordeno";
|
||||
|
||||
|
||||
|
||||
export const GuildCommands: CreateApplicationCommand = {
|
||||
name: "guild",
|
||||
description: "guild commands",
|
||||
type: ApplicationCommandTypes.ChatInput,
|
||||
options: [
|
||||
{
|
||||
name: "leaderboard",
|
||||
description: "view the current leaderboard",
|
||||
type: ApplicationCommandOptionTypes.SubCommand,
|
||||
},
|
||||
{
|
||||
name: "info",
|
||||
description: "view guild information",
|
||||
type: ApplicationCommandOptionTypes.SubCommand,
|
||||
},
|
||||
{
|
||||
name: "online",
|
||||
description: "show online players",
|
||||
type: ApplicationCommandOptionTypes.SubCommand,
|
||||
},
|
||||
],
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
/**
|
||||
* @file Automatically generated by barrelsby.
|
||||
*/
|
||||
|
||||
export * from "./admin";
|
||||
export * from "./guild_overview";
|
@ -2,7 +2,7 @@
|
||||
import { proxyActivities } from '@temporalio/workflow';
|
||||
import type * as activities from '#/activities';
|
||||
|
||||
const { update_guild, update_all_guilds } = proxyActivities<typeof activities>({
|
||||
const { update_guild, update_all_guilds, update_guild_levels } = proxyActivities<typeof activities>({
|
||||
startToCloseTimeout: '1 minute',
|
||||
});
|
||||
|
||||
@ -10,6 +10,10 @@ export const workflowSyncAllGuilds = async() => {
|
||||
await update_all_guilds()
|
||||
}
|
||||
|
||||
export const workflowSyncGuildLeaderboardInfo = async() => {
|
||||
await update_guild_levels()
|
||||
}
|
||||
|
||||
export const workflowSyncGuilds = async() => {
|
||||
// TODO side effect
|
||||
const guildNames = [
|
||||
@ -22,3 +26,4 @@ export const workflowSyncGuilds = async() => {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user