require api key for inoreader

This commit is contained in:
刘浩远 2020-12-29 17:58:19 +08:00
parent a2087cec7a
commit f794816469
6 changed files with 75 additions and 17 deletions

View File

@ -64,6 +64,7 @@ class GReaderConfigsTab extends React.Component<ServiceConfigsTabProps, GReaderC
endpoint: this.state.endpoint, endpoint: this.state.endpoint,
fetchLimit: this.state.fetchLimit fetchLimit: this.state.fetchLimit
} as GReaderConfigs } as GReaderConfigs
if (this.state.password) configs.password = this.state.password
} else { } else {
configs = { configs = {
type: SyncService.GReader, type: SyncService.GReader,

View File

@ -4,7 +4,7 @@ import { ServiceConfigsTabProps } from "../service"
import { GReaderConfigs } from "../../../scripts/models/services/greader" import { GReaderConfigs } from "../../../scripts/models/services/greader"
import { SyncService } from "../../../schema-types" import { SyncService } from "../../../schema-types"
import { Stack, Label, TextField, PrimaryButton, DefaultButton, Checkbox, import { Stack, Label, TextField, PrimaryButton, DefaultButton, Checkbox,
MessageBar, MessageBarType, Dropdown, IDropdownOption, MessageBarButton } from "@fluentui/react" MessageBar, MessageBarType, Dropdown, IDropdownOption, MessageBarButton, Link } from "@fluentui/react"
import DangerButton from "../../utils/danger-button" import DangerButton from "../../utils/danger-button"
type GReaderConfigsTabState = { type GReaderConfigsTabState = {
@ -12,8 +12,10 @@ type GReaderConfigsTabState = {
endpoint: string endpoint: string
username: string username: string
password: string password: string
apiId: string
apiKey: string
removeAd: boolean
fetchLimit: number fetchLimit: number
importGroups: boolean
} }
const endpointOptions: IDropdownOption[] = [ const endpointOptions: IDropdownOption[] = [
@ -33,8 +35,10 @@ class InoreaderConfigsTab extends React.Component<ServiceConfigsTabProps, GReade
endpoint: configs.endpoint || "https://www.inoreader.com", endpoint: configs.endpoint || "https://www.inoreader.com",
username: configs.username || "", username: configs.username || "",
password: "", password: "",
apiId: configs.inoreaderId || "",
apiKey: configs.inoreaderKey || "",
removeAd: configs.removeInoreaderAd === undefined ? true : configs.removeInoreaderAd,
fetchLimit: configs.fetchLimit || 250, fetchLimit: configs.fetchLimit || 250,
importGroups: true,
} }
} }
@ -62,7 +66,8 @@ class InoreaderConfigsTab extends React.Component<ServiceConfigsTabProps, GReade
} }
validateForm = () => { validateForm = () => {
return this.state.existing || (this.state.username && this.state.password) return (this.state.existing || (this.state.username && this.state.password))
&& this.state.apiId && this.state.apiKey
} }
save = async () => { save = async () => {
@ -71,18 +76,25 @@ class InoreaderConfigsTab extends React.Component<ServiceConfigsTabProps, GReade
configs = { configs = {
...this.props.configs, ...this.props.configs,
endpoint: this.state.endpoint, endpoint: this.state.endpoint,
fetchLimit: this.state.fetchLimit fetchLimit: this.state.fetchLimit,
inoreaderId: this.state.apiId,
inoreaderKey: this.state.apiKey,
removeInoreaderAd: this.state.removeAd,
} as GReaderConfigs } as GReaderConfigs
if (this.state.password) configs.password = this.state.password
} else { } else {
configs = { configs = {
type: SyncService.Inoreader, type: SyncService.Inoreader,
endpoint: this.state.endpoint, endpoint: this.state.endpoint,
username: this.state.username, username: this.state.username,
password: this.state.password, password: this.state.password,
inoreaderId: this.state.apiId,
inoreaderKey: this.state.apiKey,
removeInoreaderAd: this.state.removeAd,
fetchLimit: this.state.fetchLimit, fetchLimit: this.state.fetchLimit,
importGroups: true,
useInt64: true useInt64: true
} }
if (this.state.importGroups) configs.importGroups = true
} }
this.props.blockActions() this.props.blockActions()
configs = await this.props.reauthenticate(configs) as GReaderConfigs configs = await this.props.reauthenticate(configs) as GReaderConfigs
@ -97,6 +109,8 @@ class InoreaderConfigsTab extends React.Component<ServiceConfigsTabProps, GReade
} }
} }
createKey = () => window.utils.openExternal(this.state.endpoint + "/all_articles#preferences-developer")
remove = async () => { remove = async () => {
this.props.exit() this.props.exit()
await this.props.remove() await this.props.remove()
@ -106,8 +120,9 @@ class InoreaderConfigsTab extends React.Component<ServiceConfigsTabProps, GReade
return <> return <>
<MessageBar messageBarType={MessageBarType.severeWarning} <MessageBar messageBarType={MessageBarType.severeWarning}
isMultiline={false} isMultiline={false}
actions={<MessageBarButton text={intl.get("rules.help")} onClick={openSupport} />}> actions={<MessageBarButton text={intl.get("create")} onClick={this.createKey} />}>
{intl.get("service.rateLimitWarning")} {intl.get("service.rateLimitWarning")}
<Link onClick={openSupport} style={{marginLeft: 6}}>{intl.get("rules.help")}</Link>
</MessageBar> </MessageBar>
{!this.state.existing && ( {!this.state.existing && (
<MessageBar messageBarType={MessageBarType.warning}>{intl.get("service.overwriteWarning")}</MessageBar> <MessageBar messageBarType={MessageBarType.warning}>{intl.get("service.overwriteWarning")}</MessageBar>
@ -155,6 +170,32 @@ class InoreaderConfigsTab extends React.Component<ServiceConfigsTabProps, GReade
onChange={this.handleInputChange} /> onChange={this.handleInputChange} />
</Stack.Item> </Stack.Item>
</Stack> </Stack>
<Stack className="login-form" horizontal>
<Stack.Item>
<Label>API ID</Label>
</Stack.Item>
<Stack.Item grow>
<TextField
onGetErrorMessage={this.checkNotEmpty}
validateOnLoad={false}
name="apiId"
value={this.state.apiId}
onChange={this.handleInputChange} />
</Stack.Item>
</Stack>
<Stack className="login-form" horizontal>
<Stack.Item>
<Label>API Key</Label>
</Stack.Item>
<Stack.Item grow>
<TextField
onGetErrorMessage={this.checkNotEmpty}
validateOnLoad={false}
name="apiKey"
value={this.state.apiKey}
onChange={this.handleInputChange} />
</Stack.Item>
</Stack>
<Stack className="login-form" horizontal> <Stack className="login-form" horizontal>
<Stack.Item> <Stack.Item>
<Label>{intl.get("service.fetchLimit")}</Label> <Label>{intl.get("service.fetchLimit")}</Label>
@ -166,10 +207,10 @@ class InoreaderConfigsTab extends React.Component<ServiceConfigsTabProps, GReade
onChange={this.onFetchLimitOptionChange} /> onChange={this.onFetchLimitOptionChange} />
</Stack.Item> </Stack.Item>
</Stack> </Stack>
{!this.state.existing && <Checkbox <Checkbox
label={intl.get("service.importGroups")} label={intl.get("service.removeAd")}
checked={this.state.importGroups} checked={this.state.removeAd}
onChange={(_, c) => this.setState({importGroups: c})} />} onChange={(_, c) => this.setState({removeAd: c})} />
<Stack horizontal style={{marginTop: 32}}> <Stack horizontal style={{marginTop: 32}}>
<Stack.Item> <Stack.Item>
<PrimaryButton <PrimaryButton

View File

@ -191,7 +191,8 @@
"suggest": "Suggest a new service", "suggest": "Suggest a new service",
"overwriteWarning": "Local sources will be deleted if they exist in the service.", "overwriteWarning": "Local sources will be deleted if they exist in the service.",
"groupsWarning": "Groups aren't automatically synced with the service.", "groupsWarning": "Groups aren't automatically synced with the service.",
"rateLimitWarning": "If connection errors persist, the app may have been rate limited by the service.", "rateLimitWarning": "To avoid rate limiting, you need to create your own API Key.",
"removeAd": "Remove Ad",
"endpoint": "Endpoint", "endpoint": "Endpoint",
"username": "Username", "username": "Username",
"password": "Password", "password": "Password",

View File

@ -189,7 +189,8 @@
"suggest": "建议一项新服务", "suggest": "建议一项新服务",
"overwriteWarning": "若本地与服务端存在URL相同的订阅源则本地订阅源将被删除", "overwriteWarning": "若本地与服务端存在URL相同的订阅源则本地订阅源将被删除",
"groupsWarning": "分组不会自动与服务端保持同步", "groupsWarning": "分组不会自动与服务端保持同步",
"rateLimitWarning": "若反复出现错误,则原因可能是应用被服务限流", "rateLimitWarning": "为避免限流,您需要新建自己的 API Key",
"removeAd": "移除广告",
"endpoint": "端点", "endpoint": "端点",
"username": "用户名", "username": "用户名",
"password": "密码", "password": "密码",

View File

@ -189,7 +189,8 @@
"suggest": "建議一項新服務", "suggest": "建議一項新服務",
"overwriteWarning": "若本地與服務端存在URL相同的訂閱源則本地訂閱源將被刪除", "overwriteWarning": "若本地與服務端存在URL相同的訂閱源則本地訂閱源將被刪除",
"groupsWarning": "分組不會自動與服務端保持同步", "groupsWarning": "分組不會自動與服務端保持同步",
"rateLimitWarning": "若反覆出現錯誤,則原因可能是應用被服務限流", "rateLimitWarning": "為避免限流,您需要新建自己的 API Key",
"removeAd": "移除廣告",
"endpoint": "端點", "endpoint": "端點",
"username": "使用者名稱", "username": "使用者名稱",
"password": "密碼", "password": "密碼",

View File

@ -23,14 +23,22 @@ export interface GReaderConfigs extends ServiceConfigs {
lastId?: string lastId?: string
auth?: string auth?: string
useInt64: boolean // The Old Reader uses ids longer than 64 bits useInt64: boolean // The Old Reader uses ids longer than 64 bits
inoreaderId?: string
inoreaderKey?: string
removeInoreaderAd?: boolean
} }
async function fetchAPI(configs: GReaderConfigs, params: string, method="GET", body:BodyInit=null) { async function fetchAPI(configs: GReaderConfigs, params: string, method="GET", body:BodyInit=null) {
const headers = new Headers() const headers = new Headers()
if (configs.auth !== null) headers.set("Authorization", configs.auth) if (configs.auth !== null) headers.set("Authorization", configs.auth)
if (configs.type == SyncService.Inoreader) { if (configs.type == SyncService.Inoreader) {
headers.set("AppId", "999999298") if (configs.inoreaderId) {
headers.set("AppKey", "KPbKYXTfgrKbwmroOeYC7mcW21ZRwF5Y") headers.set("AppId", configs.inoreaderId)
headers.set("AppKey", configs.inoreaderKey)
} else {
headers.set("AppId", "999999298")
headers.set("AppKey", "KPbKYXTfgrKbwmroOeYC7mcW21ZRwF5Y")
}
} }
return await fetch(configs.endpoint + params, { return await fetch(configs.endpoint + params, {
method: method, method: method,
@ -189,13 +197,18 @@ export const gReaderServiceHooks: ServiceHooks = {
const source = fidMap.get(i.origin.streamId) const source = fidMap.get(i.origin.streamId)
if (source === undefined) return if (source === undefined) return
const dom = domParser.parseFromString(i.summary.content, "text/html") const dom = domParser.parseFromString(i.summary.content, "text/html")
if (configs.type == SyncService.Inoreader && configs.removeInoreaderAd !== false) {
if (dom.documentElement.textContent.trim().startsWith("Ads from Inoreader")) {
dom.body.firstChild.remove()
}
}
const item = { const item = {
source: source.sid, source: source.sid,
title: i.title, title: i.title,
link: i.canonical[0].href, link: i.canonical[0].href,
date: new Date(i.published * 1000), date: new Date(i.published * 1000),
fetchedDate: new Date(parseInt(i.crawlTimeMsec)), fetchedDate: new Date(parseInt(i.crawlTimeMsec)),
content: i.summary.content, content: dom.body.innerHTML,
snippet: dom.documentElement.textContent.trim(), snippet: dom.documentElement.textContent.trim(),
creator: i.author, creator: i.author,
hasRead: false, hasRead: false,