🐛 Fix(custom): data report

This commit is contained in:
PiEgg
2025-12-22 21:34:10 +08:00
parent a671ea4b26
commit eef736ff57
23 changed files with 332 additions and 74 deletions
+2 -1
View File
@@ -23,4 +23,5 @@ scripts/*.yml
#Electron-builder output
/dist_electron
.serena/
dist/*
dist/*
test.js
+5 -2
View File
@@ -28,6 +28,7 @@
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"@numairawan/video-duration": "^1.0.0",
"@picgo/i18n": "^1.0.0",
"@picgo/store": "^2.1.0",
"axios": "^0.19.0",
@@ -44,19 +45,21 @@
"lodash-id": "^0.14.0",
"lowdb": "^1.0.0",
"marked": "^7.0.4",
"mime-types": "^3.0.2",
"mitt": "^3.0.1",
"multer": "^1.4.5-lts.1",
"picgo": "^1.6.0",
"picgo": "^1.6.2",
"qrcode.vue": "^3.3.3",
"semver": "^7.7.3",
"shell-path": "2.1.0",
"systeminformation": "^5.27.14",
"tunnel": "^0.0.6",
"uuid": "^9.0.0",
"vue": "^3.3.4",
"vue-router": "^4.2.2",
"vue3-lazyload": "^0.3.6",
"vue3-photo-preview": "^0.3.0",
"write-file-atomic": "^4.0.1"
"write-file-atomic": "^7.0.0"
},
"devDependencies": {
"@aws-sdk/client-s3": "^3.276.0",
+69 -23
View File
@@ -11,6 +11,9 @@ importers:
'@element-plus/icons-vue':
specifier: ^2.3.2
version: 2.3.2(vue@3.5.25(typescript@5.9.3))
'@numairawan/video-duration':
specifier: ^1.0.0
version: 1.0.0
'@picgo/i18n':
specifier: ^1.0.0
version: 1.0.0
@@ -59,6 +62,9 @@ importers:
marked:
specifier: ^7.0.4
version: 7.0.5
mime-types:
specifier: ^3.0.2
version: 3.0.2
mitt:
specifier: ^3.0.1
version: 3.0.1
@@ -66,8 +72,8 @@ importers:
specifier: ^1.4.5-lts.1
version: 1.4.5-lts.2
picgo:
specifier: ^1.6.0
version: 1.6.0
specifier: ^1.6.2
version: 1.6.2
qrcode.vue:
specifier: ^3.3.3
version: 3.6.0(vue@3.5.25(typescript@5.9.3))
@@ -77,6 +83,9 @@ importers:
shell-path:
specifier: 2.1.0
version: 2.1.0
systeminformation:
specifier: ^5.27.14
version: 5.27.14
tunnel:
specifier: ^0.0.6
version: 0.0.6
@@ -96,8 +105,8 @@ importers:
specifier: ^0.3.0
version: 0.3.0(vue@3.5.25(typescript@5.9.3))
write-file-atomic:
specifier: ^4.0.1
version: 4.0.2
specifier: ^7.0.0
version: 7.0.0
devDependencies:
'@aws-sdk/client-s3':
specifier: ^3.276.0
@@ -1034,6 +1043,9 @@ packages:
resolution: {integrity: sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==}
engines: {node: ^18.17.0 || >=20.5.0}
'@numairawan/video-duration@1.0.0':
resolution: {integrity: sha512-C819BxippZk2ajzfJt/S8kD5NJqAxuDZc1W4rqHVjzgoUI5Wjo7ehoH4dTIGesUEOxTobIXh6iOKAhhvGEDYpg==}
'@picgo/bump-version@2.0.0':
resolution: {integrity: sha512-7ClH+1f0vNchLOzUq7OlYXic3dIDH154aC809BIwHFkVzoj4z4YPT5TrR1syICFDCJVixg8Tvlli0z891iO0vg==}
engines: {node: '>=20.0.0'}
@@ -1853,6 +1865,9 @@ packages:
axios@0.27.2:
resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
axios@1.13.2:
resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==}
babel-polyfill@6.26.0:
resolution: {integrity: sha512-F2rZGQnAdaHWQ8YAoeRbukc7HS9QgdgeyJ0rQDd485v9opwuPvjpPFcOOT/WmkKTdgy9ESgSPXDcTNpzrGr6iQ==}
@@ -3995,10 +4010,6 @@ packages:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
mime-db@1.50.0:
resolution: {integrity: sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==}
engines: {node: '>= 0.6'}
mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
@@ -4007,14 +4018,14 @@ packages:
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
engines: {node: '>= 0.6'}
mime-types@2.1.33:
resolution: {integrity: sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==}
engines: {node: '>= 0.6'}
mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
mime-types@3.0.2:
resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
engines: {node: '>=18'}
mime@2.6.0:
resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==}
engines: {node: '>=4.0.0'}
@@ -4454,8 +4465,8 @@ packages:
pend@1.2.0:
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
picgo@1.6.0:
resolution: {integrity: sha512-BwWsXfx7LJEFBTCVs1QcEyhmXKqcgSemx0fZpp1Jus/LoEaybLQy8yEdEftKSf9c5BdOYfqqskQfZQ8L1p79hA==}
picgo@1.6.2:
resolution: {integrity: sha512-8o7ldUGHNywDX3keHeuiuSZZHt/hCyHepzzGA3Y3OdAZLIWdHI7NRjgUIQ2fsspQnkKaB4bm5xphoENhDiHuig==}
engines: {node: '>= 20.0.0'}
hasBin: true
@@ -4591,6 +4602,9 @@ packages:
proto-list@1.2.4:
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
pseudomap@1.0.2:
resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==}
@@ -5189,6 +5203,12 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
systeminformation@5.27.14:
resolution: {integrity: sha512-3DoNDYSZBLxBwaJtQGWNpq0fonga/VZ47HY1+7/G3YoIPaPz93Df6egSzzTKbEMmlzUpy3eQ0nR9REuYIycXGg==}
engines: {node: '>=8.0.0'}
os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android]
hasBin: true
tailwindcss@3.4.18:
resolution: {integrity: sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==}
engines: {node: '>=14.0.0'}
@@ -5619,6 +5639,10 @@ packages:
resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
write-file-atomic@7.0.0:
resolution: {integrity: sha512-YnlPC6JqnZl6aO4uRc+dx5PHguiR9S6WeoLtpxNT9wIG+BDya7ZNE1q7KOjVgaA73hKhKLpVPgJ5QA9THQ5BRg==}
engines: {node: ^20.17.0 || >=22.9.0}
xml-name-validator@4.0.0:
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
engines: {node: '>=12'}
@@ -6862,6 +6886,13 @@ snapshots:
dependencies:
semver: 7.7.3
'@numairawan/video-duration@1.0.0':
dependencies:
axios: 1.13.2
mime-types: 2.1.35
transitivePeerDependencies:
- debug
'@picgo/bump-version@2.0.0(@types/node@20.19.26)(typescript@5.9.3)':
dependencies:
'@commitlint/cli': 7.6.1
@@ -7895,6 +7926,14 @@ snapshots:
transitivePeerDependencies:
- debug
axios@1.13.2:
dependencies:
follow-redirects: 1.15.11
form-data: 4.0.5
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
babel-polyfill@6.26.0:
dependencies:
babel-runtime: 6.26.0
@@ -9415,7 +9454,7 @@ snapshots:
combined-stream: 1.0.8
es-set-tostringtag: 2.1.0
hasown: 2.0.2
mime-types: 2.1.33
mime-types: 2.1.35
formstream@1.5.2:
dependencies:
@@ -10431,20 +10470,18 @@ snapshots:
braces: 3.0.3
picomatch: 2.3.1
mime-db@1.50.0: {}
mime-db@1.52.0: {}
mime-db@1.54.0: {}
mime-types@2.1.33:
dependencies:
mime-db: 1.50.0
mime-types@2.1.35:
dependencies:
mime-db: 1.52.0
mime-types@3.0.2:
dependencies:
mime-db: 1.54.0
mime@2.6.0: {}
mimic-fn@1.2.0: {}
@@ -10862,7 +10899,7 @@ snapshots:
pend@1.2.0: {}
picgo@1.6.0:
picgo@1.6.2:
dependencies:
'@picgo/i18n': 1.0.0
'@picgo/store': 2.1.0
@@ -10883,7 +10920,7 @@ snapshots:
js-yaml: 4.1.1
lodash: 4.17.21
md5: 2.3.0
mime-types: 2.1.33
mime-types: 3.0.2
minimatch: 3.1.2
minimist: 1.2.8
qiniu: 7.14.0
@@ -10996,6 +11033,8 @@ snapshots:
proto-list@1.2.4: {}
proxy-from-env@1.1.0: {}
pseudomap@1.0.2: {}
pump@3.0.3:
@@ -11672,6 +11711,8 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
systeminformation@5.27.14: {}
tailwindcss@3.4.18:
dependencies:
'@alloc/quick-lru': 5.2.0
@@ -12138,6 +12179,11 @@ snapshots:
imurmurhash: 0.1.4
signal-exit: 3.0.7
write-file-atomic@7.0.0:
dependencies:
imurmurhash: 0.1.4
signal-exit: 4.1.0
xml-name-validator@4.0.0: {}
xmlbuilder@15.1.1: {}
+2 -3
View File
@@ -10,14 +10,13 @@ import axios from 'axios'
import windowManager from '../window/windowManager'
import { showNotification } from '~/main/utils/common'
import { isDev } from '~/universal/utils/common'
import { STORE_PATH } from '~/main/utils/env'
// for test
const REMOTE_NOTICE_URL = isDev ? 'http://localhost:8181/remote-notice.json' : 'https://picgo-1251750343.cos.accelerate.myqcloud.com/remote-notice.yml'
const REMOTE_NOTICE_URL = isDev ? 'http://localhost:8181/remote-notice.json' : 'https://release.picgo.app/remote-notice.yml'
const REMOTE_NOTICE_LOCAL_STORAGE_FILE = 'picgo-remote-notice.json'
const STORE_PATH = app.getPath('userData')
const REMOTE_NOTICE_LOCAL_STORAGE_PATH = path.join(STORE_PATH, REMOTE_NOTICE_LOCAL_STORAGE_FILE)
class RemoteNoticeHandler {
+1 -1
View File
@@ -58,7 +58,7 @@ export const uploadClipboardFiles = async (): Promise<string> => {
}
}
export const uploadChoosedFiles = async (webContents: WebContents, files: IFileWithPath[]): Promise<string[]> => {
export const uploadSelectedFiles = async (webContents: WebContents, files: IFileWithPath[]): Promise<string[]> => {
const input = files.map(item => item.path)
const imgs = await uploader.setWebContents(webContents).upload(input)
const result = []
+8 -22
View File
@@ -11,9 +11,9 @@ import db from '~/main/apis/core/datastore'
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from '#/types/enum'
import util from 'util'
import { IPicGo } from 'picgo'
import { showNotification, calcDurationRange, getClipboardFilePathList } from '~/main/utils/common'
import { GET_RENAME_FILE_NAME, RENAME_FILE_NAME, TALKING_DATA_EVENT } from '~/universal/events/constants'
import type { IPicGo } from 'picgo'
import { showNotification, getClipboardFilePathList } from '~/main/utils/common'
import { GET_RENAME_FILE_NAME, RENAME_FILE_NAME } from '~/universal/events/constants'
import logger from '@core/picgo/logger'
import { T } from '~/main/i18n'
import fse from 'fs-extra'
@@ -23,6 +23,7 @@ import writeFile from 'write-file-atomic'
import { CLIPBOARD_IMAGE_FOLDER } from '~/universal/utils/static'
import { cleanupFormUploaderFiles } from '~/main/utils/cleanupFormUploaderFiles'
import { IpcMainEvent } from 'electron/main'
import { dataReportManager } from '~/main/utils/dataReport'
const waitForRename = (window: BrowserWindow, id: number): Promise<string|null> => {
return new Promise((resolve) => {
@@ -39,20 +40,6 @@ const waitForRename = (window: BrowserWindow, id: number): Promise<string|null>
})
}
const handleTalkingData = (webContents: WebContents, options: IAnalyticsData) => {
const data: ITalkingDataOptions = {
EventId: 'upload',
Label: options.type,
MapKv: {
by: options.fromClipboard ? 'clipboard' : 'files', // 上传剪贴板图片还是选择的文文件
count: options.count, // 上传的数量
duration: calcDurationRange(options.duration || 0), // 上传耗时
type: options.type
}
}
webContents.send(TALKING_DATA_EVENT, data)
}
class Uploader {
private webContents: WebContents | null = null
// private uploading: boolean = false
@@ -155,12 +142,11 @@ class Uploader {
const output = await picgo.upload(img)
if (Array.isArray(output) && output.some((item: ImgInfo) => item.imgUrl)) {
if (this.webContents) {
handleTalkingData(this.webContents, {
dataReportManager.reportUploadData(this.webContents, {
fromClipboard: !img,
type: db.get('picBed.uploader') || db.get('picBed.current') || 'smms',
count: img ? img.length : 1,
duration: Date.now() - startTime
} as IAnalyticsData)
duration: Date.now() - startTime,
outputList: output
});
}
return output.filter(item => item.imgUrl)
} else {
+2 -2
View File
@@ -5,7 +5,7 @@ import {
UPLOAD_WITH_CLIPBOARD_FILES,
UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE,
GET_WINDOW_ID,
GET_WINDOW_ID_REPONSE,
GET_WINDOW_ID_RESPONSE,
GET_SETTING_WINDOW_ID,
GET_SETTING_WINDOW_ID_RESPONSE
} from './constants'
@@ -56,7 +56,7 @@ export const uploadWithFiles = (pathList: IFileWithPath[]): Promise<{
// miniWindow or settingWindow or trayWindow
export const getWindowId = (): Promise<number> => {
return new Promise((resolve) => {
bus.once(GET_WINDOW_ID_REPONSE, (id: number) => {
bus.once(GET_WINDOW_ID_RESPONSE, (id: number) => {
resolve(id)
})
bus.emit(GET_WINDOW_ID)
+1 -1
View File
@@ -1,5 +1,5 @@
export const GET_WINDOW_ID = 'GET_WINDOW_ID' // get a current window
export const GET_WINDOW_ID_REPONSE = 'GET_WINDOW_ID_REPONSE'
export const GET_WINDOW_ID_RESPONSE = 'GET_WINDOW_ID_RESPONSE'
export const GET_SETTING_WINDOW_ID = 'GET_SETTING_WINDOW_ID' // get setting window
export const GET_SETTING_WINDOW_ID_RESPONSE = 'GET_SETTING_WINDOW_ID_RESPONSE'
export const UPLOAD_WITH_FILES = 'UPLOAD_WITH_FILES'
+1 -2
View File
@@ -1,12 +1,11 @@
import fs from 'fs-extra'
import writeFile from 'write-file-atomic'
import path from 'path'
import { app as APP } from 'electron'
import { getLogger } from '@core/utils/localLogger'
import dayjs from 'dayjs'
import { T } from '~/main/i18n'
import { FORM_IMAGE_FOLDER } from '~/universal/utils/static'
const STORE_PATH = APP.getPath('userData')
import { STORE_PATH } from '~/main/utils/env'
const configFilePath = path.join(STORE_PATH, 'data.json')
const configFileBackupPath = path.join(STORE_PATH, 'data.bak.json')
export const defaultConfigPath = configFilePath
+4 -4
View File
@@ -1,7 +1,7 @@
import bus from '@core/bus'
import {
uploadClipboardFiles,
uploadChoosedFiles
uploadSelectedFiles
} from 'apis/app/uploader/apis'
import {
createMenu
@@ -14,7 +14,7 @@ import {
UPLOAD_WITH_CLIPBOARD_FILES,
UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE,
GET_WINDOW_ID,
GET_WINDOW_ID_REPONSE,
GET_WINDOW_ID_RESPONSE,
GET_SETTING_WINDOW_ID,
GET_SETTING_WINDOW_ID_RESPONSE,
CREATE_APP_MENU
@@ -39,13 +39,13 @@ async function busCallUploadClipboardFiles () {
async function busCallUploadFiles (pathList: IFileWithPath[]) {
const win = windowManager.getAvailableWindow()
const urls = await uploadChoosedFiles(win.webContents, pathList)
const urls = await uploadSelectedFiles(win.webContents, pathList)
bus.emit(UPLOAD_WITH_FILES_RESPONSE, urls)
}
function busCallGetWindowId () {
const win = windowManager.getAvailableWindow()
bus.emit(GET_WINDOW_ID_REPONSE, win.id)
bus.emit(GET_WINDOW_ID_RESPONSE, win.id)
}
function busCallGetSettingWindowId () {
+3 -4
View File
@@ -31,15 +31,14 @@ import {
} from '#/events/constants'
import {
uploadClipboardFiles,
uploadChoosedFiles
uploadSelectedFiles
} from '~/main/apis/app/uploader/apis'
import picgoCoreIPC from './picgoCoreIPC'
import { handleCopyUrl } from '~/main/utils/common'
import { buildMainPageMenu, buildMiniPageMenu, buildPluginPageMenu, buildPicBedListMenu } from './remotes/menu'
import path from 'path'
import { T } from '~/main/i18n'
const STORE_PATH = app.getPath('userData')
import { STORE_PATH } from '~/main/utils/env'
export default {
listen () {
@@ -74,7 +73,7 @@ export default {
})
ipcMain.on('uploadChoosedFiles', async (evt: IpcMainEvent, files: IFileWithPath[]) => {
return uploadChoosedFiles(evt.sender, files)
return uploadSelectedFiles(evt.sender, files)
})
ipcMain.on('updateShortKey', (evt: IpcMainEvent, item: IShortKeyConfig, oldKey: string, from: string) => {
+2 -2
View File
@@ -16,7 +16,7 @@ import {
migrateGalleryFromVersion230
} from '~/main/migrate'
import {
uploadChoosedFiles,
uploadSelectedFiles,
uploadClipboardFiles
} from 'apis/app/uploader/apis'
import {
@@ -48,7 +48,7 @@ const handleStartUpFiles = (argv: string[], cwd: string) => {
} else {
logger.info('cli -> uploading files from cli', ...files.map(item => item.path))
const win = windowManager.getAvailableWindow()
uploadChoosedFiles(win.webContents, files)
uploadSelectedFiles(win.webContents, files)
}
return true
} else {
+2 -2
View File
@@ -4,7 +4,7 @@ import {
} from './utils'
import logger from '@core/picgo/logger'
import windowManager from 'apis/app/window/windowManager'
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
import { uploadSelectedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
import path from 'path'
import { dbPathDir } from 'apis/core/datastore/dbChecker'
const STORE_PATH = dbPathDir()
@@ -51,7 +51,7 @@ router.post('/upload', async ({
}
})
const win = windowManager.getAvailableWindow()
const res = await uploadChoosedFiles(win.webContents, pathList)
const res = await uploadSelectedFiles(win.webContents, pathList)
logger.info('[PicGo Server] upload result', res.join(' ; '))
if (res.length) {
handleResponse({
+44 -1
View File
@@ -3,6 +3,7 @@ import db from '~/main/apis/core/datastore'
import { clipboard, Notification, dialog } from 'electron'
import { handleUrlEncode } from '~/universal/utils/common'
import { readClipboardFilePaths } from 'clip-filepaths'
import crypto from 'node:crypto'
export const handleCopyUrl = (str: string): void => {
if (db.get('settings.autoCopyUrl') !== false) {
@@ -54,7 +55,7 @@ export const showMessageBox = (options: any) => {
})
}
export const calcDurationRange = (duration: number) => {
export const calcUploadProcessDurationRange = (duration: number) => {
if (duration < 1000) {
return 500
} else if (duration < 1500) {
@@ -78,6 +79,44 @@ export const calcDurationRange = (duration: number) => {
return 100000
}
// 1 2 3 4 5 6 7 8 9 10 20 30 40 50 60 70 80 90 100 200 300 ...
export const calcUploadBigFileSizeRange = (fileSizeMB: number) => {
if (fileSizeMB < 10) {
// 3.2 -> 3, 3.6 -> 4
const result = Math.round(fileSizeMB);
return result === 0 && fileSizeMB > 0 ? 1 : result;
}
else if (fileSizeMB < 100) {
// 13 -> 1.3 -> 1 -> 10
// 17 -> 1.7 -> 2 -> 20
return Math.round(fileSizeMB / 10) * 10;
}
else {
// 135 -> 1.35 -> 1 -> 100
// 160 -> 1.60 -> 2 -> 200
return Math.round(fileSizeMB / 100) * 100;
}
}
// 1 2 3 4 5 6 7 8 9 10 20 30 40 50 60 70 80 90 100 200 300 ...
export const calcVideoDurationRange = (durationSec: number) => {
if (durationSec < 10) {
// 3.2 -> 3, 3.6 -> 4
const result = Math.round(durationSec);
return result === 0 && durationSec > 0 ? 1 : result;
}
else if (durationSec < 100) {
// 13 -> 1.3 -> 1 -> 10
// 17 -> 1.7 -> 2 -> 20
return Math.round(durationSec / 10) * 10;
}
else {
// 135 -> 1.35 -> 1 -> 100
// 160 -> 1.60 -> 2 -> 200
return Math.round(durationSec / 100) * 100;
}
}
/**
* macOS public.file-url will get encoded file path,
* so we need to decode it
@@ -137,3 +176,7 @@ export const getHost = (url: string = '') => {
export const removeProtocolAndSuffix = (url: string = '') => {
return url.replace(/^(https?:\/\/)?/, '').replace(/\/$/, '')
}
export const md5 = (str: string): string => {
return crypto.createHash('md5').update(str).digest('hex')
}
+2
View File
@@ -0,0 +1,2 @@
export const MB = 1024 * 1024
export const SECOND = 1000
+91
View File
@@ -0,0 +1,91 @@
import { ipcMain, IpcMainEvent, type WebContents } from "electron";
import { deviceIdManager } from "./deviceId";
import { REGISTER_DEVICE_ID, TALKING_DATA_EVENT } from "~/universal/events/constants";
import type { IImgInfo } from "picgo";
import { calcUploadBigFileSizeRange, calcUploadProcessDurationRange, calcVideoDurationRange } from "./common";
import { app } from "electron/main";
import db from "~/main/apis/core/datastore";
import fs from 'fs-extra'
// @ts-ignore
import { videoDuration } from "@numairawan/video-duration";
import { MB, SECOND } from "./constants";
export interface IReportUploadDataOptions {
fromClipboard: boolean;
duration: number;
outputList: IImgInfo[];
}
class DataReportManager {
private deviceId: string | null = null;
private hasRegisterDeviceID: boolean = false;
constructor () {
this.init()
this.handleRegisterDeviceID()
}
private handleRegisterDeviceID() {
ipcMain.once(REGISTER_DEVICE_ID, (_evt: IpcMainEvent) => {
this.hasRegisterDeviceID = true;
console.log('Device ID registered');
});
}
private async init () {
if (this.deviceId) return;
this.deviceId = await deviceIdManager.getId()
}
public async reportUploadData(webContents: WebContents, options: IReportUploadDataOptions) {
await this.init();
await this.registerDeviceID(webContents);
const { fromClipboard, duration, outputList } = options;
const fileList = outputList.map(item => {
return {
fileName: item.fileName,
filePath: item.filePath,
mimeType: item.mimeType
}
})
const uploadEventData: ITalkingDataOptions = {
EventId: 'upload',
Label: '',
MapKv: {
by: fromClipboard ? 'clipboard' : 'files', // 上传剪贴板图片还是选择的文文件
count: fileList.length, // 上传的数量
duration: calcUploadProcessDurationRange(duration || 0), // 上传耗时
type: db.get('picBed.uploader') || db.get('picBed.current') || 'smms',
deviceId: this.deviceId,
version: app.getVersion()
}
}
webContents.send(TALKING_DATA_EVENT, uploadEventData)
fileList.forEach(async file => {
if (file?.mimeType?.startsWith('video/') && file.filePath) {
const [stats, metadata] = await Promise.all([
fs.stat(file.filePath),
videoDuration(file.filePath)
]);
// report video duration and size range
const sizeMB = stats.size / MB;
const durationSec = (metadata.ms / SECOND) || 0;
const videoEventData: ITalkingDataOptions = {
EventId: 'upload_video',
Label: '',
MapKv: {
deviceId: this.deviceId,
sizeRange: calcUploadBigFileSizeRange(sizeMB),
durationRange: calcVideoDurationRange(durationSec),
version: app.getVersion()
}
};
webContents.send(TALKING_DATA_EVENT, videoEventData);
}
});
}
public async registerDeviceID(webContents: WebContents) {
if (this.hasRegisterDeviceID) return;
await this.init();
webContents.send(REGISTER_DEVICE_ID, this.deviceId);
}
}
export const dataReportManager = new DataReportManager()
+62
View File
@@ -0,0 +1,62 @@
import { networkInterfaceDefault } from 'systeminformation'
import { md5 } from './common'
import writeFile from 'write-file-atomic'
import { DEVICE_ID_PATH } from './env'
import fs from 'fs-extra'
class DeviceIdManager {
private deviceId: string | null = null
constructor() {
this.init()
}
private async init() {
await this.loadDeviceId()
}
private async getDeviceIdWithFallback(): Promise<string> {
try {
if (fs.existsSync(DEVICE_ID_PATH)) {
const deviceId = await fs.readFile(DEVICE_ID_PATH, 'utf-8')
if (deviceId && deviceId?.trim().length > 0) {
return deviceId.trim()
}
}
const netInterfaceMac = await networkInterfaceDefault()
if (netInterfaceMac) {
return md5(netInterfaceMac)
} else {
// random fallback
return md5(`${new Date().getTime()}-${Math.random().toString(36).substring(2, 15)}`)
}
} catch (error) {
// random fallback
return md5(`${new Date().getTime()}-${Math.random().toString(36).substring(2, 15)}`)
}
}
private async saveDeviceId(id: string): Promise<void> {
try {
await writeFile(DEVICE_ID_PATH, id, { encoding: 'utf-8' })
} catch (error) {
console.error('Failed to save device ID:', error)
}
}
private async loadDeviceId(): Promise<string> {
this.deviceId = await this.getDeviceIdWithFallback()
await this.saveDeviceId(this.deviceId)
return this.deviceId
}
public async getId(): Promise<string> {
if (this.deviceId) {
return this.deviceId
}
this.deviceId = await this.loadDeviceId()
return this.deviceId
}
}
export const deviceIdManager = new DeviceIdManager()
+4
View File
@@ -29,3 +29,7 @@ export const buildRendererUrl = (hash?: string) => {
}
export const getStaticPath = () => process.env.STATIC_PATH || defaultStaticPath
// paths
export const STORE_PATH = app.getPath('userData')
export const DEVICE_ID_PATH = path.join(STORE_PATH, 'picgo-device-id')
-1
View File
@@ -31,7 +31,6 @@ app.config.globalProperties.$builtInPicBed = [
'aliyun',
'github'
]
app.config.unwrapInjectedRef = true
app.config.globalProperties.$$db = db
app.config.globalProperties.$http = axios
+1 -1
View File
@@ -66,7 +66,7 @@ import { reactive, ref, onBeforeUnmount, onBeforeMount } from 'vue'
import { ipcRenderer } from 'electron'
import $$db from '@/utils/db'
import { T as $T } from '@/i18n/index'
import { IResult } from '@picgo/store/dist/types'
import type { IResult } from '@picgo/store/dist/types'
import { PASTE_TEXT, OPEN_WINDOW } from '#/events/constants'
import { IWindowList } from '#/types/enum'
import { sendToMain } from '@/utils/dataSender'
+16 -2
View File
@@ -1,5 +1,6 @@
import {
TALKING_DATA_APPID, TALKING_DATA_EVENT
REGISTER_DEVICE_ID,
TALKING_DATA_APPID, TALKING_DATA_DEVICE_ID_EVENT, TALKING_DATA_EVENT
} from '~/universal/events/constants'
import pkg from 'root/package.json'
import { ipcRenderer } from 'electron'
@@ -10,7 +11,7 @@ export const initTalkingData = () => {
setTimeout(() => {
const talkingDataScript = document.createElement('script')
talkingDataScript.src = `http://sdk.talkingdata.com/app/h5/v1?appid=${TALKING_DATA_APPID}&vn=${version}&vc=${version}`
talkingDataScript.src = `https://jic.talkingdata.com/app/h5/v1?appid=${TALKING_DATA_APPID}&vn=${version}&vc=${version}`
const head = document.getElementsByTagName('head')[0]
head.appendChild(talkingDataScript)
@@ -20,3 +21,16 @@ export const initTalkingData = () => {
ipcRenderer.on(TALKING_DATA_EVENT, (_, data: ITalkingDataOptions) => {
handleTalkingDataEvent(data)
})
// 0:ANONYMOUS,匿名账号;
// 1:REGISTERED,自有帐户显性注册;
ipcRenderer.on(TALKING_DATA_DEVICE_ID_EVENT, (_, deviceId: string) => {
window.TDAPP.register({
profileId: deviceId,
profileType: 1
})
window.TDAPP.login({
profileId: deviceId,
profileType: 1
})
ipcRenderer.send(REGISTER_DEVICE_ID)
});
+2
View File
@@ -3,6 +3,7 @@ export const SHOW_INPUT_BOX_RESPONSE = 'SHOW_INPUT_BOX_RESPONSE'
export const TOGGLE_SHORTKEY_MODIFIED_MODE = 'TOGGLE_SHORTKEY_MODIFIED_MODE'
export const TALKING_DATA_APPID = '7E6832BCE3F1438696579E541DFEBFDA'
export const TALKING_DATA_EVENT = 'TALKING_DATA_EVENT'
export const TALKING_DATA_DEVICE_ID_EVENT = 'TALKING_DATA_DEVICE_ID_EVENT'
export const SHOW_PRIVACY_MESSAGE = 'SHOW_PRIVACY_MESSAGE'
export const PICGO_SAVE_CONFIG = 'PICGO_SAVE_CONFIG'
export const PICGO_GET_CONFIG = 'PICGO_GET_CONFIG'
@@ -37,6 +38,7 @@ export const OPEN_WINDOW = 'OPEN_WINDOW'
export const GET_PICBEDS = 'GET_PICBEDS'
export const RPC_ACTIONS = 'RPC_ACTIONS'
export const GET_PICBED_CONFIG = 'GET_PICBED_CONFIG'
export const REGISTER_DEVICE_ID = 'REGISTER_DEVICE_ID'
// i18n
export const GET_CURRENT_LANGUAGE = 'GET_CURRENT_LANGUAGE'
export const GET_LANGUAGE_LIST = 'GET_LANGUAGE_LIST'
+8
View File
@@ -19,6 +19,14 @@ declare global {
electronApi: ElectronApi
TDAPP: {
onEvent: (EventId: string, Label?: string, MapKv?: IStringKeyMap) => void
register: (opt: {
profileId: string,
profileType: number,
}) => void
login: (opt: {
profileId: string,
profileType: number,
}) => void
}
}
}