Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more filename pattern token to use. #78

Merged
merged 6 commits into from
Jul 5, 2023
Merged
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
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