From 5342a43318afee1aa82ff2cf98db3f64cf21d1aa Mon Sep 17 00:00:00 2001 From: nyatinte Date: Sun, 7 Apr 2024 21:42:19 +0900 Subject: [PATCH 01/74] Added resize functionality to notebooks --- .../templates/article/HighlightsLayer.tsx | 31 +++++++++++++++++-- packages/web/package.json | 1 + 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/web/components/templates/article/HighlightsLayer.tsx b/packages/web/components/templates/article/HighlightsLayer.tsx index c0066bc58..39242e188 100644 --- a/packages/web/components/templates/article/HighlightsLayer.tsx +++ b/packages/web/components/templates/article/HighlightsLayer.tsx @@ -32,6 +32,7 @@ import { NotebookContent } from './Notebook' import { NotebookHeader } from './NotebookHeader' import useGetWindowDimensions from '../../../lib/hooks/useGetWindowDimensions' import { ConfirmationModal } from '../../patterns/ConfirmationModal' +import { Resizable } from 're-resizable' type HighlightsLayerProps = { viewer: UserBasicData @@ -850,7 +851,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { - <> + { + if (parseInt(ref.style.width) < 210) { + props.setShowHighlightsModal(false) + } + }} + defaultSize={{ + width: windowDimensions.width < 600 ? '100%' : '420px', + height: '100%', + }} + enable={ + windowDimensions.width < 600 + ? false + : { + top: false, + right: false, + bottom: false, + left: true, + topRight: false, + bottomRight: false, + bottomLeft: false, + topLeft: false, + } + } + > - + ) diff --git a/packages/web/package.json b/packages/web/package.json index c10a0573c..a067e1b51 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -49,6 +49,7 @@ "phosphor-react": "^1.4.0", "posthog-js": "^1.78.2", "pspdfkit": "^2023.4.6", + "re-resizable": "^6.9.11", "react": "^18.2.0", "react-color": "^2.19.3", "react-colorful": "^5.5.1", From ab81d357aeb77722861f0c4863e5234daad5eecc Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 8 Apr 2024 18:47:51 +0800 Subject: [PATCH 02/74] Update version code --- android/Omnivore/app/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/Omnivore/app/build.gradle.kts b/android/Omnivore/app/build.gradle.kts index ea62ccd25..9030972dd 100644 --- a/android/Omnivore/app/build.gradle.kts +++ b/android/Omnivore/app/build.gradle.kts @@ -27,8 +27,8 @@ android { applicationId = "app.omnivore.omnivore" minSdk = 26 targetSdk = 34 - versionCode = 200004 - versionName = "0.200.4" + versionCode = 2000050 + versionName = "0.200.5" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From dd235602d69a0f95f3e927d934fa3bde4227ee04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=92=8B=E5=B0=8F=E9=9C=95?= Date: Mon, 8 Apr 2024 20:33:44 +0800 Subject: [PATCH 03/74] Add readerSettingsGeneric --- .../Sources/Views/Resources/zh-Hans.lproj/Localizable.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/apple/OmnivoreKit/Sources/Views/Resources/zh-Hans.lproj/Localizable.strings b/apple/OmnivoreKit/Sources/Views/Resources/zh-Hans.lproj/Localizable.strings index 8d223097a..251667073 100644 --- a/apple/OmnivoreKit/Sources/Views/Resources/zh-Hans.lproj/Localizable.strings +++ b/apple/OmnivoreKit/Sources/Views/Resources/zh-Hans.lproj/Localizable.strings @@ -198,6 +198,7 @@ "clubsGeneric" = "阅读俱乐部"; "filterGeneric" = "特色功能"; "errorGeneric" = "哦!出现了小问题,请您重试。"; +"readerSettingsGeneric" = "阅读设置"; "pushNotificationsGeneric" = "推送通知"; "dismissButton" = "返回或撤销"; "errorNetwork" = "我们在连接到互联网时遇到问题。"; From 90fe5e8109bcc3f7c5ae434daa6b247bf1a26c84 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 9 Apr 2024 10:30:40 +0800 Subject: [PATCH 04/74] ignore expiration of jwt token for tts streaming --- packages/text-to-speech/src/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/text-to-speech/src/index.ts b/packages/text-to-speech/src/index.ts index 506ebf22f..d148204c5 100644 --- a/packages/text-to-speech/src/index.ts +++ b/packages/text-to-speech/src/index.ts @@ -232,8 +232,9 @@ export const textToSpeechStreamingHandler = Sentry.GCPFunction.wrapHttpFunction( let claim: Claim try { - jwt.verify(token, process.env.JWT_SECRET) - claim = jwt.decode(token) as Claim + claim = jwt.verify(token, process.env.JWT_SECRET, { + ignoreExpiration: true, + }) as Claim } catch (e) { console.error('Authentication error:', e) return res.status(401).send({ errorCode: 'UNAUTHENTICATED' }) From c4882c25427e9a016cc214e15dbad72e9f50f526 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 9 Apr 2024 10:43:33 +0800 Subject: [PATCH 05/74] update dev cli --- packages/text-to-speech/package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/text-to-speech/package.json b/packages/text-to-speech/package.json index c240ea2bc..f8e669fc9 100644 --- a/packages/text-to-speech/package.json +++ b/packages/text-to-speech/package.json @@ -18,6 +18,7 @@ "start": "functions-framework --target=textToSpeechHandler", "start_streaming": "functions-framework --target=textToSpeechStreamingHandler", "dev": "concurrently \"tsc -w\" \"nodemon --watch ./build/ --exec npm run start\"", + "dev_streaming": "concurrently \"tsc -w\" \"nodemon --watch ./build/ --exec npm run start_streaming\"", "gcloud-deploy": "gcloud functions deploy text-to-speech --gen2 --entry-point=textToSpeechHandler --trigger-http --allow-unauthenticated --region=us-west2 --runtime nodejs14", "deploy": "yarn build && yarn gcloud-deploy" }, @@ -45,6 +46,7 @@ "linkedom": "^0.14.12", "microsoft-cognitiveservices-speech-sdk": "1.30", "natural": "^6.2.0", + "nodemon": "^2.0.15", "underscore": "^1.13.4" }, "volta": { From 70b1a1d3c7c76716f0551b92990a48219637ca72 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 9 Apr 2024 10:47:23 +0800 Subject: [PATCH 06/74] add comment --- packages/text-to-speech/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/text-to-speech/src/index.ts b/packages/text-to-speech/src/index.ts index d148204c5..08686e8dc 100644 --- a/packages/text-to-speech/src/index.ts +++ b/packages/text-to-speech/src/index.ts @@ -232,6 +232,7 @@ export const textToSpeechStreamingHandler = Sentry.GCPFunction.wrapHttpFunction( let claim: Claim try { + // ignore expiration for now and verify function will also decode the token claim = jwt.verify(token, process.env.JWT_SECRET, { ignoreExpiration: true, }) as Claim From e19d4357c88653539778fe142551b9baf54096de Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 9 Apr 2024 10:50:40 +0800 Subject: [PATCH 07/74] Add rules and email addresses to admin --- pkg/admin/src/db.ts | 145 +++++++++++++++++++++++++++++------------ pkg/admin/src/index.ts | 12 ++-- 2 files changed, 111 insertions(+), 46 deletions(-) diff --git a/pkg/admin/src/db.ts b/pkg/admin/src/db.ts index 6a56e8e90..eb4ce7dd9 100644 --- a/pkg/admin/src/db.ts +++ b/pkg/admin/src/db.ts @@ -44,7 +44,6 @@ export const registerDatabase = async (secrets: any): Promise => { AdminUser, User, UserProfile, - UserArticle, ReceivedEmail, ContentDisplayReport, Group, @@ -55,6 +54,8 @@ export const registerDatabase = async (secrets: any): Promise => { Recommendation, GroupMembership, Features, + EmailAddress, + Rule, ], }) @@ -113,9 +114,6 @@ export class User extends BaseEntity { @Column({ type: 'enum', enum: StatusType }) status!: StatusType - @OneToMany(() => UserArticle, (ua) => ua.user) - articles!: UserArticle[] - @Column({ type: 'text' }) source_user_id!: string @@ -132,42 +130,17 @@ export class UserProfile extends BaseEntity { public username!: string @JoinColumn({ name: 'user_id' }) - @ManyToOne(() => User, (user) => user.articles, { eager: true }) + @ManyToOne(() => User, { eager: true }) user!: User } -@Entity({ name: 'user_articles' }) -export class UserArticle extends BaseEntity { - @PrimaryGeneratedColumn('uuid') - id!: string - - @JoinColumn({ name: 'user_id' }) - @ManyToOne(() => User, (user) => user.articles, { eager: true }) - user!: User - - @Column({ type: 'text', name: 'article_id' }) - articleId!: string - - @Column({ type: 'text' }) - slug!: string - - @Column({ type: 'timestamp', name: 'created_at' }) - createdAt!: Date - - @Column({ type: 'timestamp', name: 'updated_at' }) - updatedAt!: Date - - @Column({ type: 'timestamp', name: 'saved_at' }) - savedAt!: Date -} - @Entity({ name: 'content_display_report' }) export class ContentDisplayReport extends BaseEntity { @PrimaryGeneratedColumn('uuid') id!: string @JoinColumn({ name: 'user_id' }) - @ManyToOne(() => User, (user) => user.articles, { eager: true }) + @ManyToOne(() => User, { eager: true }) user!: User @Column({ type: 'text', name: 'original_url' }) @@ -189,7 +162,7 @@ export class ReceivedEmail extends BaseEntity { id!: string @JoinColumn({ name: 'user_id' }) - @ManyToOne(() => User, (user) => user.articles, { eager: true }) + @ManyToOne(() => User, { eager: true }) user!: User @Column('text') @@ -254,7 +227,7 @@ export class Integration extends BaseEntity { id!: string @JoinColumn({ name: 'user_id' }) - @ManyToOne(() => User, (user) => user.articles, { eager: true }) + @ManyToOne(() => User, { eager: true }) user!: User @Column('varchar', { length: 40 }) @@ -293,7 +266,7 @@ export class Subscription extends BaseEntity { id!: string @JoinColumn({ name: 'user_id' }) - @ManyToOne(() => User, (user) => user.articles, { eager: true }) + @ManyToOne(() => User, { eager: true }) user!: User @Column('text') @@ -335,7 +308,7 @@ export class LibraryItem extends BaseEntity { id!: string @JoinColumn({ name: 'user_id' }) - @ManyToOne(() => User, (user) => user.articles, { eager: true }) + @ManyToOne(() => User, { eager: true }) user!: User @Column({ type: 'enum', enum: LibraryItemState }) @@ -382,7 +355,7 @@ export class UploadFile extends BaseEntity { id!: string @JoinColumn({ name: 'user_id' }) - @ManyToOne(() => User, (user) => user.articles, { eager: true }) + @ManyToOne(() => User, { eager: true }) user!: User @Column('text') @@ -410,15 +383,15 @@ export class Recommendation extends BaseEntity { id!: string @JoinColumn({ name: 'recommender_id' }) - @ManyToOne(() => User, (user) => user.articles, { eager: true }) + @ManyToOne(() => User, { eager: true }) recommender!: User @JoinColumn({ name: 'library_item_id' }) - @ManyToOne(() => User, (user) => user.articles, { eager: true }) + @ManyToOne(() => User, { eager: true }) libraryItem!: LibraryItem @JoinColumn({ name: 'group_id' }) - @ManyToOne(() => User, (user) => user.articles, { eager: true }) + @ManyToOne(() => User, { eager: true }) group!: Group @Column('text', { nullable: true }) @@ -434,7 +407,7 @@ export class GroupMembership extends BaseEntity { id!: string @JoinColumn({ name: 'user_id' }) - @ManyToOne(() => User, (user) => user.articles, { eager: true }) + @ManyToOne(() => User, { eager: true }) user!: User @JoinColumn({ name: 'group_id' }) @@ -456,7 +429,7 @@ export class Features extends BaseEntity { id!: string @JoinColumn({ name: 'user_id' }) - @ManyToOne(() => User, (user) => user.articles, { eager: true }) + @ManyToOne(() => User, { eager: true }) user!: User @Column('text') @@ -474,3 +447,93 @@ export class Features extends BaseEntity { @Column({ type: 'timestamp', name: 'updated_at' }) updatedAt!: Date } + +@Entity({ name: 'newsletter_emails' }) +export class EmailAddress extends BaseEntity { + @PrimaryGeneratedColumn('uuid') + id!: string + + @JoinColumn({ name: 'user_id' }) + @ManyToOne(() => User) + user!: User + + @Column('varchar') + address!: string + + @Column('text') + folder?: string | null + + @Column('text') + name?: string | null + + @Column('text') + description?: string | null + + @Column({ type: 'timestamp', name: 'created_at' }) + createdAt!: Date + + @Column({ type: 'timestamp', name: 'updated_at' }) + updatedAt!: Date +} + +@Entity({ name: 'rules' }) +export class Rule extends BaseEntity { + @PrimaryGeneratedColumn('uuid') + id!: string + + @JoinColumn({ name: 'user_id' }) + @ManyToOne(() => User) + user!: User + + @Column('varchar') + name!: string + + @Column('text') + filter!: string + + @Column({ type: 'timestamp', name: 'created_at' }) + createdAt!: Date + + @Column({ type: 'timestamp', name: 'updated_at' }) + updatedAt!: Date + + @Column({ type: 'timestamp', name: 'failed_at' }) + failedAt?: Date +} + +// @Entity({ name: 'rules' }) +// export class Rule { +// @PrimaryGeneratedColumn('uuid') +// id!: string + +// // @ManyToOne(() => User, { onDelete: 'CASCADE' }) +// // @JoinColumn({ name: 'user_id' }) +// // user!: User + +// @Column('text') +// name!: string + +// // @Column('text') +// // filter!: string + +// // @Column('simple-json') +// // actions!: RuleAction[] + +// @Column('text', { nullable: true }) +// description?: string | null + +// // @Column('text', { array: true }) +// // eventTypes!: RuleEventType[] + +// // @Column('boolean', { default: true }) +// // enabled!: boolean + +// // @Column({ type: 'timestamp', name: 'created_at' }) +// // createdAt!: Date + +// // @Column({ type: 'timestamp', name: 'updated_at' }) +// // updatedAt!: Date + +// // @Column({ type: 'timestamp', name: 'failed_at' }) +// // failedAt?: Date +// } diff --git a/pkg/admin/src/index.ts b/pkg/admin/src/index.ts index d6b5fcd07..662cb8180 100644 --- a/pkg/admin/src/index.ts +++ b/pkg/admin/src/index.ts @@ -5,7 +5,6 @@ import { registerDatabase, AdminUser, User, - UserArticle, UserProfile, ReceivedEmail, Group, @@ -16,6 +15,8 @@ import { Recommendation, GroupMembership, Features, + EmailAddress, + Rule, } from './db' import { compare, hashSync } from 'bcryptjs' const readYamlFile = require('read-yaml-file') @@ -33,6 +34,9 @@ const ADMIN_USER_EMAIL = databases: [db], rootPath: '/admin', resources: [ + { + resource: ContentDisplayReport, + }, { resource: User, options: { @@ -40,18 +44,16 @@ const ADMIN_USER_EMAIL = }, }, { resource: UserProfile, options: { parent: { name: 'Users' } } }, - { resource: UserArticle, options: { parent: { name: 'Users' } } }, { resource: ReceivedEmail, options: { parent: { name: 'Users' } } }, { resource: Group, options: { parent: { name: 'Users' } } }, { resource: Subscription, options: { parent: { name: 'Users' } } }, { resource: Integration, options: { parent: { name: 'Users' } } }, { resource: LibraryItem, options: { parent: { name: 'Users' } } }, - { - resource: ContentDisplayReport, - }, { resource: Recommendation, options: { parent: { name: 'Users' } } }, { resource: GroupMembership, options: { parent: { name: 'Users' } } }, { resource: Features, options: { parent: { name: 'Users' } } }, + { resource: EmailAddress, options: { parent: { name: 'Users' } } }, + { resource: Rule, options: { parent: { name: 'Users' } } }, ], }) From 5d6c2ba7f8ee12160a72404584a72e2ea261ed95 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 9 Apr 2024 11:16:47 +0800 Subject: [PATCH 08/74] Remove commented out items --- pkg/admin/src/db.ts | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/pkg/admin/src/db.ts b/pkg/admin/src/db.ts index eb4ce7dd9..78f55c4c7 100644 --- a/pkg/admin/src/db.ts +++ b/pkg/admin/src/db.ts @@ -500,40 +500,3 @@ export class Rule extends BaseEntity { @Column({ type: 'timestamp', name: 'failed_at' }) failedAt?: Date } - -// @Entity({ name: 'rules' }) -// export class Rule { -// @PrimaryGeneratedColumn('uuid') -// id!: string - -// // @ManyToOne(() => User, { onDelete: 'CASCADE' }) -// // @JoinColumn({ name: 'user_id' }) -// // user!: User - -// @Column('text') -// name!: string - -// // @Column('text') -// // filter!: string - -// // @Column('simple-json') -// // actions!: RuleAction[] - -// @Column('text', { nullable: true }) -// description?: string | null - -// // @Column('text', { array: true }) -// // eventTypes!: RuleEventType[] - -// // @Column('boolean', { default: true }) -// // enabled!: boolean - -// // @Column({ type: 'timestamp', name: 'created_at' }) -// // createdAt!: Date - -// // @Column({ type: 'timestamp', name: 'updated_at' }) -// // updatedAt!: Date - -// // @Column({ type: 'timestamp', name: 'failed_at' }) -// // failedAt?: Date -// } From e7fc2c4567569e7cdcd51efd86d25ccd4273ccaa Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 9 Apr 2024 18:39:17 +0800 Subject: [PATCH 09/74] reset failed_at to null if a feed is refreshed successfully after failure --- packages/api/src/jobs/rss/refreshFeed.ts | 2 +- packages/api/src/services/update_subscription.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/api/src/jobs/rss/refreshFeed.ts b/packages/api/src/jobs/rss/refreshFeed.ts index 76078a8f4..8fec18c20 100644 --- a/packages/api/src/jobs/rss/refreshFeed.ts +++ b/packages/api/src/jobs/rss/refreshFeed.ts @@ -511,7 +511,7 @@ const processSubscription = async ( // fetch feed let itemCount = 0, - failedAt: Date | undefined + failedAt: Date | null = null const feedLastBuildDate = feed.lastBuildDate logger.info(`Feed last build date ${feedLastBuildDate || 'N/A'}`) diff --git a/packages/api/src/services/update_subscription.ts b/packages/api/src/services/update_subscription.ts index 9d0970ba5..d333f58ba 100644 --- a/packages/api/src/services/update_subscription.ts +++ b/packages/api/src/services/update_subscription.ts @@ -49,7 +49,7 @@ export const updateSubscription = async ( lastFetchedChecksum: newData.lastFetchedChecksum || undefined, status: newData.status || undefined, scheduledAt: newData.scheduledAt || undefined, - failedAt: newData.failedAt || undefined, + failedAt: newData.failedAt, autoAddToLibrary: newData.autoAddToLibrary ?? undefined, isPrivate: newData.isPrivate ?? undefined, fetchContentType: newData.fetchContentType || undefined, From a59c850c5f6f1116e12cd166274547949610f11b Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 9 Apr 2024 19:44:16 +0800 Subject: [PATCH 10/74] fix telegram channel --- packages/api/src/jobs/rss/refreshFeed.ts | 34 +++++++++++++++++++----- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/packages/api/src/jobs/rss/refreshFeed.ts b/packages/api/src/jobs/rss/refreshFeed.ts index 8fec18c20..5afdab813 100644 --- a/packages/api/src/jobs/rss/refreshFeed.ts +++ b/packages/api/src/jobs/rss/refreshFeed.ts @@ -192,22 +192,40 @@ export const fetchAndChecksum = async (url: string) => { const parseFeed = async (url: string, content: string) => { try { - // check if url is a telegram channel - const telegramRegex = /https:\/\/t\.me\/([a-zA-Z0-9_]+)/ + // check if url is a telegram channel or preview + const telegramRegex = /t\.me\/([^/]+)/ const telegramMatch = url.match(telegramRegex) if (telegramMatch) { + let channel = telegramMatch[1] + if (channel.startsWith('s/')) { + channel = channel.slice(2) + } else { + // open the preview page to get the data + const fetchResult = await fetchAndChecksum(`https://t.me/s/${channel}`) + if (!fetchResult) { + return null + } + + content = fetchResult.content + } + const dom = parseHTML(content).document - const title = dom.querySelector('meta[property="og:title"]') + const title = + dom + .querySelector('meta[property="og:title"]') + ?.getAttribute('content') || dom.title // post has attribute data-post const posts = dom.querySelectorAll('[data-post]') const items = Array.from(posts) .map((post) => { - const id = post.getAttribute('data-post') + const id = post.getAttribute('data-post')?.split('/')[1] if (!id) { return null } - const url = `https://t.me/${telegramMatch[1]}/${id}` + const url = `https://t.me/s/${channel}/${id}` + const content = post.outerHTML + // find the