feat(route): Add GanjingWorld (#20086)
* Created route for channel videos * chore: updated route name * feat: add short video route * Added Article Route * Added Posts Route * chore: change file name * chore: reorder folder structure * Update lib/routes/ganjingworld/namespace.ts Co-authored-by: Tony <TonyRL@users.noreply.github.com> * fix(route/ganjingworld): fix target url; fix repeated name, fix redundant parsing, fix video poster attribute, fix naming convention * fix(route/ganjingworld) fix examples * fix(route/ganjingworld) fix optional empty string * chore(route/ganjingworld): fix example link * style: auto format * New files from Fly.io Launch * Revert "New files from Fly.io Launch" This reverts commit 71d9f42f5f0073f35bc34ac4e9417e4a4a1d94fc. * Fix: resolved requested changes * fix: implement suggested changes * fix: fix variable names * fix: update error messages and improve data handling in Ganjing World routes --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Fly.io <noreply@fly.io> Co-authored-by: fly-io[bot] <52462049+fly-io[bot]@users.noreply.github.com>
This commit is contained in:
parent
6d8e143916
commit
dd5e1ffcf8
|
|
@ -0,0 +1,73 @@
|
|||
// RSSHub route for fetching articles from Ganjing World.
|
||||
// Returns a list of articles in a channel.
|
||||
// Source: https://www.ganjingworld.com
|
||||
|
||||
import type { Route } from '@/types';
|
||||
import cache from '@/utils/cache';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
|
||||
import type { ApiResponse } from '../interfaces/api';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/channel/articles/:id',
|
||||
categories: ['social-media'],
|
||||
features: {
|
||||
requireConfig: false,
|
||||
requirePuppeteer: false,
|
||||
antiCrawler: false,
|
||||
supportBT: false,
|
||||
supportPodcast: false,
|
||||
supportScihub: false,
|
||||
},
|
||||
example: '/ganjingworld/channel/articles/1fcahpcut9t3gz4zIvYSJR7qd1cs0c',
|
||||
parameters: { id: 'Channel ID, can be found in channel url' },
|
||||
radar: [
|
||||
{
|
||||
source: ['ganjingworld.com'],
|
||||
target: '/channel/articles/:id',
|
||||
},
|
||||
],
|
||||
url: 'www.ganjingworld.com',
|
||||
name: 'Articles in a channel',
|
||||
maintainers: ['yixiangli2001'],
|
||||
|
||||
handler,
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const id = ctx.req.param('id');
|
||||
const url = `https://www.ganjingworld.com/channel/${id}?tab=articles`;
|
||||
const apiUrl = `https://gw.ganjingworld.com/v1.1/content/get-by-channel?channel_id=${id}&content_type=News`;
|
||||
// const apiUrl = `https://gw.ganjingworld.com/v1.1/content/get-by-channel?channel_id=1fcahpcut9t3gz4zIvYSJR7qd1cs0c&content_type=News`;
|
||||
|
||||
const parsed: ApiResponse = await ofetch<ApiResponse>(apiUrl);
|
||||
if (parsed.data.list.length === 0) {
|
||||
throw new Error('No articles found for this channel. Please make sure the channel ID is correct and that the channel contains articles.');
|
||||
}
|
||||
const title = parsed.data.list[0].channel.name;
|
||||
const items = await Promise.all(
|
||||
parsed.data.list.map((item) =>
|
||||
cache.tryGet(item.id, async () => {
|
||||
const pubDate = new Date(item.time_scheduled);
|
||||
const fetchArticleUrl = `https://gw.ganjingworld.com/v1.1/content/query?lang=zh-TW&query=basic%2Cfull%2Ctranslations%2Clike%2Cshare%2Csave%2Cview%2Ctag_list&ids=${item.id}`;
|
||||
const parsedArticle: ApiResponse = await ofetch<ApiResponse>(fetchArticleUrl);
|
||||
|
||||
const description = parsedArticle.data.list[0]?.text ?? '';
|
||||
|
||||
return {
|
||||
title: item.title,
|
||||
link: `https://www.ganjingworld.com/news/${item.id}`,
|
||||
pubDate,
|
||||
description,
|
||||
};
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
title,
|
||||
link: url,
|
||||
description: `Articles from Ganjing World Channel ${title}`,
|
||||
item: items,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
// RSSHub route for fetching posts from Ganjing World.
|
||||
// Returns a list of posts in a channel.
|
||||
// Source: https://www.ganjingworld.com
|
||||
|
||||
import type { Route } from '@/types';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
|
||||
import type { ApiResponse } from '../interfaces/api';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/channel/posts/:id',
|
||||
categories: ['social-media'],
|
||||
features: {
|
||||
requireConfig: false,
|
||||
requirePuppeteer: false,
|
||||
antiCrawler: false,
|
||||
supportBT: false,
|
||||
supportPodcast: false,
|
||||
supportScihub: false,
|
||||
},
|
||||
example: '/ganjingworld/channel/posts/1fcahpcut9t3gz4zIvYSJR7qd1cs0c',
|
||||
parameters: { id: 'Channel ID, can be found in channel url' },
|
||||
radar: [
|
||||
{
|
||||
source: ['ganjingworld.com'],
|
||||
target: '/channel/posts/:id',
|
||||
},
|
||||
],
|
||||
url: 'www.ganjingworld.com',
|
||||
name: 'posts in a channel',
|
||||
maintainers: ['yixiangli2001'],
|
||||
|
||||
handler,
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const id = ctx.req.param('id');
|
||||
const url = `https://www.ganjingworld.com/channel/${id}?tab=posts`;
|
||||
const apiUrl = `https://gw.ganjingworld.com/v1.0c/social-content/get-owner-posts?owner_id=${id}&start_key=&page_size=48&post_image=true&query=basic,post,owner,comment,view,like,is_liked,share `;
|
||||
// const apiUrl = `https://gw.ganjingworld.com/v1.0c/social-content/get-owner-posts?owner_id=1fcahpcut9t3gz4zIvYSJR7qd1cs0c&start_key=&page_size=48&post_image=true&query=basic,post,owner,comment,view,like,is_liked,share%20Request%20Method `;
|
||||
|
||||
const parsed: ApiResponse = await ofetch<ApiResponse>(apiUrl);
|
||||
if (parsed.data.list.length === 0) {
|
||||
throw new Error('No posts found for this channel. Please make sure the channel ID is correct and that the channel contains posts.');
|
||||
}
|
||||
const title = parsed.data.list[0].channel.name;
|
||||
const items = parsed.data.list.map((item) => {
|
||||
const pubDate = new Date(item.time_scheduled);
|
||||
|
||||
const raw = item.text?.replaceAll('\n', '<br>') || '';
|
||||
const textWithMedia = item.media?.length > 0 ? raw + `<figure><img src="${item.media[0].url}"></figure>` : raw;
|
||||
return {
|
||||
title: item.title,
|
||||
link: `https://www.ganjingworld.com/news/${item.id}`,
|
||||
pubDate,
|
||||
description: textWithMedia,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
title,
|
||||
link: url,
|
||||
description: `Posts from Ganjing World Channel ${title}`,
|
||||
item: items,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
// RSSHub route for fetching shorts from Ganjing World.
|
||||
// Returns a list of shorts in a channel.
|
||||
// Source: https://www.ganjingworld.com
|
||||
import type { Route } from '@/types';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
|
||||
import type { ApiResponse } from '../interfaces/api';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/channel/shorts/:id',
|
||||
categories: ['social-media'],
|
||||
features: {
|
||||
requireConfig: false,
|
||||
requirePuppeteer: false,
|
||||
antiCrawler: false,
|
||||
supportBT: false,
|
||||
supportPodcast: false,
|
||||
supportScihub: false,
|
||||
},
|
||||
example: '/ganjingworld/channel/shorts/1fq5chh3ajo67UNu14uAvfzOp1a80c',
|
||||
parameters: { id: 'Channel ID, can be found in channel url' },
|
||||
radar: [
|
||||
{
|
||||
source: ['ganjingworld.com'],
|
||||
target: '/channel/shorts/:id',
|
||||
},
|
||||
],
|
||||
url: 'www.ganjingworld.com',
|
||||
name: 'Shorts in a channel',
|
||||
maintainers: ['yixiangli2001'],
|
||||
handler,
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const id = ctx.req.param('id');
|
||||
const url = `https://www.ganjingworld.com/channel/${id}?tab=shorts`;
|
||||
const apiUrl = `https://gw.ganjingworld.com/v1.1/content/get-by-channel?channel_id=${id}&content_type=Shorts&page_size=30&query=basic%2Cwatch_count&visibility=public&mode=ready`;
|
||||
|
||||
const parsed: ApiResponse = await ofetch<ApiResponse>(apiUrl);
|
||||
if (parsed.data.list.length === 0) {
|
||||
throw new Error('No shorts found for this channel. Please make sure the channel ID is correct and that the channel contains shorts.');
|
||||
}
|
||||
const title = parsed.data.list[0].channel.name;
|
||||
const items = parsed.data.list.map((item) => {
|
||||
const pubDate = new Date(item.time_scheduled);
|
||||
const videoUrl = item.video_url || (item.media.length > 0 ? item.media[0].url : '');
|
||||
const posterUrl = item.poster_url || item.image_auto_url || '';
|
||||
|
||||
let description = item.description || '';
|
||||
if (videoUrl) {
|
||||
description = `<video controls src="${videoUrl}" poster="${posterUrl}" style="max-width: 100%;"></video><br/>${description}`;
|
||||
}
|
||||
|
||||
return {
|
||||
title: item.title,
|
||||
link: `https://www.ganjingworld.com/video/${item.id}`,
|
||||
pubDate,
|
||||
description,
|
||||
};
|
||||
});
|
||||
return {
|
||||
title,
|
||||
link: url,
|
||||
description: `Short videos from Channel ${title}`,
|
||||
item: items,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
// RSSHub route for fetching videos from Ganjing World.
|
||||
// Returns a list of videos in a channel.
|
||||
// Source: https://www.ganjingworld.com
|
||||
|
||||
import type { Route } from '@/types';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
|
||||
import type { ApiResponse } from '../interfaces/api';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/channel/videos/:id',
|
||||
categories: ['social-media'],
|
||||
features: {
|
||||
requireConfig: false,
|
||||
requirePuppeteer: false,
|
||||
antiCrawler: false,
|
||||
supportBT: false,
|
||||
supportPodcast: false,
|
||||
supportScihub: false,
|
||||
},
|
||||
example: '/ganjingworld/channel/videos/1eiqjdnq7go1OPYtIbLDVMpM61ok0c',
|
||||
parameters: { id: 'Channel ID, can be found in channel url' },
|
||||
radar: [
|
||||
{
|
||||
source: ['ganjingworld.com'],
|
||||
target: '/channel/videos/:id',
|
||||
},
|
||||
],
|
||||
url: 'www.ganjingworld.com',
|
||||
name: 'Videos in a channel',
|
||||
maintainers: ['yixiangli2001'],
|
||||
|
||||
handler,
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const id = ctx.req.param('id');
|
||||
const url = `https://www.ganjingworld.com/channel/${id}`;
|
||||
const apiUrl = `https://gw.ganjingworld.com/v1.1/content/get-by-channel?lang=zh-TW&start_key=&channel_id=${id}&page_size=24&content_type=AllVideo&query=basic%2Ctranslations%2Cwatch_count&visibility=public&mode=ready`;
|
||||
// const apiUrl = `https://gw.ganjingworld.com/v1.1/content/get-by-channel?lang=zh-TW&start_key=&channel_id=1eiqjdnq7go1OPYtIbLDVMpM61ok0c&page_size=24&content_type=AllVideo&query=basic%2Ctranslations%2Cwatch_count&visibility=public&mode=ready`;
|
||||
|
||||
const parsed: ApiResponse = await ofetch<ApiResponse>(apiUrl);
|
||||
const title = parsed.data.list.length > 0 ? parsed.data.list[0].channel.name : 'Ganjing World Channel';
|
||||
const items = parsed.data.list.map((item) => {
|
||||
const pubDate = new Date(item.time_scheduled);
|
||||
const videoUrl = item.video_url || (item.media.length > 0 ? item.media[0].url : '');
|
||||
const posterUrl = item.poster_url || item.image_auto_url || '';
|
||||
|
||||
let description = item.description || '';
|
||||
if (videoUrl) {
|
||||
description = `<video controls src="${videoUrl}" poster="${posterUrl}" style="max-width: 100%;"></video><br/>${description}`;
|
||||
}
|
||||
|
||||
return {
|
||||
title: item.title,
|
||||
link: `https://www.ganjingworld.com/video/${item.id}`,
|
||||
pubDate,
|
||||
description,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
title,
|
||||
link: url,
|
||||
description: `Videos from Ganjing World Channel ${title}`,
|
||||
item: items,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// interfaces/api.ts
|
||||
export interface ApiResponse {
|
||||
data: {
|
||||
list: ListItem[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface ListItem {
|
||||
title: string;
|
||||
description?: string;
|
||||
id: string;
|
||||
time_scheduled: number;
|
||||
channel: Channel;
|
||||
poster_url: string;
|
||||
image_auto_url?: string;
|
||||
video_url: string;
|
||||
text?: string;
|
||||
media: Media[];
|
||||
watch_count?: number;
|
||||
}
|
||||
export interface Channel {
|
||||
icon: string;
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface Media {
|
||||
type: string;
|
||||
id: string;
|
||||
url: string;
|
||||
width: number;
|
||||
height: number;
|
||||
ratio: number;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import type { Namespace } from '@/types';
|
||||
|
||||
export const namespace: Namespace = {
|
||||
name: 'Ganjing World',
|
||||
url: 'www.ganjingworld.com/',
|
||||
description:
|
||||
'"Gan Jing" means Clean. Through the utilization of technology, Gan Jing World is dedicated to revitalizing traditional connections—fostering a culture of care, kindness, mutual respect, and trust among individuals, within families, and throughout society.',
|
||||
lang: 'en',
|
||||
};
|
||||
Loading…
Reference in New Issue