From 2d82ddcddc9c28d5bf83edc407ace3d49686edaa Mon Sep 17 00:00:00 2001
From: Innei
Date: Wed, 21 Aug 2024 22:49:59 +0800
Subject: [PATCH] feat(route/rss3): RSS3 metadata parser (#16486)
* init
* done
Signed-off-by: Innei
* chore: cleanup
Signed-off-by: Innei
* fix: format
Signed-off-by: Innei
* fix: pass through
Signed-off-by: Innei
* chore: add testcase
Signed-off-by: Innei
* fix: update
Signed-off-by: Innei
---------
Signed-off-by: Innei
---
lib/routes/rss3/index.ts | 460 ++++++++++++++++++++++++-
lib/routes/rss3/interfaces/metadata.ts | 67 ++++
lib/utils/camelcase-keys.spec.ts | 127 +++++++
lib/utils/camelcase-keys.ts | 27 ++
package.json | 3 +-
pnpm-lock.yaml | 44 +++
6 files changed, 714 insertions(+), 14 deletions(-)
create mode 100644 lib/routes/rss3/interfaces/metadata.ts
create mode 100644 lib/utils/camelcase-keys.spec.ts
create mode 100644 lib/utils/camelcase-keys.ts
diff --git a/lib/routes/rss3/index.ts b/lib/routes/rss3/index.ts
index 49ad4ddd8..820e36c2b 100644
--- a/lib/routes/rss3/index.ts
+++ b/lib/routes/rss3/index.ts
@@ -1,6 +1,10 @@
-import { Route } from '@/types';
+/* eslint-disable default-case */
+import { Route, type DataItem } from '@/types';
+import { camelcaseKeys } from '@/utils/camelcase-keys';
import ofetch from '@/utils/ofetch';
+import type { Action } from '@rss3/sdk';
+import type { GetRSS3DataMetadata } from './interfaces/metadata';
export const route: Route = {
path: '/:account/:network?/:tag?',
@@ -132,17 +136,447 @@ async function handler(ctx) {
return {
title: `${account} activities`,
link: 'https://rss3.io',
- item: data.map((item) => ({
- title: `New ${item.tag} ${item.type} action on ${item.network}`,
- description: `New ${item.tag} ${item.type} action on ${item.network}
From: ${item.from}
To: ${item.to}`,
- link: item.actions?.[0]?.related_urls?.[0],
- guid: item.id,
- author: [
- {
- name: item.owner,
- avatar: `https://cdn.stamp.fyi/avatar/eth:${item.owner}`,
- },
- ],
- })),
+ item: data.map((item) => {
+ const content = parseItemActionToContent(camelcaseKeys(item.actions));
+
+ const description = `New ${item.tag} ${item.type} action on ${item.network}
From: ${item.from}
To: ${item.to}`;
+ return {
+ title: `New ${item.tag} ${item.type} action on ${item.network}`,
+ description: content ? `${description}
${content}` : description,
+ link: item.actions?.[0]?.related_urls?.[0],
+ guid: item.id,
+ author: [
+ {
+ name: item.owner,
+ avatar: `https://cdn.stamp.fyi/avatar/eth:${item.owner}`,
+ },
+ ],
+
+ _extra: { raw: item },
+ } as DataItem;
+ }),
};
}
+
+function parseItemActionToContent(actions: Action[]): string | undefined {
+ if (!actions) {
+ return;
+ }
+
+ let joint = '';
+
+ for (const action of actions) {
+ const metadata = action.metadata;
+ if (!metadata) {
+ continue;
+ }
+ const { tag } = action;
+ switch (tag) {
+ case 'social':
+ joint += renderSocialTagContent(action);
+ break;
+ case 'collectible':
+ joint += renderCollectibleTagContent(action);
+ break;
+ case 'metaverse':
+ joint += renderMetaverseTagContent(action);
+
+ break;
+ case 'exchange':
+ joint += renderExchange(action);
+ break;
+ case 'transaction':
+ joint += renderTransaction(action);
+ break;
+ }
+
+ joint += '
';
+ }
+
+ return joint;
+}
+
+const renderTransaction = (action: Action) => {
+ let joint = '';
+ const { type } = action;
+ const tag = 'transaction';
+ switch (type) {
+ case 'transfer':
+ case 'burn':
+ case 'mint':
+ case 'approval': {
+ const metadata = extractMetadata(tag, type, action);
+ if (!metadata) {
+ break;
+ }
+ joint += buildHTML([
+ /* html */ `Transaction ${type.toUpperCase().at(0) + type.slice(1)}
`,
+ /* html */ `
Name: ${metadata.name}
`, + /* html */ `Value: ${metadata.value}
`, + /* html */ `Standard: ${metadata.standard}
`, + /* html */ `Symbol: ${metadata.symbol}
`, + /* html */ `Decimals: ${metadata.decimals}
`, + /* html */ `Address: ${metadata.address}
`, + ]); + break; + } + case 'event': { + const metadata = extractMetadata(tag, type, action); + if (!metadata) { + break; + } + joint += buildHTML([/* html */ `Block Hash: ${metadata.block.hash}
`, /* html */ `Transaction Hash: ${metadata.transaction.hash}
`]); + break; + } + case 'bridge': { + const metadata = extractMetadata(tag, type, action); + if (!metadata) { + break; + } + joint += buildHTML([ + /* html */ `Action: ${metadata.action}
`, + /* html */ `Source Network: ${metadata.sourceNetwork}
`, + /* html */ `Target Network: ${metadata.targetNetwork}
`, + metadata.token && /* html */ `Token name: ${metadata.token.name}
`, + metadata.token && /* html */ `Token Symbol: ${metadata.token.symbol}
`, + metadata.token && /* html */ `Token Value: ${metadata.token.value}
`, + metadata.token && /* html */ `Token Address: ${metadata.token.address}
`, + ]); + break; + } + } + + return buildSectionFooterHTML(joint, action); +}; + +const renderExchange = (action: Action) => { + let joint = ''; + const { type } = action; + const tag = 'exchange'; + switch (type) { + case 'liquidity': { + const metadata = extractMetadata(tag, type, action); + if (!metadata) { + break; + } + joint += buildHTML([ + /* html */ `Action: ${metadata.action}
`, + /* html */ `+
| Address | +Value | +Name | +Symbol | +Decimals | +Standard | +
|---|---|---|---|---|---|
| ${token.address} | +${token.value} | +${token.name} | +${token.symbol} | +${token.decimals} | +${token.standard} | +
Action: ${metadata.action}
`, + metadata.token && + /* html */ `+ + Token: +
From: ${metadata.from.address}
`, + /* html */ metadata.to && `To: ${metadata.to?.address}
`, + ]); + } + } + + return buildSectionFooterHTML(joint, action); +}; + +const renderMetaverseTagContent = (action: Action) => { + let joint = ''; + const { from, to, type } = action; + const tag = 'metaverse'; + switch (type) { + case 'burn': { + const metadata = extractMetadata(tag, type, action); + if (!metadata) { + break; + } + joint += buildHTML([ + /* html */ `Name: ${metadata.name}
`, + /* html */ `Address: ${metadata.address}
`, + /* html */ `Symbol: ${metadata.symbol}
`, + /* html */ `Value: ${metadata.value}
`, + /* html */ `${from} --> ${to}
`, + ]); + break; + } + case 'trade': { + const metadata = extractMetadata(tag, type, action); + if (!metadata) { + break; + } + joint += buildHTML([ + /* html */ `Name: ${metadata.name}
`, + /* html */ `Address: ${metadata.address}
`, + /* html */ `Symbol: ${metadata.symbol}
`, + /* html */ `Value: ${metadata.value}
`, + /* html */ `${from} --> ${to}
`, + ]); + break; + } + case 'mint': + { + const metadata = extractMetadata(tag, type, action); + if (!metadata) { + break; + } + joint += buildHTML([ + /* html */ `Name: ${metadata.name}
`, + /* html */ `Address: ${metadata.address}
`, + /* html */ `Symbol: ${metadata.symbol}
`, + /* html */ `Value: ${metadata.value}
`, + /* html */ `${from} --> ${to}
`, + ]); + } + break; + case 'transfer': { + const metadata = extractMetadata(tag, type, action); + if (!metadata) { + break; + } + joint += buildHTML([ + /* html */ `Name: ${metadata.name}
`, + /* html */ `Address: ${metadata.address}
`, + /* html */ `Symbol: ${metadata.symbol}
`, + /* html */ `Value: ${metadata.value}
`, + /* html */ `${from} --> ${to}
`, + ]); + } + } + + return buildSectionFooterHTML(joint, action); +}; +const renderCollectibleTagContent = (action: Action) => { + let joint = ''; + const { from, to, type } = action; + const tag = 'collectible'; + switch (type) { + case 'approval': { + const metadata = extractMetadata(tag, type, action); + if (!metadata) { + break; + } + joint += buildHTML([ + /* html */ `Name: ${metadata.name}
`, + /* html */ `Address: ${metadata.address}
`, + /* html */ `Symbol: ${metadata.symbol}
`, + /* html */ `Value: ${metadata.value}
`, + /* html */ `${from} --> ${to}
`, + ]); + + break; + } + case 'burn': { + const metadata = extractMetadata(tag, type, action); + if (!metadata) { + break; + } + joint += buildHTML([ + /* html */ `Name: ${metadata.name}
`, + /* html */ `Address: ${metadata.address}
`, + /* html */ `Symbol: ${metadata.symbol}
`, + /* html */ `Value: ${metadata.value}
`, + /* html */ `${from} --> ${to}
`, + ]); + break; + } + case 'trade': { + const metadata = extractMetadata(tag, type, action); + if (!metadata) { + break; + } + joint += buildHTML([ + /* html */ `Name: ${metadata.name}
`, + /* html */ `Address: ${metadata.address}
`, + /* html */ `Symbol: ${metadata.symbol}
`, + /* html */ `Value: ${metadata.value}
`, + /* html */ `${from} --> ${to}
`, + ]); + break; + } + case 'mint': + { + const metadata = extractMetadata(tag, type, action); + if (!metadata) { + break; + } + joint += buildHTML([ + /* html */ `Name: ${metadata.name}
`, + /* html */ `Address: ${metadata.address}
`, + /* html */ `Symbol: ${metadata.symbol}
`, + /* html */ `Value: ${metadata.value}
`, + /* html */ `${from} --> ${to}
`, + ]); + } + break; + case 'transfer': { + const metadata = extractMetadata(tag, type, action); + if (!metadata) { + break; + } + joint += buildHTML([ + /* html */ `Name: ${metadata.name}
`, + /* html */ `Address: ${metadata.address}
`, + /* html */ `Symbol: ${metadata.symbol}
`, + /* html */ `Value: ${metadata.value}
`, + /* html */ `${from} --> ${to}
`, + ]); + } + } + + return buildSectionFooterHTML(joint, action); +}; + +const renderSocialTagContent = (action: Action) => { + let joint = ''; + const { from, to, platform, type } = action; + const tag = 'social'; + switch (type) { + case 'profile': { + const metadata = extractMetadata(tag, type, action); + if (!metadata) { + break; + } + + joint += buildHTML([ + `Name: ${metadata.name}
`, + `Handle: ${metadata.handle}
`, + `Bio: ${metadata.bio}
`, + `Platform: ${platform}
`, + metadata.imageUri && `Title: ${metadata.title}
`, `${from} --> ${to}
`]); + break; + } + case 'delete': { + const metadata = extractMetadata(tag, type, action); + if (!metadata) { + break; + } + joint += buildHTML([`Title: ${metadata.title}
`]); + break; + } + case 'post': { + const metadata = extractMetadata(tag, type, action); + if (!metadata) { + break; + } + joint += buildHTML([ + /* html*/ `Title: ${metadata.title}
`, + /* html*/ `Author: ${metadata.handle}
`, + /* html*/ `Content:
${metadata.body}`,
+ /* html*/ `Platform: ${platform}
`, + ]); + break; + } + case 'comment': { + const metadata = extractMetadata(tag, type, action); + if (!metadata) { + break; + } + joint += buildHTML([ + /* html*/ `Comment Anchor:${metadata.handle}
`, + metadata.target && /* html*/ `Comment Target: ${metadata.target.title || metadata.targetUrl}
`, + ]); + + break; + } + case 'reward': + case 'revise': + case 'proxy': + case 'share': + break; + } + + return joint; +}; + +function extractMetadataPlatform: ${action.platform}
`, + + /* html */ `Related URLs: +