diff --git a/deno.json b/deno.json index 72b9c7f..5944e6e 100644 --- a/deno.json +++ b/deno.json @@ -6,7 +6,8 @@ "@hongminhee/x-forwarded-fetch": "jsr:@hongminhee/x-forwarded-fetch@^0.2.0", "@logtape/logtape": "jsr:@logtape/logtape@^0.6.3", "@opentelemetry/api": "npm:@opentelemetry/api@^1.9.0", - "@std/assert": "jsr:@std/assert@1" + "@std/assert": "jsr:@std/assert@1", + "@@x/escape": "https://deno.land/x/escape@1.3.0/mod.ts" }, "unstable": [ "temporal" diff --git a/deno.lock b/deno.lock index 6c220c6..16ac9f1 100644 --- a/deno.lock +++ b/deno.lock @@ -367,6 +367,7 @@ "https://esm.sh/v135/@types/extract-files@latest/isExtractableFile~.d.ts": "https://esm.sh/v135/@types/extract-files@13.0.1/isExtractableFile~.d.ts" }, "remote": { + "https://deno.land/x/escape@1.3.0/mod.ts": "cd9ff4e962fcafc310858edc8b839065fea328da42b5bf1727a2d760a65c4c2d", "https://deno.land/x/graphql_deno@v15.0.0/lib/error/GraphQLError.js": "18adbba7aa651770e0876d0c7df4e6e2ab647a9f09d4b5c107c57d6fa157be9d", "https://deno.land/x/graphql_deno@v15.0.0/lib/error/formatError.js": "aec87433c501df6d6272b64974e8edf53b2ed192e66782b827328d635ed55df8", "https://deno.land/x/graphql_deno@v15.0.0/lib/error/index.js": "7557dcea8830550f82dd7b1984fdc216e14327d094f501bd2a03f80bf609a768", diff --git a/src/federation.ts b/src/federation.ts index 66ae0cf..563dad2 100644 --- a/src/federation.ts +++ b/src/federation.ts @@ -1,7 +1,8 @@ -import { createFederation, Person, Application, Image, PropertyValue } from "@fedify/fedify" +import { createFederation, Person, Application, Image, PropertyValue, Organization, Group } from "@fedify/fedify" import { kv } from "./redis.ts" import { getLogger } from "https://jsr.io/@logtape/logtape/0.6.3/logtape/logger.ts" -import { doQueryPlayer } from "./graphql/stratz.ts" +import { doQueryGuild, doQueryPlayer } from "./graphql/stratz.ts" +import { escapeHtml } from "@@x/escape" const l = getLogger(["dotino-veloce", "federation"]) @@ -9,6 +10,7 @@ l.debug`Creating federation object...` export const federation = createFederation({ kv }) const userMatcher = /u([0-9]+)/ +const guildMatcher = /g([0-9]+)/ l.debug`Creating actor dispatcher...` // deno-lint-ignore require-await @@ -55,7 +57,7 @@ async function actorDispatcher(ctx: any, handle: string) { // https://akkoma.dev/AkkomaGang/akkoma/src/commit/f1018867097e6f293d8b2b5b6935f0a7ebf99bd0/lib/pleroma/web/activity_pub/object_validators/user_validator.ex#L72 inbox: ctx.getInboxUri(handle), - name: `[TEST] ${player.name}`, + name: `[TEST] ${escapeHtml(player.name)}`, icon: new Image({ url: new URL(player.avatar), mediaType: "image/jpeg" @@ -66,7 +68,7 @@ async function actorDispatcher(ctx: any, handle: string) { value: `https://steamcommunity.com/profiles/[U:1:${player.id}]`, }), new PropertyValue({ - name: "Dota profile", + name: "STRATZ", value: `https://stratz.com/players/${player.id}`, }), new PropertyValue({ @@ -81,6 +83,51 @@ async function actorDispatcher(ctx: any, handle: string) { }) } + const guildId = Number.parseInt(handle.match(guildMatcher)?.[1] as any) + if(Number.isFinite(guildId)) { + l.info`Dispatching guild with Guild ID: ${guildId}` + + const guild = await doQueryGuild(guildId) + + if(guild === null) { + l.warn`No guild was found with ID: ${guildId}` + return null + } + + return new Group({ + id, + preferredUsername: id.href, + // Akkoma expects URL to be equal to ID + // https://akkoma.dev/AkkomaGang/akkoma/src/commit/f1018867097e6f293d8b2b5b6935f0a7ebf99bd0/lib/pleroma/object/fetcher.ex#L287 + url: id, + // Akkoma requires inboxes to be setup to display profiles + // https://akkoma.dev/AkkomaGang/akkoma/src/commit/f1018867097e6f293d8b2b5b6935f0a7ebf99bd0/lib/pleroma/web/activity_pub/object_validators/user_validator.ex#L72 + inbox: ctx.getInboxUri(handle), + + name: `[TEST] ${escapeHtml(guild.name)}`, + summary: escapeHtml(guild.description), + icon: new Image({ + url: new URL(`https://steamusercontent-a.akamaihd.net/ugc/${guild.logo}`), + mediaType: "image/jpeg" + }), + published: Temporal.Instant.fromEpochMilliseconds(guild.createdDateTime * 1000), + attachments: [ + new PropertyValue({ + name: "Tag", + value: `[${escapeHtml(guild.tag)}]`, + }), + new PropertyValue({ + name: "Message of the day", + value: escapeHtml(guild.motd), + }), + new PropertyValue({ + name: "STRATZ", + value: `https://stratz.com/guilds/${guild.id}`, + }), + ] + }) + } + l.warn`No dispatcher was found for handle: ${handle}` return null diff --git a/src/graphql/stratz.ts b/src/graphql/stratz.ts index b2bd3a2..6bf4e65 100644 --- a/src/graphql/stratz.ts +++ b/src/graphql/stratz.ts @@ -50,7 +50,7 @@ export async function doQueryPlayer(steamId: number): Promise<{ avatar: string, } | null> { l.info`Querying player ${steamId} on the Stratz API...` - const player = await doQuery( + const response = await doQuery( graphql` query ($steamId: Long!) { player (steamAccountId: $steamId) { @@ -66,5 +66,34 @@ export async function doQueryPlayer(steamId: number): Promise<{ `, {steamId} ) - return player?.player?.steamAccount ?? null + return response?.player?.steamAccount ?? null +} + +export async function doQueryGuild(guildId: number): Promise<{ + id: number, + motd: string, + name: string, + tag: string, + logo: string, + description: string, + createdDateTime: number, +} | null> { + l.info`Querying guild ${guildId} on the Stratz API...` + const response = await doQuery( + graphql` + query ($guildId: Int!) { + guild(id: $guildId) { + id, + motd, + name, + tag, + logo, + description, + createdDateTime, + } + } + `, + {guildId} + ) + return response?.guild ?? null }