Skip to content

Commit

Permalink
Merge pull request #78 from EltonChou/feat-more-pattern
Browse files Browse the repository at this point in the history
Add more filename pattern token to use.
  • Loading branch information
EltonChou authored Jul 5, 2023
2 parents 83df50d + f50f929 commit 55e915a
Show file tree
Hide file tree
Showing 23 changed files with 336 additions and 162 deletions.
11 changes: 10 additions & 1 deletion src/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,20 @@
"options_general_filenamePattern_token_date": {
"message": "Date"
},
"options_general_filenamePattern_token_accountId": {
"message": "Account ID"
},
"options_general_filenamePattern_token_tweetDate": {
"message": "Tweet Date"
},
"options_general_filenamePattern_token_tweetDatetime": {
"message": "Tweet Datetime"
},
"options_general_filenameSettings_message_dir": {
"message": "Invalid directory name. Cannot contain <>:\"/\\|?*"
},
"options_general_filenameSettings_message_pattern": {
"message": "Invalid pattern. The pattern must include `Tweet ID` + `Serial` or `Hash`."
"message": "Invalid pattern. The pattern must includes `Tweet ID` + `Serial` or `Hash`."
},
"options_features_revealNsfw": {
"message": "Auto-reveal sensitive content"
Expand Down
9 changes: 9 additions & 0 deletions src/_locales/ja/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,15 @@
"options_general_filenamePattern_token_date": {
"message": "日付"
},
"options_general_filenamePattern_token_accountId": {
"message": "アカウント ID"
},
"options_general_filenamePattern_token_tweetDate": {
"message": "ツイート 日付"
},
"options_general_filenamePattern_token_tweetDatetime": {
"message": "ツイート 日時"
},
"options_general_filenameSettings_message_dir": {
"message": "ディレクトリ名に次の文字は使えません <>:\"/\\|?*"
},
Expand Down
9 changes: 9 additions & 0 deletions src/_locales/zh_TW/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,15 @@
"options_general_filenamePattern_token_date": {
"message": "日期"
},
"options_general_filenamePattern_token_accountId": {
"message": "帳號 ID"
},
"options_general_filenamePattern_token_tweetDate": {
"message": "推特日期"
},
"options_general_filenamePattern_token_tweetDatetime": {
"message": "推特日期時間"
},
"options_general_filenameSettings_message_dir": {
"message": "無效的資料夾名稱。不能含有 <>:\"/\\|?*"
},
Expand Down
15 changes: 7 additions & 8 deletions src/backend/downloads/MediaDownloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,28 @@ const ARIA2_ID =
process.env.TARGET === 'chrome' ? 'mpkodccbngfoacfalldjimigbofkhgjn' : 'jjfgljkjddpcpfapejfkelkbjbehagbh'

export default class MediaDownloader {
readonly tweetInfo: TweetInfo
readonly tweetDetail: TweetDetail
readonly filenameSettings: V4FilenameSettings
readonly downloadSettings: DownloadSettings
readonly featureSettings: FeatureSettings
readonly mode: DownloadMode
private record_config: DownloadItemRecorder

constructor(
tweetInfo: TweetInfo,
tweetDetail: TweetDetail,
filenameSettings: V4FilenameSettings,
downloadSettings: DownloadSettings,
featureSettings: FeatureSettings
) {
this.tweetInfo = tweetInfo
this.tweetDetail = tweetDetail
this.filenameSettings = filenameSettings
this.downloadSettings = downloadSettings
this.featureSettings = featureSettings
this.mode = this.downloadSettings.enableAria2 ? DownloadMode.Aria2 : DownloadMode.Browser
this.record_config = downloadItemRecorder(tweetInfo)
this.record_config = downloadItemRecorder({ tweetId: tweetDetail.id, screenName: tweetDetail.screenName })
}

static async build(tweetInfo: TweetInfo) {
static async build(tweetInfo: TweetDetail) {
const fileNameSettings = await storageConfig.v4FilenameSettingsRepo.getSettings()
const downloadSettings = await storageConfig.downloadSettingsRepo.getSettings()
const featureSettings = await storageConfig.featureSettingsRepo.getSettings()
Expand All @@ -43,13 +43,12 @@ export default class MediaDownloader {

if (!this.featureSettings.includeVideoThumbnail && media_url.includes('video_thumb')) return

const mediaFile = new TwitterMediaFile(this.tweetInfo, media_url, index, this.downloadSettings.askWhereToSave)
const mediaFile = new TwitterMediaFile(this.tweetDetail, media_url, index, this.downloadSettings.askWhereToSave)
const config = mediaFile.makeDownloadConfigBySetting(this.filenameSettings, this.mode)

const downloadCallback = this.record_config(config)
this.mode === DownloadMode.Aria2
? browser.runtime.sendMessage(ARIA2_ID, config)
: browser.downloads.download(config).then(downloadCallback)
: browser.downloads.download(config).then(downloadId => this.record_config(config)(downloadId))
}

downloadMediasByMediaCatalog(mediaCatalog: TweetMediaCatalog) {
Expand Down
19 changes: 7 additions & 12 deletions src/backend/downloads/TwitterMediaFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,15 @@ export const enum DownloadMode {
}

export default class TwitterMediaFile {
readonly screenName: string
readonly tweetId: string
readonly tweetDetail: TweetDetail
readonly src: string
readonly ext: string
readonly name: string
readonly order: number
readonly askPath: boolean

constructor(tweetInfo: TweetInfo, url: string, index = 0, askPath: boolean) {
this.screenName = tweetInfo.screenName
this.tweetId = tweetInfo.tweetId
constructor(tweetDetail: TweetDetail, url: string, index = 0, askPath: boolean) {
this.tweetDetail = tweetDetail
this.ext = path.extname(url)
this.src = this.isVideo() ? url : makeImageOrigSrc(url)
this.name = path.basename(url, this.ext)
Expand Down Expand Up @@ -47,20 +45,17 @@ export default class TwitterMediaFile {
mode: DownloadMode
): Downloads.DownloadOptionsType | Aria2DownloadOption {
const filenameSettingsUseCase = new V4FilenameSettingsUsecase(setting)
const url = this.src
const filename = filenameSettingsUseCase.makeFilename({
account: this.screenName,
tweetId: this.tweetId,
const filename = filenameSettingsUseCase.makeFilename(this.tweetDetail, {
serial: this.order,
hash: this.name,
date: new Date(),
})
const fileFullPath = filenameSettingsUseCase.makeFullPathWithFilenameAndExt(filename, this.ext)
const tweetReferer = `https://twitter.com/i/web/status/${this.tweetId}`
const tweetReferer = `https://twitter.com/i/web/status/${this.tweetDetail.id}`

return mode === DownloadMode.Aria2
? makeAria2DownloadConfig(url, fileFullPath, tweetReferer)
: makeBrowserDownloadConfig(url, fileFullPath, this.askPath)
? makeAria2DownloadConfig(this.src, fileFullPath, tweetReferer)
: makeBrowserDownloadConfig(this.src, fileFullPath, this.askPath)
}

static isValidFileUrl(url: string): boolean {
Expand Down
23 changes: 11 additions & 12 deletions src/backend/downloads/downloadActionUseCase.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import { storageConfig } from '@backend/configurations'
import {
GraphQLTweetUseCase,
ITweetUseCase,
MediaTweetUseCases,
V1TweetUseCase,
V2TweetUseCase,
} from '@backend/twitterApi/useCases'
import { GraphQLTweetUseCase, ITweetUseCase, MediaTweetUseCases, V1TweetUseCase } from '@backend/twitterApi/useCases'
import { addBreadcrumb, captureException } from '@sentry/browser'
import { NotFound, TooManyRequest, TwitterApiError, Unauthorized } from '../errors'
import { FetchErrorNotificationUseCase, InternalErrorNotificationUseCase } from '../notifications/notifyUseCase'
Expand All @@ -24,14 +18,11 @@ const selectTweetUseCase = async (tweetId: string): Promise<ITweetUseCase> => {
case 'v1':
return new V1TweetUseCase(tweetId)

case 'v2':
return new V2TweetUseCase(tweetId)

case 'gql':
return new GraphQLTweetUseCase(tweetId)

default:
return new V2TweetUseCase(tweetId)
return new GraphQLTweetUseCase(tweetId)
}
}

Expand All @@ -47,11 +38,19 @@ export default class DownloadActionUseCase {
level: 'info',
})

const mediaDownloader = await MediaDownloader.build(this.tweetInfo)
const tweetUseCase = await selectTweetUseCase(this.tweetInfo.tweetId)
const mediaTweetUseCase = new MediaTweetUseCases(tweetUseCase)
console.info(`Fetching media info (tweetId: ${this.tweetInfo.tweetId})...`)
const mediaCatelog = await mediaTweetUseCase.fetchMediaCatalog()
const tweet = await tweetUseCase.fetchTweet()
const tweetDetail: TweetDetail = {
id: tweet.id,
userId: tweet.authorId,
createdAt: tweet.createdAt,
displayName: tweet.authorName,
screenName: tweet.authorScreenName,
}
const mediaDownloader = await MediaDownloader.build(tweetDetail)
mediaDownloader.downloadMediasByMediaCatalog(mediaCatelog)
}

Expand Down
13 changes: 13 additions & 0 deletions src/backend/enums.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export enum PatternToken {
Account = '{account}',
AccountId = '{accountId}',
TweetId = '{tweetId}',
Serial = '{serial}',
Hash = '{hash}',
Date = '{date}',
Datetime = '{datetime}',
Timestamp = '{timestamp}',
TweetDate = '{tweetDate}',
TweetDatetime = '{tweetDatetime}',
TweetTimestamp = '{tweetTimestamp}',
}
11 changes: 2 additions & 9 deletions src/backend/settings/filenameSettings/repository.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { PatternToken } from '@backend/enums'
import type { V4FilenameSettings } from '@schema'
import type { Storage } from 'webextension-polyfill'
import { DEFAULT_DIRECTORY } from '../../../constants'
Expand All @@ -21,18 +22,10 @@ const defaultFilenameSettings: FilenameSettings = {
},
}

enum PatternToken {
account = '{account}',
tweetId = '{tweetId}',
serial = '{serial}',
hash = '{serial}',
date = '{date}',
}

const defaultV4FilenameSettings: V4FilenameSettings = {
directory: DEFAULT_DIRECTORY,
noSubDirectory: false,
filenamePattern: [PatternToken.account, PatternToken.tweetId, PatternToken.serial],
filenamePattern: [PatternToken.Account, PatternToken.TweetId, PatternToken.Serial],
}

export class V4FilenameSettingsRepository implements ISettingsRepository<V4FilenameSettings> {
Expand Down
39 changes: 21 additions & 18 deletions src/backend/settings/filenameSettings/usecase.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,41 @@
import { PatternToken } from '@backend/enums'
import type { V4FilenameSettings } from '@schema'
import path from 'path'

export type FileInfo = {
account: string
tweetId: string
serial: number
hash: string
date: Date
}

enum PatternToken {
Account = '{account}',
TweetId = '{tweetId}',
Serial = '{serial}',
Hash = '{hash}',
Date = '{date}',
}
// YYYYMMDDHHMMSS
const makeDatetimeString = (date: Date): string =>
String(date.getFullYear()) +
String(date.getMonth() + 1).padStart(2, '0') +
String(date.getDate()).padStart(2, '0') +
String(date.getHours()).padStart(2, '0') +
String(date.getMinutes()).padStart(2, '0') +
String(date.getSeconds()).padStart(2, '0')

// YYYYMMDD
const makeDateString = (date: Date): string =>
String(date.getFullYear()) + String(date.getMonth() + 1).padStart(2, '0') + String(date.getDate()).padStart(2, '0')

export default class V4FilenameSettingsUsecase {
constructor(readonly settings: V4FilenameSettings) {}

makeFilename({ account, tweetId, serial, hash, date }: FileInfo): string {
const dateString =
String(date.getFullYear()) +
String(date.getMonth() + 1).padStart(2, '0') +
String(date.getDate()).padStart(2, '0')

makeFilename(tweetDetail: TweetDetail, { serial, hash, date }: FileInfo): string {
const filename = this.settings.filenamePattern
.join('-')
.replace(PatternToken.Account, account)
.replace(PatternToken.TweetId, tweetId)
.replace(PatternToken.Account, tweetDetail.screenName)
.replace(PatternToken.TweetId, tweetDetail.id)
.replace(PatternToken.Serial, String(serial).padStart(2, '0'))
.replace(PatternToken.Hash, hash)
.replace(PatternToken.Date, dateString)
.replace(PatternToken.Date, makeDateString(date))
.replace(PatternToken.Datetime, makeDatetimeString(date))
.replace(PatternToken.TweetDate, makeDateString(tweetDetail.createdAt))
.replace(PatternToken.TweetDatetime, makeDatetimeString(tweetDetail.createdAt))
.replace(PatternToken.AccountId, tweetDetail.userId)

return filename
}
Expand Down
Loading

0 comments on commit 55e915a

Please sign in to comment.