diff --git a/electron-app/hasher.js b/electron-app/hasher.js new file mode 100644 index 00000000..8ae9819d --- /dev/null +++ b/electron-app/hasher.js @@ -0,0 +1,40 @@ +const path = require('path'); +const fs = require('fs'); +const crypto = require('crypto'); +const { version } = require('./package.json'); +const { parse, stringify } = require('yaml'); + +const BASE_PATH = path.join(__dirname, 'release'); +const SETUP_PATH = path.join(BASE_PATH, `postybirb-plus-setup-${version}.exe`); +const YAML_PATH = path.join(BASE_PATH, `latest.yml`); + +function hashFile(file, algorithm = 'sha512', encoding = 'base64', options) { + return new Promise((resolve, reject) => { + const hash = crypto.createHash(algorithm); + hash.on('error', reject).setEncoding(encoding); + fs.createReadStream( + file, + Object.assign({}, options, { + highWaterMark: 1024 * 1024, + /* better to use more memory but hash faster */ + }), + ) + .on('error', reject) + .on('end', () => { + hash.end(); + resolve(hash.read()); + }) + .pipe(hash, { + end: false, + }); + }); +} + +hashFile(SETUP_PATH).then(hash => { + console.log(hash); + const yaml = parse(fs.readFileSync(YAML_PATH, 'utf8')); + yaml.sha512 = hash; + yaml.files[0].sha512 = hash; + console.log(stringify(yaml)); + fs.writeFileSync(YAML_PATH, stringify(yaml)); +}); diff --git a/electron-app/package-lock.json b/electron-app/package-lock.json index 312e42ce..86b6afee 100644 --- a/electron-app/package-lock.json +++ b/electron-app/package-lock.json @@ -1,12 +1,12 @@ { "name": "postybirb-plus", - "version": "3.1.44", + "version": "3.1.46", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "postybirb-plus", - "version": "3.1.44", + "version": "3.1.46", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { @@ -81,7 +81,8 @@ "ts-jest": "^26.5.6", "ts-loader": "^8.3.0", "ts-node": "^9.0.0", - "tsconfig-paths": "^3.11.0" + "tsconfig-paths": "^3.11.0", + "yaml": "^2.5.0" } }, "../commons": { @@ -7512,15 +7513,6 @@ "node": ">= 8" } }, - "node_modules/@react-native-community/cli-doctor/node_modules/yaml": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", - "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", - "peer": true, - "engines": { - "node": ">= 14" - } - }, "node_modules/@react-native-community/cli-hermes": { "version": "11.3.5", "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-11.3.5.tgz", @@ -11934,6 +11926,15 @@ "node": ">=8" } }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/crc": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", @@ -27252,12 +27253,14 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", + "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", + "bin": { + "yaml": "bin.mjs" + }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/yargs": { @@ -33035,12 +33038,6 @@ "requires": { "isexe": "^2.0.0" } - }, - "yaml": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", - "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", - "peer": true } } }, @@ -36499,6 +36496,14 @@ "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.7.2" + }, + "dependencies": { + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + } } }, "crc": { @@ -48659,10 +48664,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", + "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==" }, "yargs": { "version": "15.4.1", diff --git a/electron-app/package.json b/electron-app/package.json index 1092835f..909c05e8 100644 --- a/electron-app/package.json +++ b/electron-app/package.json @@ -1,6 +1,6 @@ { "name": "postybirb-plus", - "version": "3.1.45", + "version": "3.1.46", "description": "(ClientServer) PostyBirb is an application that helps artists post art and other multimedia to multiple websites more quickly.", "main": "dist/main.js", "author": "Michael DiCarlo", @@ -18,6 +18,7 @@ "build:linux": "electron-builder -l", "build:osx": "electron-builder -m", "build:windows": "electron-builder -w", + "build:rehash": "node hasher.js", "build:release": "export $(cat .env | xargs) && electron-builder -mwl -p always", "release:windows": "electron-builder -w -p always", "release:linux": "electron-builder -l -p always", @@ -108,7 +109,8 @@ "ts-jest": "^26.5.6", "ts-loader": "^8.3.0", "ts-node": "^9.0.0", - "tsconfig-paths": "^3.11.0" + "tsconfig-paths": "^3.11.0", + "yaml": "^2.5.0" }, "build": { "appId": "com.lemonynade.postybirb.plus", @@ -144,9 +146,11 @@ ] }, "nsis": { + "artifactName": "postybirb-plus-setup-${version}.${ext}", "deleteAppDataOnUninstall": true }, "win": { + "artifactName": "postybirb-plus-${version}.${ext}", "publisherName": [ "Michael DiCarlo" ], diff --git a/electron-app/src/server/websites/deviant-art/deviant-art.service.ts b/electron-app/src/server/websites/deviant-art/deviant-art.service.ts index 91140a6f..0816146b 100644 --- a/electron-app/src/server/websites/deviant-art/deviant-art.service.ts +++ b/electron-app/src/server/websites/deviant-art/deviant-art.service.ts @@ -23,6 +23,7 @@ import { ValidationParts } from 'src/server/submission/validator/interfaces/vali import BrowserWindowUtil from 'src/server/utils/browser-window.util'; import FileSize from 'src/server/utils/filesize.util'; import FormContent from 'src/server/utils/form-content.util'; +import { HttpExperimental } from 'src/server/utils/http-experimental'; import WebsiteValidator from 'src/server/utils/website-validator.util'; import { GenericAccountProp } from '../generic/generic-account-props.enum'; import { LoginResponse } from '../interfaces/login-response.interface'; @@ -66,17 +67,13 @@ export class DeviantArt extends Website { async checkLoginStatus(data: UserAccountEntity): Promise { const status: LoginResponse = { loggedIn: false, username: null }; - const res = await Http.get(this.BASE_URL, data._id); - if (!res.body.includes('https://www.deviantart.com/users/login')) { + const res = await HttpExperimental.get(this.BASE_URL, { partition: data._id }); + const cookies = await Http.getWebsiteCookies(data._id, this.BASE_URL); + const userInfoCookie = cookies.find(c => c.name === 'userinfo'); + if (userInfoCookie) { status.loggedIn = true; - status.username = res.body.match(/data-username="(\w+)"/)[1]; - - const csrf = res.body.match(/window.__CSRF_TOKEN__ = '(.*)'/)?.[1]; - if (csrf) { - await this.getFolders(data._id, status.username); - } else { - this.logger.warn('Could not find CSRF token for DeviantArt to retrieve folders.'); - } + status.username = JSON.parse(decodeURIComponent(userInfoCookie.value).split(';')[1]).username; + await this.getFolders(data._id, status.username); } return status; @@ -85,16 +82,13 @@ export class DeviantArt extends Website { private async getFolders(profileId: string, username: string) { try { const csrf = await this.getCSRF(profileId); - const res = await Http.get<{ results: DeviantArtFolder[] }>( + const res = await HttpExperimental.get<{ results: DeviantArtFolder[] }>( `${ this.BASE_URL }/_puppy/dashared/gallection/folders?offset=0&limit=250&type=gallery&with_all_folder=true&with_permissions=true&username=${encodeURIComponent( username, )}&da_minor_version=20230710&csrf_token=${csrf}`, - profileId, - { - requestOptions: { json: true }, - }, + { partition: profileId }, ); const folders: Folder[] = []; res.body.results.forEach((f: DeviantArtFolder) => { @@ -126,7 +120,7 @@ export class DeviantArt extends Website { } private async getCSRF(profileId: string) { - const url = await Http.get(this.BASE_URL, profileId); + const url = await HttpExperimental.get(this.BASE_URL, { partition: profileId }); return url.body.match(/window.__CSRF_TOKEN__ = '(.*)'/)?.[1]; } @@ -135,7 +129,7 @@ export class DeviantArt extends Website { data: FilePostData, accountData: DeviantArtAccountData, ): Promise { - const fileUpload = await Http.post<{ + const fileUpload = await HttpExperimental.post<{ deviationId: number; status: string; stashId: number; @@ -143,7 +137,8 @@ export class DeviantArt extends Website { size: number; cursor: string; title: string; - }>(`${this.BASE_URL}/_puppy/dashared/deviation/submit/upload/deviation`, data.part.accountId, { + }>(`${this.BASE_URL}/_puppy/dashared/deviation/submit/upload/deviation`, { + partition: data.part.accountId, type: 'multipart', data: { upload_file: data.primary.file, @@ -152,7 +147,6 @@ export class DeviantArt extends Website { da_minor_version: '20230710', csrf_token: await this.getCSRF(data.part.accountId), }, - requestOptions: { json: true }, }); if (fileUpload.body.status !== 'success') { @@ -207,7 +201,7 @@ export class DeviantArt extends Website { subject_tags: '_empty', tags: this.formatTags(data.tags), tierids: '_empty', - title: data.title, + title: this.truncateTitle(data.title), csrf_token: await this.getCSRF(data.part.accountId), }; @@ -247,18 +241,18 @@ export class DeviantArt extends Website { ); } - const publish = await Http.post<{ + const publish = await HttpExperimental.post<{ status: string; url: string; deviationId: number; - }>(`${this.BASE_URL}/_puppy/dashared/deviation/publish`, data.part.accountId, { + }>(`${this.BASE_URL}/_puppy/dashared/deviation/publish`, { + partition: data.part.accountId, type: 'json', data: { stashid: fileUpload.body.deviationId, da_minor_version: 20230710, csrf_token: await this.getCSRF(data.part.accountId), }, - requestOptions: { json: true }, }); if (publish.body.status !== 'success') { @@ -299,15 +293,15 @@ export class DeviantArt extends Website { title: data.title, }; - const create = await Http.post<{ + const create = await HttpExperimental.post<{ deviation: { deviationId: number; url: string; }; - }>(`${this.BASE_URL}/_napi/shared_api/journal/create`, data.part.accountId, { + }>(`${this.BASE_URL}/_napi/shared_api/journal/create`, { + partition: data.part.accountId, type: 'json', data: form, - requestOptions: { json: true }, }); if (!create.body.deviation?.deviationId) { @@ -319,12 +313,13 @@ export class DeviantArt extends Website { ); } - const publish = await Http.post<{ + const publish = await HttpExperimental.post<{ deviation: { deviationId: number; url: string; }; - }>(`${this.BASE_URL}/_puppy/dashared/journal/publish`, data.part.accountId, { + }>(`${this.BASE_URL}/_puppy/dashared/journal/publish`, { + partition: data.part.accountId, type: 'json', data: { deviationid: create.body.deviation.deviationId, @@ -332,7 +327,6 @@ export class DeviantArt extends Website { csrf_token: await this.getCSRF(data.part.accountId), featured: true, }, - requestOptions: { json: true }, }); if (!publish.body.deviation?.deviationId) { @@ -347,6 +341,12 @@ export class DeviantArt extends Website { return this.createPostResponse({ source: publish.body.deviation.url }); } + private titleLimit = 50; + private truncateTitle(title: string) { + const newTitle = title.substring(0, this.titleLimit); + return { title: newTitle, exceedsLimit: newTitle !== title }; + } + validateFileSubmission( submission: FileSubmission, submissionPart: SubmissionPart, @@ -356,9 +356,11 @@ export class DeviantArt extends Website { const warnings: string[] = []; const isAutoscaling: boolean = submissionPart.data.autoScale; - const title = submissionPart.data.title || defaultPart.data.title || submission.title; - if (title.length > 50) { - warnings.push(`Title will be truncated to 50 characters: ${title.substring(0, 50)}`); + const { title, exceedsLimit } = this.truncateTitle( + submissionPart.data.title || defaultPart.data.title || submission.title, + ); + if (exceedsLimit) { + warnings.push(`Title will be truncated to ${this.titleLimit} characters: ${title}`); } if (submissionPart.data.folders && submissionPart.data.folders.length) { @@ -369,7 +371,7 @@ export class DeviantArt extends Website { ); submissionPart.data.folders.forEach(f => { if (!WebsiteValidator.folderIdExists(f, folders)) { - problems.push(`Folder (${f}) not found.`); + warnings.push(`Folder (${f}) not found.`); } }); } diff --git a/package.json b/package.json index dae57af4..f491c35a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postybirb-plus", - "version": "3.1.45", + "version": "3.1.46", "description": "PostyBirb is an application that helps artists post art and other multimedia to multiple websites more quickly..", "main": "index.js", "scripts": { diff --git a/ui/package.json b/ui/package.json index ef8769b2..87f78cb8 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "postybirb-plus-ui", - "version": "3.1.45", + "version": "3.1.46", "license": "BSD-3-Clause", "private": true, "Author": "Michael DiCarlo", @@ -34,6 +34,7 @@ "scripts": { "start": "react-scripts start", "build": "run-s build:clean build:tailwind build:antd-tweaks build:themes && react-scripts build && shx mv build ../electron-app", + "build:legacy": "run-s build:clean build:tailwind build:antd-tweaks build:themes && react-scripts --openssl-legacy-provider build && shx mv build ../electron-app", "build:clean": "rimraf ../electron-app/build", "build:style": "npm run build:tailwind && npm run build:themes", "build:tailwind": "tailwind build src/styles/index.css -o public/styles/tailwind.css",