From b3d0bb9ed79cc0607cc97a24224edd2a201bf74f Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Mon, 11 Dec 2023 14:14:54 +0800 Subject: [PATCH] return absolute feed url --- .../api/src/resolvers/function_resolvers.ts | 1 + .../api/src/resolvers/subscriptions/index.ts | 31 ++++++++++++------- packages/api/src/utils/helpers.ts | 8 +++++ packages/api/src/utils/parser.ts | 21 +++++++++---- 4 files changed, 43 insertions(+), 18 deletions(-) diff --git a/packages/api/src/resolvers/function_resolvers.ts b/packages/api/src/resolvers/function_resolvers.ts index 909aeb121..9663d72bc 100644 --- a/packages/api/src/resolvers/function_resolvers.ts +++ b/packages/api/src/resolvers/function_resolvers.ts @@ -517,4 +517,5 @@ export const functionResolvers = { ...resultResolveTypeResolver('SetFavoriteArticle'), ...resultResolveTypeResolver('UpdateSubscription'), ...resultResolveTypeResolver('UpdateEmail'), + ...resultResolveTypeResolver('ScanFeeds'), } diff --git a/packages/api/src/resolvers/subscriptions/index.ts b/packages/api/src/resolvers/subscriptions/index.ts index fad1714d5..7ba41e667 100644 --- a/packages/api/src/resolvers/subscriptions/index.ts +++ b/packages/api/src/resolvers/subscriptions/index.ts @@ -40,7 +40,7 @@ import { unsubscribe } from '../../services/subscriptions' import { Merge } from '../../util' import { analytics } from '../../utils/analytics' import { enqueueRssFeedFetch } from '../../utils/createTask' -import { authorized } from '../../utils/helpers' +import { authorized, getAbsoluteUrl } from '../../utils/helpers' import { parseFeed, parseOpml, RSS_PARSER_CONFIG } from '../../utils/parser' type PartialSubscription = Omit @@ -418,7 +418,6 @@ export const scanFeedsResolver = authorized< } return { - __typename: 'ScanFeedsSuccess', feeds: feeds.map((feed) => ({ url: feed.url, title: feed.title, @@ -445,31 +444,39 @@ export const scanFeedsResolver = authorized< if (isHtml) { // this is an html page, parse rss feed links const dom = parseHTML(content).document - const links = dom.querySelectorAll('link[type="application/rss+xml"]') + // type is application/rss+xml or application/atom+xml + const links = dom.querySelectorAll( + 'link[type="application/rss+xml"], link[type="application/atom+xml"]' + ) + const feeds = Array.from(links) - .map((link) => ({ - url: link.getAttribute('href') || '', - title: link.getAttribute('title') || '', - type: 'rss', - })) + .map((link) => { + const href = link.getAttribute('href') || '' + const feedUrl = getAbsoluteUrl(href, url) + + return { + url: feedUrl, + title: link.getAttribute('title') || '', + type: 'rss', + } + }) .filter((feed) => feed.url) return { - __typename: 'ScanFeedsSuccess', feeds, } } // this is the url to an RSS feed - const feed = await parseFeed(url) + const feed = await parseFeed(url, content) if (!feed) { + log.error('Failed to parse RSS feed') return { - errorCodes: [ScanFeedsErrorCode.BadRequest], + feeds: [], } } return { - __typename: 'ScanFeedsSuccess', feeds: [feed], } } catch (error) { diff --git a/packages/api/src/utils/helpers.ts b/packages/api/src/utils/helpers.ts index b0ff7cfcf..df6537996 100644 --- a/packages/api/src/utils/helpers.ts +++ b/packages/api/src/utils/helpers.ts @@ -399,3 +399,11 @@ export const deepDelete = (obj: T, keys: K[]) => { return copy as Omit } + +export const isRelativeUrl = (url: string): boolean => { + return url.startsWith('/') +} + +export const getAbsoluteUrl = (url: string, baseUrl: string): string => { + return new URL(url, baseUrl).href +} diff --git a/packages/api/src/utils/parser.ts b/packages/api/src/utils/parser.ts index 2a8dbb86d..dff855b4f 100644 --- a/packages/api/src/utils/parser.ts +++ b/packages/api/src/utils/parser.ts @@ -798,17 +798,23 @@ export const parseHtml = async (url: string): Promise => { } } -export const parseFeed = async (url: string): Promise => { +export const parseFeed = async ( + url: string, + content?: string | null +): Promise => { try { // check if url is a telegram channel const telegramRegex = /https:\/\/t\.me\/([a-zA-Z0-9_]+)/ const telegramMatch = url.match(telegramRegex) if (telegramMatch) { - // fetch HTML and parse feeds - const html = await fetchHtml(url) - if (!html) return null + if (!content) { + // fetch HTML and parse feeds + content = await fetchHtml(url) + } - const dom = parseHTML(html).document + if (!content) return null + + const dom = parseHTML(content).document const title = dom.querySelector('meta[property="og:title"]') const thumbnail = dom.querySelector('meta[property="og:image"]') const description = dom.querySelector('meta[property="og:description"]') @@ -824,7 +830,10 @@ export const parseFeed = async (url: string): Promise => { const parser = new Parser(RSS_PARSER_CONFIG) - const feed = await parser.parseURL(url) + const feed = content + ? await parser.parseString(content) + : await parser.parseURL(url) + const feedUrl = feed.feedUrl || url return {