feat(route): add sdo/ff14risingstones (#19914)
* feat(route): sdo/ff14risingstones * fix: add a check for `from_info` * feat: add category * fix: raw output * feat: add author * fix: remove the duplicate `[RP]` * docs: add information for the `ua` configuration * feat: add fc create_time * feat: add fc create_time * fix: add fc detail_mask * fix error message * Update utils.ts * Update lib/routes/sdo/ff14risingstones/strats.ts Co-authored-by: Tony <TonyRL@users.noreply.github.com> * Update lib/routes/sdo/ff14risingstones/posts.ts
This commit is contained in:
parent
e3119f6207
commit
e20f27e491
|
|
@ -325,6 +325,10 @@ export type Config = {
|
|||
scihub: {
|
||||
host?: string;
|
||||
};
|
||||
sdo: {
|
||||
ff14risingstones?: string;
|
||||
ua?: string;
|
||||
};
|
||||
sis001: {
|
||||
baseUrl?: string;
|
||||
};
|
||||
|
|
@ -797,6 +801,10 @@ const calculateValue = () => {
|
|||
scihub: {
|
||||
host: envs.SCIHUB_HOST || 'https://sci-hub.se/',
|
||||
},
|
||||
sdo: {
|
||||
ff14risingstones: envs.SDO_FF14RISINGSTONES,
|
||||
ua: envs.SDO_UA,
|
||||
},
|
||||
sis001: {
|
||||
baseUrl: envs.SIS001_BASE_URL || 'https://sis001.com',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
import cache from '@/utils/cache';
|
||||
import { API_URL } from './constant';
|
||||
import type { DutiesPartyDetail, FreeCompanyPartyDetail, NoviceNetworkParty, PostDetail, Resently, UserDynamic, UserInfo, UserPost } from './types';
|
||||
import { requestAPI } from './utils';
|
||||
|
||||
// 获取用户游戏近况
|
||||
export const getResently = async (uid: string) => await requestAPI<Resently[]>(`${API_URL}/home/userInfo/getResently?uuid=${uid}`);
|
||||
|
||||
// 获取用户信息
|
||||
export const getUserInfo = async (uid: string) => await requestAPI<UserInfo>(`${API_URL}/home/userInfo/getUserInfo?uuid=${uid}`);
|
||||
|
||||
// 获取用户帖子
|
||||
export const getUserPosts = async (uid: string, type: 1 | 2) =>
|
||||
await requestAPI<{
|
||||
rows: UserPost[];
|
||||
}>(`${API_URL}/home/userInfo/getUserPosts?uuid=${uid}&type=${type}`).then((res) => res.rows);
|
||||
|
||||
// 获取用户动态
|
||||
export const getUserDynamic = async (uid: string) =>
|
||||
await requestAPI<{
|
||||
rows: UserDynamic[];
|
||||
}>(`${API_URL}/home/userInfo/getUserDynamic?uuid=${uid}`).then((res) => res.rows);
|
||||
|
||||
// 获取帖子详情
|
||||
export async function getPostsDetail(postID: string) {
|
||||
try {
|
||||
return await cache.tryGet(`sdo/ff14risingstones/post-detail:${postID}`, async () => await requestAPI<PostDetail>(`${API_URL}/home/posts/postsDetail?id=${postID}`));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取导芽招募详情
|
||||
export async function getNoviceNetworkRecruitDetail(recruitID: number) {
|
||||
try {
|
||||
return await cache.tryGet(`sdo/ff14risingstones/novice-network-recruit-detail:${recruitID}`, async () => await requestAPI<NoviceNetworkParty>(`${API_URL}/home/recruit/getNeDetail?id=${recruitID}`));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取副本招募详情
|
||||
export async function getDutiesRecruitDetail(recruitID: number) {
|
||||
try {
|
||||
return await cache.tryGet(`sdo/ff14risingstones/duties-recruit-detail:${recruitID}`, async () => await requestAPI<DutiesPartyDetail>(`${API_URL}/home/recruit/getRecruitFbDetail?id=${recruitID}`));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取部队招募详情
|
||||
export async function getFreeCompanyRecruitDetail(recruitID: number) {
|
||||
try {
|
||||
return await cache.tryGet(`sdo/ff14risingstones/free-company-recruit-detail:${recruitID}`, async () => await requestAPI<FreeCompanyPartyDetail>(`${API_URL}/home/recruit/getRecruitGuildDetail?id=${recruitID}`));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取帖文列表
|
||||
export async function getPosts(params: { type: 1 | 2; is_top?: 0 | 1; is_refine?: 0 | 1; limit?: number | string; order?: string; hotType?: string; part_id?: string }) {
|
||||
const searchParams = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== undefined) {
|
||||
searchParams.set(key, value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return await requestAPI<{
|
||||
rows: UserPost[];
|
||||
}>(`${API_URL}/home/posts/postsList?${searchParams.toString()}`).then((res) => res.rows);
|
||||
}
|
||||
|
||||
// 获取用户关注动态
|
||||
export const getFollowDynamicList = async (limit: number | string) =>
|
||||
await requestAPI<{
|
||||
rows: UserDynamic[];
|
||||
}>(`${API_URL}/home/dynamic/getFollowDynamicList?limit=${limit}`).then((res) => res.rows);
|
||||
|
|
@ -0,0 +1,338 @@
|
|||
import type { Route } from '@/types';
|
||||
|
||||
export const REQUIRE_CONFIG: NonNullable<Route['features']>['requireConfig'] = [
|
||||
{
|
||||
name: 'SDO_FF14RISINGSTONES',
|
||||
description: '值为 Cookie 头中 ff14risingstones 值',
|
||||
},
|
||||
{
|
||||
// https://github.com/StarHeartHunt/ff14risingstone_sign_task/issues/17
|
||||
name: 'SDO_UA',
|
||||
description: '值为与在网页端获取 Cookie 时相匹配的 User-Agent 值',
|
||||
},
|
||||
];
|
||||
|
||||
export const API_URL = 'https://apiff14risingstones.web.sdo.com/api';
|
||||
|
||||
export const ORIGIN = 'https://ff14risingstones.web.sdo.com';
|
||||
export const INDEX_URL = `${ORIGIN}/pc/index.html`;
|
||||
|
||||
export const LOGO_URL = `${ORIGIN}/pc/favicon.ico`;
|
||||
|
||||
// source: /api/home/recruit/styleConfigList
|
||||
export const PLAY_STYLE = {
|
||||
'2': '采矿/园艺',
|
||||
'3': '成就收集',
|
||||
'4': '大型任务',
|
||||
'5': '钓鱼',
|
||||
'6': '多玛方城战',
|
||||
'7': '房屋',
|
||||
'8': '怪物狩猎',
|
||||
'9': '集体动作',
|
||||
'10': '角色扮演',
|
||||
'11': '金碟游乐场',
|
||||
'18': '练级',
|
||||
'19': '休闲玩家',
|
||||
'20': '硬核玩家',
|
||||
'21': '主线',
|
||||
'22': '赚钱',
|
||||
'23': '武具投影',
|
||||
'26': '乐器演奏',
|
||||
'28': '聊天',
|
||||
'29': '玩家自办活动',
|
||||
'30': '迷宫挑战',
|
||||
'31': '讨伐歼灭战',
|
||||
'32': '玩家对战',
|
||||
'33': '随机任务',
|
||||
'34': '寻宝',
|
||||
'35': '特殊迷宫探索',
|
||||
'36': '特殊地图探索',
|
||||
'37': '青魔法师',
|
||||
'38': '开拓无人岛',
|
||||
'39': '制作',
|
||||
'41': '绝境战',
|
||||
'42': '深层迷宫挑战',
|
||||
};
|
||||
|
||||
// source: /api/home/recruit/getJobConfigList
|
||||
export const JOB = {
|
||||
'1': '防护职业',
|
||||
'3': '进攻职业',
|
||||
'5': '治疗职业',
|
||||
'6': '骑士',
|
||||
'7': '战士',
|
||||
'8': '暗黑骑士',
|
||||
'9': '绝枪战士',
|
||||
'10': '白魔法师',
|
||||
'11': '占星术士',
|
||||
'12': '学者',
|
||||
'13': '贤者',
|
||||
'14': '武僧',
|
||||
'15': '龙骑士',
|
||||
'16': '忍者',
|
||||
'17': '武士',
|
||||
'18': '钐镰客',
|
||||
'19': '吟游诗人',
|
||||
'20': '机工士',
|
||||
'21': '舞者',
|
||||
'22': '黑魔法师',
|
||||
'23': '召唤师',
|
||||
'24': '赤魔法师',
|
||||
'25': '青魔法师',
|
||||
'28': '近战职业',
|
||||
'29': '远程物理',
|
||||
'30': '远程魔法',
|
||||
'32': '任意职业',
|
||||
'33': '蝰蛇剑士',
|
||||
'34': '绘灵法师',
|
||||
};
|
||||
|
||||
// source: /api/home/posts/partList type = 1
|
||||
export const POST_PART = [
|
||||
{
|
||||
value: '34',
|
||||
label: '冒险者行会',
|
||||
},
|
||||
{
|
||||
value: '52',
|
||||
label: '生活杂谈',
|
||||
},
|
||||
{
|
||||
value: '38',
|
||||
label: '同人创作',
|
||||
},
|
||||
{
|
||||
value: '36',
|
||||
label: '剧情讨论',
|
||||
},
|
||||
{
|
||||
value: '51',
|
||||
label: '建议和BUG反馈',
|
||||
},
|
||||
{
|
||||
value: '37',
|
||||
label: '游戏记录',
|
||||
},
|
||||
{
|
||||
value: '35',
|
||||
label: '举手提问',
|
||||
},
|
||||
{
|
||||
value: '74',
|
||||
label: '版务专区',
|
||||
},
|
||||
{
|
||||
value: '75',
|
||||
label: '官方讯息',
|
||||
},
|
||||
];
|
||||
|
||||
// source: /api/home/posts/partList type = 2
|
||||
export const STRAT_PART = [
|
||||
{
|
||||
label: '新手指引',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: '副本攻略',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
label: '战斗职业',
|
||||
value: '3',
|
||||
},
|
||||
{
|
||||
label: 'PVP',
|
||||
value: '4',
|
||||
},
|
||||
{
|
||||
label: '生产采集',
|
||||
value: '5',
|
||||
},
|
||||
{
|
||||
label: '投影外观',
|
||||
value: '6',
|
||||
},
|
||||
{
|
||||
label: '房屋装修',
|
||||
value: '7',
|
||||
},
|
||||
{
|
||||
label: '骑士',
|
||||
value: '8',
|
||||
},
|
||||
{
|
||||
label: '武僧',
|
||||
value: '9',
|
||||
},
|
||||
{
|
||||
label: '战士',
|
||||
value: '10',
|
||||
},
|
||||
{
|
||||
label: '龙骑士',
|
||||
value: '11',
|
||||
},
|
||||
{
|
||||
label: '吟游诗人',
|
||||
value: '12',
|
||||
},
|
||||
{
|
||||
label: '白魔法师',
|
||||
value: '13',
|
||||
},
|
||||
{
|
||||
label: '黑魔法师',
|
||||
value: '14',
|
||||
},
|
||||
{
|
||||
label: '召唤师',
|
||||
value: '15',
|
||||
},
|
||||
{
|
||||
label: '学者',
|
||||
value: '16',
|
||||
},
|
||||
{
|
||||
label: '忍者',
|
||||
value: '17',
|
||||
},
|
||||
{
|
||||
label: '机工士',
|
||||
value: '18',
|
||||
},
|
||||
{
|
||||
label: '暗黑骑士',
|
||||
value: '19',
|
||||
},
|
||||
{
|
||||
label: '占星术士',
|
||||
value: '20',
|
||||
},
|
||||
{
|
||||
label: '武士',
|
||||
value: '21',
|
||||
},
|
||||
{
|
||||
label: '赤魔法师',
|
||||
value: '22',
|
||||
},
|
||||
{
|
||||
label: '青魔法师',
|
||||
value: '23',
|
||||
},
|
||||
{
|
||||
label: '绝枪战士',
|
||||
value: '24',
|
||||
},
|
||||
{
|
||||
label: '舞者',
|
||||
value: '25',
|
||||
},
|
||||
{
|
||||
label: '钐镰客',
|
||||
value: '26',
|
||||
},
|
||||
{
|
||||
label: '贤者',
|
||||
value: '27',
|
||||
},
|
||||
{
|
||||
label: '猫魅族',
|
||||
value: '28',
|
||||
},
|
||||
{
|
||||
label: '拉拉菲尔族',
|
||||
value: '29',
|
||||
},
|
||||
{
|
||||
label: '人族',
|
||||
value: '30',
|
||||
},
|
||||
{
|
||||
label: '精灵族',
|
||||
value: '31',
|
||||
},
|
||||
{
|
||||
label: '维埃拉族',
|
||||
value: '32',
|
||||
},
|
||||
{
|
||||
label: '敖龙族',
|
||||
value: '59',
|
||||
},
|
||||
{
|
||||
label: '硌狮族',
|
||||
value: '60',
|
||||
},
|
||||
{
|
||||
label: '鲁加族',
|
||||
value: '61',
|
||||
},
|
||||
{
|
||||
label: '无人岛',
|
||||
value: '62',
|
||||
},
|
||||
{
|
||||
label: '特殊场景探索',
|
||||
value: '63',
|
||||
},
|
||||
{
|
||||
label: '游戏资讯',
|
||||
value: '64',
|
||||
},
|
||||
{
|
||||
label: '内容考据',
|
||||
value: '65',
|
||||
},
|
||||
{
|
||||
label: '摄影截图',
|
||||
value: '66',
|
||||
},
|
||||
{
|
||||
label: '金碟游乐场',
|
||||
value: '67',
|
||||
},
|
||||
{
|
||||
label: '综合',
|
||||
value: '68',
|
||||
},
|
||||
{
|
||||
label: '其他',
|
||||
value: '69',
|
||||
},
|
||||
{
|
||||
label: '国际服资讯翻译',
|
||||
value: '70',
|
||||
},
|
||||
{
|
||||
label: '游戏资讯整理',
|
||||
value: '71',
|
||||
},
|
||||
{
|
||||
label: '其他',
|
||||
value: '72',
|
||||
},
|
||||
];
|
||||
|
||||
export const POST_TYPE = {
|
||||
top: '置顶',
|
||||
refine: '精华',
|
||||
hot: '周热门',
|
||||
};
|
||||
|
||||
export enum DynamicSource {
|
||||
General = 1,
|
||||
Post = 2,
|
||||
Strat = 3,
|
||||
NoviceNetwork = 5,
|
||||
Duty = 6,
|
||||
FreeCompany = 7,
|
||||
RolePlay = 8,
|
||||
Other = 9,
|
||||
}
|
||||
|
||||
export enum NoviceNetworkIdentity {
|
||||
Mentor = 1,
|
||||
Sprout = 2,
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
import type { Route } from '@/types';
|
||||
import { INDEX_URL, LOGO_URL, POST_PART, POST_TYPE, REQUIRE_CONFIG } from './constant';
|
||||
import type { Context } from 'hono';
|
||||
import { checkConfig, generatePostFeeds } from './utils';
|
||||
import { getPosts } from './api';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/ff14risingstones/posts/:pid?/:type?',
|
||||
example: '/sdo/ff14risingstones/posts/all/hot',
|
||||
name: '帖子',
|
||||
categories: ['bbs'],
|
||||
maintainers: ['KarasuShin'],
|
||||
features: {
|
||||
requireConfig: REQUIRE_CONFIG,
|
||||
},
|
||||
parameters: {
|
||||
pid: {
|
||||
description: '分区id,默认显示所有分区,可通过 `,` 拼接多个分区 id 进行筛选',
|
||||
default: 'all',
|
||||
options: [
|
||||
{
|
||||
label: '全部',
|
||||
value: 'all',
|
||||
},
|
||||
...POST_PART,
|
||||
],
|
||||
},
|
||||
type: {
|
||||
description: '帖文类型,默认不做筛选',
|
||||
options: [
|
||||
{
|
||||
label: '置顶',
|
||||
value: 'top',
|
||||
},
|
||||
{
|
||||
label: '精华',
|
||||
value: 'refine',
|
||||
},
|
||||
{
|
||||
label: '周热门',
|
||||
value: 'hot',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
handler,
|
||||
};
|
||||
|
||||
async function handler(ctx: Context) {
|
||||
checkConfig();
|
||||
|
||||
const limit = ctx.req.query('limit') || 20;
|
||||
|
||||
const pid =
|
||||
ctx.req
|
||||
.param('pid')
|
||||
?.split(',')
|
||||
.filter((i) => POST_PART.some((part) => part.value === i)) ?? [];
|
||||
|
||||
const type = ctx.req.param('type');
|
||||
|
||||
const postPart = pid.length ? pid.map((i) => POST_PART.find((p) => p.value === i)?.label).join(',') : '';
|
||||
|
||||
const posts = await getPosts({
|
||||
type: 1,
|
||||
limit,
|
||||
order: 'latest',
|
||||
hotType: type === 'hot' ? 'postsHotWeek' : 'postsHotNow',
|
||||
is_refine: type === 'refine' ? 1 : 0,
|
||||
is_top: type === 'top' ? 1 : 0,
|
||||
part_id: pid.length ? pid.join(',') : undefined,
|
||||
});
|
||||
|
||||
return {
|
||||
title: `石之家 - ${POST_TYPE[type] ?? ''}帖文${postPart ? ` - ${postPart}` : ''}`,
|
||||
link: `${INDEX_URL}#/post`,
|
||||
image: LOGO_URL,
|
||||
item: await generatePostFeeds(posts),
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
import type { Route } from '@/types';
|
||||
import { INDEX_URL, LOGO_URL, POST_TYPE, REQUIRE_CONFIG, STRAT_PART } from './constant';
|
||||
import type { Context } from 'hono';
|
||||
import { checkConfig, generatePostFeeds } from './utils';
|
||||
import { getPosts } from './api';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/ff14risingstones/strats/:pid?/:type?',
|
||||
example: '/sdo/ff14risingstones/strats/1,2/refine',
|
||||
name: '攻略',
|
||||
categories: ['bbs'],
|
||||
maintainers: ['KarasuShin'],
|
||||
features: {
|
||||
requireConfig: REQUIRE_CONFIG,
|
||||
},
|
||||
parameters: {
|
||||
pid: {
|
||||
description: '分区id,默认显示所有分区,可通过 `,` 拼接多个分区 id 进行筛选',
|
||||
default: 'all',
|
||||
options: [
|
||||
{
|
||||
label: '全部',
|
||||
value: 'all',
|
||||
},
|
||||
...STRAT_PART,
|
||||
],
|
||||
},
|
||||
type: {
|
||||
description: '攻略类型,默认不做筛选',
|
||||
options: [
|
||||
{
|
||||
label: '置顶',
|
||||
value: 'top',
|
||||
},
|
||||
{
|
||||
label: '精华',
|
||||
value: 'refine',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
handler,
|
||||
};
|
||||
|
||||
async function handler(ctx: Context) {
|
||||
checkConfig();
|
||||
|
||||
const limit = ctx.req.query('limit') || 20;
|
||||
|
||||
const pid =
|
||||
ctx.req
|
||||
.param('pid')
|
||||
?.split(',')
|
||||
.filter((i) => STRAT_PART.some((part) => part.value === i)) ?? [];
|
||||
|
||||
const type = ctx.req.param('type');
|
||||
|
||||
const stratPart = pid.length ? pid.map((i) => STRAT_PART.find((p) => p.value === i)?.label).join(',') : '';
|
||||
|
||||
const posts = await getPosts({
|
||||
type: 2,
|
||||
limit,
|
||||
order: 'latest',
|
||||
is_refine: type === 'refine' ? 1 : 0,
|
||||
is_top: type === 'top' ? 1 : 0,
|
||||
part_id: pid.length ? pid.join(',') : undefined,
|
||||
});
|
||||
|
||||
return {
|
||||
title: `石之家 - ${POST_TYPE[type] ?? ''}攻略${stratPart ? ` - ${stratPart}` : ''}`,
|
||||
link: `${INDEX_URL}#/strat`,
|
||||
image: LOGO_URL,
|
||||
item: await generatePostFeeds(posts),
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<div>
|
||||
<p>进度:{{ progress }}</p>
|
||||
<p>攻略:{{ strategy }}</p>
|
||||
<p>时间:{{ fb_time }}</p>
|
||||
<p>标签:{{ labelInfo.map(i => i.name).join(',') }}</p>
|
||||
{{ if team_composition === '团队' }}
|
||||
<p>队伍构成</P>
|
||||
<p>A队:{{ team_position.A }}</p>
|
||||
<p>B队:{{ team_position.B }}</p>
|
||||
<p>C队:{{ team_position.C }}</p>
|
||||
{{ else if team_composition === '满编小队' }}
|
||||
<p>队伍构成</p>
|
||||
<p>MT:{{ MT }}</p>
|
||||
<p>ST:{{ ST }}</p>
|
||||
<p>H1:{{ H1 }}</p>
|
||||
<p>H2:{{ H2 }}</p>
|
||||
<p>D1:{{ D1 }}</p>
|
||||
<p>D2:{{ D2 }}</p>
|
||||
<p>D3:{{ D3 }}</p>
|
||||
<p>D4:{{ D4 }}</p>
|
||||
{{ else if team_composition === '轻锐小队' }}
|
||||
<p>队伍构成</p>
|
||||
<p>T:{{ T }}</p>
|
||||
<p>H:{{ H }}</p>
|
||||
<p>D1:{{ D1 }}</p>
|
||||
<p>D2:{{ D2 }}</p>
|
||||
{{ /if }}
|
||||
|
||||
{{ if need_job }}
|
||||
<p>需求职业:{{ need_job }}</P>
|
||||
{{ /if }}
|
||||
{{ if team_detail_mask }}
|
||||
<p>队伍详情:{{ team_detail_mask }}</p>
|
||||
{{ /if }}
|
||||
{{ if recruit_require_mask }}
|
||||
<p>招募要求:{{ recruit_require_mask }}</p>
|
||||
{{ /if }}
|
||||
{{ if strategy_desc_mask }}
|
||||
<p>攻略说明:{{ strategy_desc_mask }}</p>
|
||||
{{ /if }}
|
||||
</div>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
{{ if cover_pic }}
|
||||
<img src="{{ cover_pic }}" />
|
||||
{{ /if }}
|
||||
|
||||
<p>部队名称:{{ guild_name }} <{{ guild_tag }}></p>
|
||||
<p>区服:{{ area_name }} {{ group_name }}</p>
|
||||
<p>活跃成员:{{ active_member_num }}</p>
|
||||
<p>招募人数:{{ target_recruit_num }}</p>
|
||||
<p>活跃时间:工作日 {{ weekday_time }} 休息日 {{ weekend_time }}</p>
|
||||
{{ if guild_address }}
|
||||
<p>部队地址:{{ guild_address }}</p>
|
||||
{{ /if }}
|
||||
{{ if create_time }}
|
||||
<p>成立时间:{{ create_time }}</p>
|
||||
{{ /if }}
|
||||
<p>部队标签:{{ labelInfo.map(i => i.name).join(',') }}</p>
|
||||
|
||||
<div>
|
||||
{{@ detail_mask }}
|
||||
</div>
|
||||
|
||||
{{ if foot_pic }}
|
||||
{{ each foot_pic.split(',') url }}
|
||||
<img src="{{ url }}" />
|
||||
{{ /each }}
|
||||
{{ /if }}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{{@ detail_mask }}
|
||||
|
||||
<div>
|
||||
{{ if weekday_time && weekend_time }}
|
||||
<p>活跃时间:工作日 {{ weekday_time }} 休息日 {{ weekend_time }}</p>
|
||||
{{ /if }}
|
||||
<p>游戏风格:{{ styles }}</p>
|
||||
<p>招募区服:{{ target }}</p>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{{ if cover_pic }}
|
||||
<img src="{{ cover_pic }}" />
|
||||
{{ /if }}
|
||||
|
||||
<p>开放时间:{{ open_time }}</p>
|
||||
<p>RP 类型:{{ rp_type }}</p>
|
||||
<p>创立时间:{{ create_time }}</p>
|
||||
<p>区服:{{ area }}</p>
|
||||
<p>地址:{{ address }}</p>
|
||||
<p>标签:{{ custom_label }}</p>
|
||||
<p>简介:{{ profile }}</p>
|
||||
|
||||
<div>
|
||||
{{@ detail_mask }}
|
||||
</div>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import type { Data, Route } from '@/types';
|
||||
import { INDEX_URL, LOGO_URL, REQUIRE_CONFIG } from './constant';
|
||||
import type { Context } from 'hono';
|
||||
import { checkConfig, generateDynamicFeeds } from './utils';
|
||||
import { getFollowDynamicList } from './api';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/ff14risingstones/timeline',
|
||||
example: '/sdo/ff14risingstones/timeline',
|
||||
name: '时间线',
|
||||
categories: ['bbs'],
|
||||
maintainers: ['KarasuShin'],
|
||||
features: {
|
||||
requireConfig: REQUIRE_CONFIG,
|
||||
},
|
||||
handler,
|
||||
};
|
||||
|
||||
async function handler(ctx: Context) {
|
||||
checkConfig();
|
||||
|
||||
const limit = ctx.req.query('limit') || 20;
|
||||
const dynamics = await getFollowDynamicList(limit);
|
||||
|
||||
return {
|
||||
title: '石之家 - 时间线',
|
||||
link: `${INDEX_URL}#/dynamic`,
|
||||
image: LOGO_URL,
|
||||
item: await generateDynamicFeeds(dynamics),
|
||||
} as Data;
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import type { DynamicSource } from '../constant';
|
||||
import type { DateTimeFormat, UserPost } from './other';
|
||||
import type { DutiesParty, NoviceNetworkParty, FreeCompanyParty, OtherParty, RolePlayParty } from './party';
|
||||
|
||||
interface BaseUserDynamic {
|
||||
character_name: string;
|
||||
area_name: string;
|
||||
group_name: string;
|
||||
created_at: DateTimeFormat;
|
||||
from: DynamicSource;
|
||||
from_id: string;
|
||||
id: number;
|
||||
mask_content: string;
|
||||
}
|
||||
|
||||
export interface GeneralDynamic extends BaseUserDynamic {
|
||||
from: DynamicSource.General;
|
||||
}
|
||||
|
||||
export interface PostDynamic extends BaseUserDynamic {
|
||||
from: DynamicSource.Post | DynamicSource.Strat;
|
||||
from_info?: UserPost;
|
||||
}
|
||||
|
||||
export interface NoviceNetworkRecruitDynamic extends BaseUserDynamic {
|
||||
from: DynamicSource.NoviceNetwork;
|
||||
from_info?: Omit<NoviceNetworkParty, 'styleInfo' | 'weekday_time' | 'weekend_time'>;
|
||||
}
|
||||
|
||||
export interface DutiesRecruitDynamic extends BaseUserDynamic {
|
||||
from: DynamicSource.Duty;
|
||||
from_info?: DutiesParty;
|
||||
}
|
||||
|
||||
export interface FreeCompanyRecruitDynamic extends BaseUserDynamic {
|
||||
from: DynamicSource.FreeCompany;
|
||||
from_info?: FreeCompanyParty;
|
||||
}
|
||||
|
||||
export interface RolePlayRecruitDynamic extends BaseUserDynamic {
|
||||
from: DynamicSource.RolePlay;
|
||||
from_info?: RolePlayParty;
|
||||
}
|
||||
|
||||
export interface OtherRecruitDynamic extends BaseUserDynamic {
|
||||
from: DynamicSource.Other;
|
||||
from_info?: OtherParty;
|
||||
}
|
||||
|
||||
export type UserDynamic = GeneralDynamic | PostDynamic | NoviceNetworkRecruitDynamic | FreeCompanyRecruitDynamic | RolePlayRecruitDynamic | DutiesRecruitDynamic | OtherRecruitDynamic;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export type * from './dynamic';
|
||||
export type * from './other';
|
||||
export type * from './party';
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
export type TimeStamp = `${number}`;
|
||||
|
||||
export type DateFormat = `${number}-${number}-${number}`;
|
||||
|
||||
export type DateTimeFormat = `${number}-${number}-${number} ${number}:${number}:${number}`;
|
||||
|
||||
export type BaseResponse<T = any> = {
|
||||
code: number;
|
||||
data: T;
|
||||
msg: string;
|
||||
};
|
||||
|
||||
export interface Resently {
|
||||
detail: string;
|
||||
event_type: string;
|
||||
event_type_id: string;
|
||||
log_time: DateTimeFormat;
|
||||
part_date: DateFormat;
|
||||
}
|
||||
|
||||
export interface UserInfo {
|
||||
avatar: string;
|
||||
character_name: string;
|
||||
group_name: string;
|
||||
}
|
||||
|
||||
export interface UserPost {
|
||||
created_at: DateTimeFormat;
|
||||
posts_id: string;
|
||||
part_name: string;
|
||||
title: string;
|
||||
character_name: string;
|
||||
group_name: string;
|
||||
area_name: string;
|
||||
}
|
||||
|
||||
export interface PostDetail {
|
||||
contentInfo: {
|
||||
content: string;
|
||||
created_at: DateTimeFormat;
|
||||
id: string;
|
||||
posts_id: string;
|
||||
};
|
||||
created_at: DateTimeFormat;
|
||||
updated_at: DateTimeFormat;
|
||||
}
|
||||
|
||||
export interface TeamPosition {
|
||||
D1: string;
|
||||
D2: string;
|
||||
D3: string;
|
||||
D4: string;
|
||||
H1: string;
|
||||
H2: string;
|
||||
MT: string;
|
||||
ST: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
import type { NoviceNetworkIdentity } from '../constant';
|
||||
import type { DateFormat, DateTimeFormat, TeamPosition, TimeStamp } from './other';
|
||||
|
||||
export interface BaseParty {
|
||||
area_name: string;
|
||||
begin_time: TimeStamp;
|
||||
character_name: string;
|
||||
end_time: TimeStamp;
|
||||
group_name: string;
|
||||
id: number;
|
||||
status: number; // 1: 招募中, !1: 已结束
|
||||
target_area_name: string;
|
||||
target_group_name: string | null;
|
||||
}
|
||||
|
||||
// 副本招募
|
||||
export interface DutiesParty extends BaseParty {
|
||||
D1: number;
|
||||
D2: number;
|
||||
D3: number;
|
||||
D4: number;
|
||||
H: number;
|
||||
H1: number;
|
||||
H2: number;
|
||||
MT: number;
|
||||
ST: number;
|
||||
T: number;
|
||||
fb_name: string;
|
||||
fb_time: string;
|
||||
fb_type: string;
|
||||
labelInfo: {
|
||||
name: string;
|
||||
}[];
|
||||
progress: string;
|
||||
strategy: string;
|
||||
team_composition: string;
|
||||
team_position: {
|
||||
A: TeamPosition;
|
||||
B: TeamPosition;
|
||||
C: TeamPosition;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface DutiesPartyDetail extends DutiesParty {
|
||||
team_detail_mask: string | null;
|
||||
recruit_require_mask: string | null;
|
||||
strategy_desc_mask: string | null;
|
||||
updated_at: DateTimeFormat | null;
|
||||
need_job: string[];
|
||||
}
|
||||
|
||||
// 导芽招募
|
||||
export interface NoviceNetworkParty extends BaseParty {
|
||||
identity: NoviceNetworkIdentity; // 1: 导师, 2: 豆芽
|
||||
title: string;
|
||||
weekday_time: string;
|
||||
weekend_time: string;
|
||||
style: string[];
|
||||
styleInfo: { style: string; pic_url: string }[];
|
||||
detail_mask: string;
|
||||
}
|
||||
|
||||
// 角色扮演招募
|
||||
export interface RolePlayParty extends BaseParty {
|
||||
address: string;
|
||||
custom_label: string;
|
||||
detail_mask: string;
|
||||
open_time: string;
|
||||
profile: string;
|
||||
rp_area_name: string;
|
||||
rp_group_name: string;
|
||||
rp_name: string;
|
||||
rp_type: ('0' | '1' | '2' | '3')[]; // 0: 无 RP 元素, 1: 轻 RP 元素, 2: 中 RP 元素, 3: 重 RP 元素
|
||||
create_time: DateFormat;
|
||||
cover_pic: string;
|
||||
}
|
||||
|
||||
export interface RolePlayPartyDetail extends RolePlayParty {
|
||||
updated_at: DateTimeFormat | null;
|
||||
}
|
||||
|
||||
// 部队招募
|
||||
export interface FreeCompanyParty extends BaseParty {
|
||||
created_at: TimeStamp;
|
||||
guild_id: string;
|
||||
guild_name: string;
|
||||
guild_tag: string;
|
||||
labelInfo: {
|
||||
name: string;
|
||||
}[];
|
||||
detail_mask: string;
|
||||
active_member_num: number;
|
||||
target_recruit_num: number;
|
||||
cover_pic: string;
|
||||
weekday_time: string;
|
||||
weekend_time: string;
|
||||
}
|
||||
|
||||
export interface FreeCompanyPartyDetail extends FreeCompanyParty {
|
||||
updated_at: DateTimeFormat | null;
|
||||
create_time: DateTimeFormat | null;
|
||||
guild_address: string | null;
|
||||
foot_pic: string | null;
|
||||
}
|
||||
|
||||
// 其它招募
|
||||
export interface OtherParty extends BaseParty {
|
||||
category_name: string;
|
||||
title: string;
|
||||
detail_mask: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import type { Data, Route } from '@/types';
|
||||
import { INDEX_URL, REQUIRE_CONFIG } from './constant';
|
||||
import type { Context } from 'hono';
|
||||
import { checkConfig, generateDynamicFeeds } from './utils';
|
||||
import { getUserDynamic, getUserInfo } from './api';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/ff14risingstones/user-dynamics/:uid',
|
||||
example: '/sdo/ff14risingstones/user-dynamics/10001226',
|
||||
name: '用户动态',
|
||||
categories: ['bbs'],
|
||||
maintainers: ['KarasuShin'],
|
||||
features: {
|
||||
requireConfig: REQUIRE_CONFIG,
|
||||
},
|
||||
handler,
|
||||
};
|
||||
|
||||
async function handler(ctx: Context) {
|
||||
checkConfig();
|
||||
|
||||
const uid = ctx.req.param('uid');
|
||||
|
||||
const [dynamics, userInfo] = await Promise.all([getUserDynamic(uid), getUserInfo(uid)]);
|
||||
|
||||
return {
|
||||
title: `石之家 - ${userInfo.character_name}@${userInfo.group_name} 的动态`,
|
||||
link: `${INDEX_URL}#/me/dynamics?uuid=${uid}`,
|
||||
image: userInfo.avatar,
|
||||
item: await generateDynamicFeeds(dynamics),
|
||||
} as Data;
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import type { Data, Route } from '@/types';
|
||||
import { INDEX_URL, REQUIRE_CONFIG } from './constant';
|
||||
import type { Context } from 'hono';
|
||||
import { checkConfig, generatePostFeeds } from './utils';
|
||||
import { getUserInfo, getUserPosts } from './api';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/ff14risingstones/user-posts/:uid',
|
||||
example: '/sdo/ff14risingstones/user-posts/10001226',
|
||||
name: '用户发帖',
|
||||
categories: ['bbs'],
|
||||
maintainers: ['KarasuShin'],
|
||||
features: {
|
||||
requireConfig: REQUIRE_CONFIG,
|
||||
},
|
||||
handler,
|
||||
};
|
||||
|
||||
async function handler(ctx: Context) {
|
||||
checkConfig();
|
||||
|
||||
const uid = ctx.req.param('uid');
|
||||
|
||||
const [posts, userInfo] = await Promise.all([getUserPosts(uid, 1), getUserInfo(uid)]);
|
||||
|
||||
return {
|
||||
title: `石之家 - ${userInfo.character_name}@${userInfo.group_name} 发布的帖子`,
|
||||
link: `${INDEX_URL}#/me/posts?uuid=${uid}`,
|
||||
image: userInfo.avatar,
|
||||
item: await generatePostFeeds(posts),
|
||||
} as Data;
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import type { Data, Route } from '@/types';
|
||||
import { INDEX_URL, REQUIRE_CONFIG } from './constant';
|
||||
import type { Context } from 'hono';
|
||||
import { checkConfig } from './utils';
|
||||
import { getResently, getUserInfo } from './api';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import timezone from '@/utils/timezone';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/ff14risingstones/user-resently/:uid',
|
||||
categories: ['bbs'],
|
||||
example: '/sdo/ff14risingstones/user-resently/10008214',
|
||||
name: '游戏近况',
|
||||
maintainers: ['KarasuShin'],
|
||||
features: {
|
||||
requireConfig: REQUIRE_CONFIG,
|
||||
},
|
||||
handler,
|
||||
};
|
||||
|
||||
async function handler(ctx: Context) {
|
||||
checkConfig();
|
||||
|
||||
const uid = ctx.req.param('uid');
|
||||
|
||||
const [resently, userInfo] = await Promise.all([getResently(uid), getUserInfo(uid)]);
|
||||
|
||||
return {
|
||||
title: `石之家 - ${userInfo.character_name}@${userInfo.group_name} 的游戏近况`,
|
||||
link: `${INDEX_URL}#/me/info?uuid=${uid}`,
|
||||
image: userInfo.avatar,
|
||||
item: resently.map((i) => ({
|
||||
title: `${i.event_type} - ${i.detail}`,
|
||||
pubDate: timezone(parseDate(i.log_time), +8),
|
||||
guid: `sdo/ff14risingstones/resently:${uid}-${i.detail}`,
|
||||
})),
|
||||
} as Data;
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import type { Data, Route } from '@/types';
|
||||
import { INDEX_URL, REQUIRE_CONFIG } from './constant';
|
||||
import type { Context } from 'hono';
|
||||
import { checkConfig, generatePostFeeds } from './utils';
|
||||
import { getUserInfo, getUserPosts } from './api';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/ff14risingstones/user-strats/:uid',
|
||||
example: '/sdo/ff14risingstones/user-strats/10001226',
|
||||
name: '用户攻略',
|
||||
categories: ['bbs'],
|
||||
maintainers: ['KarasuShin'],
|
||||
features: {
|
||||
requireConfig: REQUIRE_CONFIG,
|
||||
},
|
||||
handler,
|
||||
};
|
||||
|
||||
async function handler(ctx: Context) {
|
||||
checkConfig();
|
||||
|
||||
const uid = ctx.req.param('uid');
|
||||
|
||||
const [posts, userInfo] = await Promise.all([getUserPosts(uid, 2), getUserInfo(uid)]);
|
||||
|
||||
return {
|
||||
title: `石之家 - ${userInfo.character_name}@${userInfo.group_name} 发布的攻略`,
|
||||
link: `${INDEX_URL}#/me/posts?uuid=${uid}`,
|
||||
image: userInfo.avatar,
|
||||
item: await generatePostFeeds(posts),
|
||||
} as Data;
|
||||
}
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
import { config } from '@/config';
|
||||
import ConfigNotFoundError from '@/errors/types/config-not-found';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import type { BaseResponse, DutiesPartyDetail, FreeCompanyPartyDetail, NoviceNetworkParty, PostDetail, UserDynamic, UserPost } from './types';
|
||||
import { DataItem } from '@/types';
|
||||
import { DynamicSource, INDEX_URL, JOB, NoviceNetworkIdentity, PLAY_STYLE } from './constant';
|
||||
import { getDutiesRecruitDetail, getFreeCompanyRecruitDetail, getNoviceNetworkRecruitDetail, getPostsDetail } from './api';
|
||||
import timezone from '@/utils/timezone';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { art } from '@/utils/render';
|
||||
import path from 'node:path';
|
||||
|
||||
export function checkConfig() {
|
||||
if (!config.sdo.ff14risingstones || !config.sdo.ua) {
|
||||
throw new ConfigNotFoundError('ff14risingstones RSS is disabled due to the lack of relevant config');
|
||||
}
|
||||
}
|
||||
|
||||
export function request(url: string, options?: RequestInit) {
|
||||
return ofetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
Cookie: `ff14risingstones=${config.sdo.ff14risingstones}`,
|
||||
'User-Agent': config.sdo.ua!,
|
||||
...options?.headers,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function requestAPI<T = any>(url: string, options?: RequestInit) {
|
||||
const response = (await request(url, options)) as BaseResponse<T>;
|
||||
|
||||
if (response.code !== 10000) {
|
||||
throw new Error(response.msg);
|
||||
}
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function generatePostFeeds(posts: UserPost[]) {
|
||||
return await Promise.all(
|
||||
posts.map(async (post) => {
|
||||
const detail = await getPostsDetail(post.posts_id);
|
||||
return {
|
||||
title: `[${post.part_name}] ${post.title}`,
|
||||
link: `${INDEX_URL}#/post/detail/${post.posts_id}`,
|
||||
description: detail?.contentInfo.content,
|
||||
pubDate: timezone(parseDate(post.created_at), +8),
|
||||
updated: detail?.updated_at ? timezone(parseDate(detail.updated_at), +8) : undefined,
|
||||
guid: `sdo/ff14risingstones/posts:${post.posts_id}`,
|
||||
author: `${post.character_name}@${post.group_name}`,
|
||||
} as DataItem;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateDynamicFeeds(dynamics: UserDynamic[]) {
|
||||
return await Promise.all(
|
||||
dynamics.map(async (dynamic) => {
|
||||
let title = `${dynamic.character_name}@${dynamic.group_name} ${dynamic.mask_content}`;
|
||||
let link: string | undefined;
|
||||
let description: string | undefined;
|
||||
let detail: PostDetail | DutiesPartyDetail | FreeCompanyPartyDetail | NoviceNetworkParty | null = null;
|
||||
|
||||
switch (dynamic.from) {
|
||||
case DynamicSource.Post:
|
||||
case DynamicSource.Strat:
|
||||
if (!dynamic.from_info) {
|
||||
break;
|
||||
}
|
||||
title += dynamic.from_info.title;
|
||||
link = `${INDEX_URL}#/post/detail/${dynamic.from_info.posts_id}`;
|
||||
detail = await getPostsDetail(dynamic.from_info.posts_id);
|
||||
description = detail?.contentInfo.content;
|
||||
break;
|
||||
|
||||
case DynamicSource.NoviceNetwork:
|
||||
if (!dynamic.from_info) {
|
||||
break;
|
||||
}
|
||||
title += `[找${dynamic.from_info.identity === NoviceNetworkIdentity.Mentor ? '豆芽' : '导师'}] ${dynamic.from_info.title}`;
|
||||
link = `${INDEX_URL}#/recruit/beginner?id=${dynamic.from_info.id}`;
|
||||
detail = await getNoviceNetworkRecruitDetail(dynamic.from_info.id);
|
||||
description = art(path.join(__dirname, 'templates/novice-network-party.art'), {
|
||||
detail_mask: dynamic.from_info.detail_mask,
|
||||
styles: dynamic.from_info.style.map((i) => PLAY_STYLE[i]).join(','),
|
||||
target: `${dynamic.from_info.target_area_name} ${dynamic.from_info.target_group_name ?? '全区'}`,
|
||||
weekday_time: detail?.weekday_time,
|
||||
weekend_time: detail?.weekend_time,
|
||||
});
|
||||
break;
|
||||
|
||||
case DynamicSource.Duty:
|
||||
if (!dynamic.from_info) {
|
||||
break;
|
||||
}
|
||||
title += `[${dynamic.from_info.fb_type}] ${dynamic.from_info.fb_name}`;
|
||||
link = `${INDEX_URL}#/recruit/party?id=${dynamic.from_info.id}`;
|
||||
detail = await getDutiesRecruitDetail(dynamic.from_info.id);
|
||||
|
||||
description = art(path.join(__dirname, 'templates/duties-party.art'), {
|
||||
progress: dynamic.from_info.progress,
|
||||
strategy: dynamic.from_info.strategy,
|
||||
fb_name: dynamic.from_info.fb_name,
|
||||
fb_time: dynamic.from_info.fb_time,
|
||||
labelInfo: dynamic.from_info.labelInfo,
|
||||
team_composition: dynamic.from_info.team_composition,
|
||||
team_position: dynamic.from_info.team_position
|
||||
? {
|
||||
A: Object.keys(dynamic.from_info.team_position.A)
|
||||
.filter((i) => dynamic.from_info!.team_position!.A[i] !== '0')
|
||||
.map((i) => `${i}: ${JOB[dynamic.from_info!.team_position!.A[i]]}`)
|
||||
.join(','),
|
||||
B: Object.keys(dynamic.from_info.team_position.B)
|
||||
.filter((i) => dynamic.from_info!.team_position!.B[i] !== '0')
|
||||
.map((i) => `${i}: ${JOB[dynamic.from_info!.team_position!.B[i]]}`)
|
||||
.join(','),
|
||||
C: Object.keys(dynamic.from_info.team_position.C)
|
||||
.filter((i) => dynamic.from_info!.team_position!.C[i] !== '0')
|
||||
.map((i) => `${i}: ${JOB[dynamic.from_info!.team_position!.C[i]]}`)
|
||||
.join(','),
|
||||
}
|
||||
: null,
|
||||
MT: JOB[dynamic.from_info.MT],
|
||||
ST: JOB[dynamic.from_info.ST],
|
||||
T: JOB[dynamic.from_info.T],
|
||||
H: JOB[dynamic.from_info.H],
|
||||
H1: JOB[dynamic.from_info.H1],
|
||||
H2: JOB[dynamic.from_info.H2],
|
||||
D1: JOB[dynamic.from_info.D1],
|
||||
D2: JOB[dynamic.from_info.D2],
|
||||
D3: JOB[dynamic.from_info.D3],
|
||||
D4: JOB[dynamic.from_info.D4],
|
||||
need_job: detail?.need_job.map((i) => JOB[i]).join(','),
|
||||
team_detail_mask: detail?.team_detail_mask,
|
||||
recruit_require_mask: detail?.recruit_require_mask,
|
||||
strategy_desc_mask: detail?.strategy_desc_mask,
|
||||
});
|
||||
break;
|
||||
|
||||
case DynamicSource.FreeCompany:
|
||||
if (!dynamic.from_info) {
|
||||
break;
|
||||
}
|
||||
title += `[部队招待] ${dynamic.from_info.guild_name} <${dynamic.from_info.guild_tag}>`;
|
||||
link = `${INDEX_URL}#/recruit/guild/detail/${dynamic.from_info.id}`;
|
||||
detail = await getFreeCompanyRecruitDetail(dynamic.from_info.id);
|
||||
|
||||
description = art(path.join(__dirname, 'templates/fc-party.art'), {
|
||||
cover_pic: dynamic.from_info.cover_pic,
|
||||
guild_name: dynamic.from_info.guild_name,
|
||||
guild_tag: dynamic.from_info.guild_tag,
|
||||
labelInfo: dynamic.from_info.labelInfo,
|
||||
area_name: dynamic.from_info.target_area_name,
|
||||
group_name: dynamic.from_info.target_group_name,
|
||||
active_member_num: dynamic.from_info.active_member_num,
|
||||
target_recruit_num: dynamic.from_info.target_recruit_num,
|
||||
weekday_time: dynamic.from_info.weekday_time,
|
||||
weekend_time: dynamic.from_info.weekend_time,
|
||||
guild_address: detail?.guild_address ?? '',
|
||||
create_time: detail?.create_time ?? '',
|
||||
foot_pic: detail?.foot_pic ?? '',
|
||||
detail_mask: dynamic.from_info.detail_mask,
|
||||
});
|
||||
break;
|
||||
|
||||
case DynamicSource.RolePlay:
|
||||
if (!dynamic.from_info) {
|
||||
break;
|
||||
}
|
||||
title += dynamic.from_info.rp_name;
|
||||
link = `${INDEX_URL}#/recruit/roleplay/detail/${dynamic.from_info.id}`;
|
||||
description = art(path.join(__dirname, 'templates/rp-party.art'), {
|
||||
cover_pic: dynamic.from_info.cover_pic,
|
||||
open_time: dynamic.from_info.open_time,
|
||||
rp_type: `${dynamic.from_info.rp_type
|
||||
.map(
|
||||
(i) =>
|
||||
({
|
||||
'0': '无',
|
||||
'1': '轻',
|
||||
'2': '中',
|
||||
'3': '重',
|
||||
})[i]
|
||||
)
|
||||
.join('/')}RP 元素`,
|
||||
create_time: dynamic.from_info.create_time,
|
||||
area: `${dynamic.from_info.rp_area_name} ${dynamic.from_info.rp_group_name}`,
|
||||
address: dynamic.from_info.address,
|
||||
custom_label: dynamic.from_info.custom_label,
|
||||
profile: dynamic.from_info.profile,
|
||||
detail_mask: dynamic.from_info.detail_mask,
|
||||
});
|
||||
break;
|
||||
case DynamicSource.Other:
|
||||
if (!dynamic.from_info) {
|
||||
break;
|
||||
}
|
||||
title += `[${dynamic.from_info.category_name}] ${dynamic.from_info.title}`;
|
||||
link = `${INDEX_URL}#/recruit/others?id=${dynamic.from_info.id}`;
|
||||
description = dynamic.from_info.detail_mask;
|
||||
break;
|
||||
default:
|
||||
// do nothing
|
||||
}
|
||||
return {
|
||||
title,
|
||||
link,
|
||||
pubDate: timezone(parseDate(dynamic.created_at), +8),
|
||||
guid: `sdo/ff14risingstones/dynamics:${dynamic.id}`,
|
||||
author: `${dynamic.character_name}@${dynamic.group_name}`,
|
||||
description,
|
||||
} as DataItem;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import type { Namespace } from '@/types';
|
||||
|
||||
export const namespace: Namespace = {
|
||||
name: '盛趣游戏在线',
|
||||
url: 'sdo.com',
|
||||
lang: 'zh-CN',
|
||||
};
|
||||
Loading…
Reference in New Issue