Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/bridges/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,22 @@ const utilsBridge = {
initFontList: (): Promise<Array<string>> => {
return ipcRenderer.invoke("init-font-list")
},

fetchRSS: (url: string): Promise<string> => {
return ipcRenderer.invoke("fetch-rss", url)
},

fetchFaviconHTML: (url: string): Promise<string | null> => {
return ipcRenderer.invoke("fetch-favicon-html", url)
},

validateFavicon: (url: string): Promise<boolean> => {
return ipcRenderer.invoke("validate-favicon", url)
},

fetchArticleHTML: (url: string): Promise<ArrayBuffer> => {
return ipcRenderer.invoke("fetch-article-html", url)
}
}

declare global {
Expand Down
13 changes: 10 additions & 3 deletions src/components/article.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -330,9 +330,16 @@ class Article extends React.Component<ArticleProps, ArticleState> {
this.setState({ fullContent: "", loaded: false, error: false })
const link = this.props.item.link
try {
const result = await fetch(link)
if (!result || !result.ok) throw new Error()
const html = await decodeFetchResponse(result, true)
const buffer = await window.utils.fetchArticleHTML(link)

if (!buffer) throw new Error()

const response = new Response(buffer, {
headers: { "content-type": "text/html" },
})

const html = await decodeFetchResponse(response, true)

if (link === this.props.item.link) {
this.setState({ fullContent: html })
}
Expand Down
70 changes: 70 additions & 0 deletions src/main/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,4 +308,74 @@ export function setUtilsListeners(manager: WindowManager) {
disableQuoting: true,
})
})

ipcMain.handle("fetch-rss", async (_, url: string) => {
const res = await fetch(url, {
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:147.0) Gecko/20100101 Firefox/147.0",
"Accept": "application/xml,text/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9",
},
})

if (!res.ok) {
dialog.showErrorBox("Failed to fetch RSS", `${res.status} ${res.statusText}`)
}

return await res.text()
})

ipcMain.handle("fetch-favicon-html", async (_, url: string) => {
const res = await fetch(url, {
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:147.0) Gecko/20100101 Firefox/147.0",
Accept: "text/html,application/xhtml+xml;q=0.9,*/*;q=0.8",
},
redirect: "follow",
})

if (!res.ok) return null
return await res.text()
})

ipcMain.handle("validate-favicon", async (_, url: string) => {
try {
const res = await fetch(url, {
method: "GET",
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:147.0) Gecko/20100101 Firefox/147.0",
"Accept":
"image/avif,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5",
"Accept-Language": "zh-CN,zh;q=0.9",
},
})

if (!res.ok) return false

const type = res.headers.get("content-type") ?? ""
return type.startsWith("image/")
} catch {
return false
}
})

ipcMain.handle("fetch-article-html", async (_, url: string) => {
const res = await fetch(url, {
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:147.0) Gecko/20100101 Firefox/147.0",
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language":
"zh-CN,zh;q=0.9,en-US;q=0.6,en;q=0.5",
},
redirect: "follow",
})

if (!res.ok) throw new Error(res.statusText)
return await res.arrayBuffer()
})
}
48 changes: 17 additions & 31 deletions src/scripts/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,22 +74,18 @@ export async function decodeFetchResponse(response: Response, isHTML = false) {
}

export async function parseRSS(url: string) {
let result: Response
let xml: string

try {
result = await fetch(url, { credentials: "omit" })
xml = await window.utils.fetchRSS(url)
} catch {
throw new Error(intl.get("log.networkError"))
}
if (result && result.ok) {
try {
return await rssParser.parseString(
await decodeFetchResponse(result)
)
} catch {
throw new Error(intl.get("log.parseError"))
}
} else {
throw new Error(result.status + " " + result.statusText)

try {
return await rssParser.parseString(xml)
} catch {
throw new Error(intl.get("log.parseError"))
}
}

Expand All @@ -98,11 +94,11 @@ export const domParser = new DOMParser()
export async function fetchFavicon(url: string) {
try {
url = url.split("/").slice(0, 3).join("/")
let result = await fetch(url, { credentials: "omit" })
if (result.ok) {
let html = await result.text()
let html = await window.utils.fetchFaviconHTML(url)
if (html) {
let dom = domParser.parseFromString(html, "text/html")
let links = dom.getElementsByTagName("link")

for (let link of links) {
let rel = link.getAttribute("rel")
if (
Expand All @@ -111,15 +107,17 @@ export async function fetchFavicon(url: string) {
) {
let href = link.getAttribute("href")
let parsedUrl = Url.parse(url)

if (href.startsWith("//")) return parsedUrl.protocol + href
else if (href.startsWith("/")) return url + href
else return href
}
}
}
url = url + "/favicon.ico"
if (await validateFavicon(url)) {
return url

const fallback = url + "/favicon.ico"
if (await validateFavicon(fallback)) {
return fallback
} else {
return null
}
Expand All @@ -129,19 +127,7 @@ export async function fetchFavicon(url: string) {
}

export async function validateFavicon(url: string) {
let flag = false
try {
const result = await fetch(url, { credentials: "omit" })
if (
result.status == 200 &&
result.headers.has("Content-Type") &&
result.headers.get("Content-Type").startsWith("image")
) {
flag = true
}
} finally {
return flag
}
return window.utils.validateFavicon(url)
}

export function htmlDecode(input: string) {
Expand Down