import { WebApplicationInterface } from '@standardnotes/services' import { ContentType } from '@standardnotes/common' import { DecryptedTransferPayload, NoteContent } from '@standardnotes/models' import { readFileAsText } from '../Utils' type GoogleKeepJsonNote = { color: string isTrashed: boolean isPinned: boolean isArchived: boolean textContent: string title: string userEditedTimestampUsec: number } export class GoogleKeepConverter { constructor(protected application: WebApplicationInterface) {} async convertGoogleKeepBackupFileToNote( file: File, stripHtml: boolean, ): Promise> { const content = await readFileAsText(file) const possiblePayloadFromJson = this.tryParseAsJson(content) if (possiblePayloadFromJson) { return possiblePayloadFromJson } const possiblePayloadFromHtml = this.tryParseAsHtml(content, file, stripHtml) if (possiblePayloadFromHtml) { return possiblePayloadFromHtml } throw new Error('Could not parse Google Keep backup file') } tryParseAsHtml(data: string, file: { name: string }, stripHtml: boolean): DecryptedTransferPayload { const rootElement = document.createElement('html') rootElement.innerHTML = data const contentElement = rootElement.getElementsByClassName('content')[0] let content: string | null // Replace
with \n so line breaks get recognised contentElement.innerHTML = contentElement.innerHTML.replace(/
/g, '\n') if (stripHtml) { content = contentElement.textContent } else { content = contentElement.innerHTML } if (!content) { throw new Error('Could not parse content') } const title = rootElement.getElementsByClassName('title')[0]?.textContent || file.name const date = this.getDateFromGKeepNote(data) || new Date() return { created_at: date, created_at_timestamp: date.getTime(), updated_at: date, updated_at_timestamp: date.getTime(), uuid: this.application.generateUUID(), content_type: ContentType.Note, content: { title: title, text: content, references: [], }, } } getDateFromGKeepNote(note: string) { const regexWithTitle = /.*(?=<\/div>\n
)/ const regexWithoutTitle = /.*(?=<\/div>\n\n
)/ const possibleDateStringWithTitle = regexWithTitle.exec(note)?.[0] const possibleDateStringWithoutTitle = regexWithoutTitle.exec(note)?.[0] if (possibleDateStringWithTitle) { const date = new Date(possibleDateStringWithTitle) if (date.toString() !== 'Invalid Date' && date.toString() !== 'NaN') { return date } } if (possibleDateStringWithoutTitle) { const date = new Date(possibleDateStringWithoutTitle) if (date.toString() !== 'Invalid Date' && date.toString() !== 'NaN') { return date } } return } static isValidGoogleKeepJson(json: any): boolean { return ( json.title && json.textContent && json.userEditedTimestampUsec && typeof json.isArchived === 'boolean' && typeof json.isTrashed === 'boolean' && typeof json.isPinned === 'boolean' && json.color ) } tryParseAsJson(data: string): DecryptedTransferPayload | null { try { const parsed = JSON.parse(data) as GoogleKeepJsonNote if (!GoogleKeepConverter.isValidGoogleKeepJson(parsed)) { return null } const date = new Date(parsed.userEditedTimestampUsec / 1000) return { created_at: date, created_at_timestamp: date.getTime(), updated_at: date, updated_at_timestamp: date.getTime(), uuid: this.application.generateUUID(), content_type: ContentType.Note, content: { title: parsed.title, text: parsed.textContent, references: [], archived: Boolean(parsed.isArchived), trashed: Boolean(parsed.isTrashed), pinned: Boolean(parsed.isPinned), }, } } catch (e) { return null } } }