mirror of
https://github.com/Wanxp/obsidian-douban.git
synced 2026-04-04 00:28:43 +08:00
fix: support douban image uploads with referer
- use subject page referer for douban image requests\n- keep PicGo clipboard upload flow and await clipboard writes\n- add webp clipboard fallback for image decoding
This commit is contained in:
parent
318aabb21b
commit
e8a0d8a00c
@ -543,24 +543,20 @@ export default abstract class DoubanAbstractLoadHandler<T extends DoubanSubject>
|
||||
}
|
||||
fileName = this.parsePartPath(fileName, extract, context, variableMap)
|
||||
fileName = fileName + fileNameSuffix;
|
||||
const imageReferer = extract.url || extract.imageUrl || image;
|
||||
const imageReferer = (extract.id ? this.getSubjectUrl(extract.id) : '') || extract.url;
|
||||
const referHeaders = HttpUtil.buildImageRequestHeaders(
|
||||
context.plugin.settingsManager.getHeaders() as Record<string, any>,
|
||||
imageReferer,
|
||||
image
|
||||
imageReferer
|
||||
);
|
||||
if ((syncConfig ? syncConfig.cacheHighQuantityImage : context.settings.cacheHighQuantityImage) && context.userComponent.isLogin()) {
|
||||
try {
|
||||
const fileNameSpilt = fileName.split('.');
|
||||
const highFilename = fileNameSpilt.first() + '.jpg';
|
||||
|
||||
const highImage = this.getHighQuantityImageUrl(highFilename);
|
||||
const highImageFilename = this.getImageFilename(image);
|
||||
const highImage = this.getHighQuantityImageUrl(highImageFilename);
|
||||
const highImageHeaders = HttpUtil.buildImageRequestHeaders(
|
||||
context.plugin.settingsManager.getHeaders() as Record<string, any>,
|
||||
imageReferer,
|
||||
highImage
|
||||
imageReferer
|
||||
);
|
||||
const resultValue = await this.handleImage(highImage, folder, highFilename, context, false, highImageHeaders);
|
||||
const resultValue = await this.handleImage(highImage, folder, fileName, context, false, highImageHeaders);
|
||||
if (resultValue && resultValue.success) {
|
||||
extract.image = resultValue.filepath;
|
||||
this.initImageVariableMap(extract, context, variableMap);
|
||||
@ -578,6 +574,14 @@ export default abstract class DoubanAbstractLoadHandler<T extends DoubanSubject>
|
||||
}
|
||||
}
|
||||
|
||||
private getImageFilename(image: string): string {
|
||||
if (!image) {
|
||||
return '';
|
||||
}
|
||||
const imageUrl = image.split('?').first() || image;
|
||||
return imageUrl.substring(imageUrl.lastIndexOf('/') + 1);
|
||||
}
|
||||
|
||||
private initImageVariableMap(extract: T, context: HandleContext, variableMap : Map<string, DataField>) {
|
||||
variableMap.set(DoubanParameterName.IMAGE_URL, new DataField(
|
||||
DoubanParameterName.IMAGE_URL,
|
||||
|
||||
@ -335,6 +335,8 @@ PS: This file could be delete if you want to.
|
||||
'130105': `Can not use Douban this time, Please try again after 12 hour or 24 hour. Or you can reset your connection `,
|
||||
'130106': `Can not use Douban this time, Please try Login In Douban Plugin. If not working please again after 12 hour or 24 hour. Or you can reset your connection `,
|
||||
'130404': `404 Url Not Found`,
|
||||
'130109': `Downloaded image is empty`,
|
||||
'130110': `Clipboard write failed`,
|
||||
|
||||
|
||||
'130107': `Can not find array setting for {1} in {0} , Please add it in array settings`,
|
||||
|
||||
@ -350,6 +350,8 @@ export default {
|
||||
'130105': `由于多次频繁请求数据,豆瓣当前暂时不可用. 请于12小时或24小时后再重试,或重置你的网络(如重新拨号或更换网络) `,
|
||||
'130106': `请尝试在Douban插件中登录后操作. 若还是无效果则尝试于12小时或24小时后再重试,或重置你的网络(如重新拨号或更换网络) `,
|
||||
'130107': `参数{0}中指定的数组输出类型{1}不存在,请前往配置进行设置`,
|
||||
'130109': `下载图片内容为空`,
|
||||
'130110': `写入剪贴板失败`,
|
||||
'130120': `同步时发生错误,但同步将会继续。错误项目是 {}。`,
|
||||
'130121': `总数只有{0}, 选择的开始比总数还要大,将不会同步。`,
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import FileHandler from "../file/FileHandler";
|
||||
import {FileUtil} from "../utils/FileUtil";
|
||||
import HandleContext from "../douban/data/model/HandleContext";
|
||||
import HttpUtil from "../utils/HttpUtil";
|
||||
import {ClipboardUtil} from "../utils/ClipboardUtil";
|
||||
import {ResultI} from "../utils/model/Result";
|
||||
|
||||
export default class NetFileHandler {
|
||||
@ -16,16 +17,19 @@ export default class NetFileHandler {
|
||||
|
||||
async downloadDBFile(url: string, folder:string, filename: string, context:HandleContext, showError:boolean, headers?:any): Promise<{ success: boolean, error:string, filepath: string }> {
|
||||
const filePath:string = FileUtil.join(folder, filename);
|
||||
return HttpUtil.httpRequestBuffer(url, headers, context.plugin.settingsManager)
|
||||
.then((response) => {
|
||||
if (response.status == 404) {
|
||||
throw new Error(i18nHelper.getMessage('130404'));
|
||||
}
|
||||
if (response.status == 403) {
|
||||
throw new Error(i18nHelper.getMessage('130106'));
|
||||
}
|
||||
return response.textArrayBuffer;
|
||||
})
|
||||
return HttpUtil.httpRequestBuffer(url, headers, context.plugin.settingsManager)
|
||||
.then((response) => {
|
||||
if (response.status == 404) {
|
||||
throw new Error(i18nHelper.getMessage('130404'));
|
||||
}
|
||||
if (response.status == 403) {
|
||||
throw new Error(i18nHelper.getMessage('130106'));
|
||||
}
|
||||
if (response.status == 418) {
|
||||
throw new Error(i18nHelper.getMessage('130105'));
|
||||
}
|
||||
return response.textArrayBuffer;
|
||||
})
|
||||
.then((buffer) => {
|
||||
if (!buffer || buffer.byteLength == 0) {
|
||||
return 0;
|
||||
@ -55,19 +59,23 @@ export default class NetFileHandler {
|
||||
async downloadDBUploadPicGoByClipboard(url: string, filename: string, context:HandleContext, showError:boolean, headers?:any): Promise<{ success: boolean, error:string, filepath: string }> {
|
||||
return HttpUtil.httpRequestBuffer(url, headers, context.plugin.settingsManager)
|
||||
.then((response) => {
|
||||
if (response.status == 404) {
|
||||
throw new Error(i18nHelper.getMessage('130404'));
|
||||
}
|
||||
if (response.status == 403) {
|
||||
throw new Error(i18nHelper.getMessage('130106'));
|
||||
}
|
||||
if (response.status == 418) {
|
||||
throw new Error(i18nHelper.getMessage('130105'));
|
||||
}
|
||||
return response.textArrayBuffer;
|
||||
})
|
||||
.then(async (buffer) => {
|
||||
const tempFilePath = this.getPicGoTempFilePath(filename, context);
|
||||
try {
|
||||
await this.fileHandler.creatAttachmentWithData(tempFilePath.relativePath, buffer);
|
||||
return await this.uploadClipboardFile(context, tempFilePath.absolutePath);
|
||||
} finally {
|
||||
await this.fileHandler.deleteFile(tempFilePath.relativePath);
|
||||
}
|
||||
.then(async (buffer) => {
|
||||
if (!buffer || buffer.byteLength == 0) {
|
||||
throw new Error(i18nHelper.getMessage('130109'));
|
||||
}
|
||||
await ClipboardUtil.writeImage(buffer);
|
||||
return await this.uploadClipboardFile(context);
|
||||
}).then((data) => {
|
||||
if (data.success) {
|
||||
return {success: true, error: '', filepath: HttpUtil.extractURLFromString(data.result[0])};
|
||||
@ -90,23 +98,9 @@ export default class NetFileHandler {
|
||||
}
|
||||
|
||||
|
||||
private getPicGoTempFilePath(filename: string, context: HandleContext) {
|
||||
const tempFileName = `${Date.now()}_${filename}`;
|
||||
const relativePath = FileUtil.join(this.fileHandler.getTmpPath(), tempFileName);
|
||||
// @ts-ignore
|
||||
const adapter = context.plugin.app.vault.adapter;
|
||||
// @ts-ignore
|
||||
const basePath = adapter && adapter.getBasePath ? adapter.getBasePath() : this.fileHandler.getRootPath();
|
||||
return {
|
||||
relativePath: relativePath,
|
||||
absolutePath: FileUtil.join(basePath, relativePath),
|
||||
};
|
||||
}
|
||||
|
||||
async uploadClipboardFile(context:HandleContext, filePath?: string): Promise<ResultI> {
|
||||
const body = filePath ? JSON.stringify({list: [filePath]}) : null;
|
||||
async uploadClipboardFile(context:HandleContext): Promise<ResultI> {
|
||||
const response = await HttpUtil.httpRequest(
|
||||
context.settings.pictureBedSetting.url, {}, context.plugin.settingsManager, {method: "post", body: body});
|
||||
context.settings.pictureBedSetting.url, {}, context.plugin.settingsManager, {method: "post"});
|
||||
const data = response.textJson as ResultI;
|
||||
return data;
|
||||
}
|
||||
@ -124,3 +118,4 @@ export default class NetFileHandler {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,12 +1,78 @@
|
||||
|
||||
|
||||
export class ClipboardUtil {
|
||||
import {i18nHelper} from "../lang/helper";
|
||||
|
||||
public static async writeImage(data:ArrayBuffer, options: ClipboardOptions = defaultClipboardOptions) {
|
||||
const { clipboard, nativeImage } = require('electron');
|
||||
export class ClipboardUtil {
|
||||
|
||||
await clipboard.writeImage(nativeImage.createFromBuffer(data));
|
||||
console.log(`Copied to clipboard as HTML`);
|
||||
public static async writeImage(data:ArrayBuffer, options: ClipboardOptions = defaultClipboardOptions) {
|
||||
if (!data || data.byteLength == 0) {
|
||||
throw new Error(i18nHelper.getMessage('130110'));
|
||||
}
|
||||
const { clipboard, nativeImage } = require('electron');
|
||||
const isWebp = this.isWebp(data);
|
||||
let image = nativeImage.createFromBuffer(Buffer.from(data));
|
||||
if ((!image || image.isEmpty()) && isWebp) {
|
||||
image = await this.createNativeImageFromWebp(data);
|
||||
}
|
||||
if (!image || image.isEmpty()) {
|
||||
throw new Error(i18nHelper.getMessage('130110'));
|
||||
}
|
||||
clipboard.clear();
|
||||
clipboard.writeImage(image);
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
const clipboardImage = clipboard.readImage();
|
||||
if (!clipboardImage || clipboardImage.isEmpty()) {
|
||||
throw new Error(i18nHelper.getMessage('130110'));
|
||||
}
|
||||
console.log(`Copied to clipboard as HTML`);
|
||||
}
|
||||
|
||||
private static isWebp(data: ArrayBuffer): boolean {
|
||||
const bytes = new Uint8Array(data);
|
||||
if (bytes.length < 12) {
|
||||
return false;
|
||||
}
|
||||
return bytes[0] === 0x52
|
||||
&& bytes[1] === 0x49
|
||||
&& bytes[2] === 0x46
|
||||
&& bytes[3] === 0x46
|
||||
&& bytes[8] === 0x57
|
||||
&& bytes[9] === 0x45
|
||||
&& bytes[10] === 0x42
|
||||
&& bytes[11] === 0x50;
|
||||
}
|
||||
|
||||
private static async createNativeImageFromWebp(data: ArrayBuffer) {
|
||||
const { nativeImage } = require('electron');
|
||||
const imageElement = await this.loadImage(URL.createObjectURL(new Blob([data], {type: 'image/webp'})));
|
||||
try {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = imageElement.naturalWidth || imageElement.width;
|
||||
canvas.height = imageElement.naturalHeight || imageElement.height;
|
||||
const context = canvas.getContext('2d');
|
||||
if (!context) {
|
||||
throw new Error(i18nHelper.getMessage('130110'));
|
||||
}
|
||||
context.drawImage(imageElement, 0, 0);
|
||||
return nativeImage.createFromDataURL(canvas.toDataURL('image/png'));
|
||||
} finally {
|
||||
imageElement.src = '';
|
||||
}
|
||||
}
|
||||
|
||||
private static loadImage(url: string): Promise<HTMLImageElement> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const image = new Image();
|
||||
image.onload = () => {
|
||||
URL.revokeObjectURL(url);
|
||||
resolve(image);
|
||||
};
|
||||
image.onerror = () => {
|
||||
URL.revokeObjectURL(url);
|
||||
reject(new Error(i18nHelper.getMessage('130110')));
|
||||
};
|
||||
image.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -20,4 +86,3 @@ interface ClipboardOptions {
|
||||
const defaultClipboardOptions: ClipboardOptions = {
|
||||
contentType: 'text/plain',
|
||||
}
|
||||
|
||||
|
||||
@ -73,16 +73,14 @@ export default class HttpUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static buildImageRequestHeaders(headers: Record<string, any> = {}, referer?: string, imageUrl?: string): Record<string, string> {
|
||||
public static buildImageRequestHeaders(headers: Record<string, any> = {}, referer?: string): Record<string, string> {
|
||||
const nextHeaders: Record<string, string> = {};
|
||||
let currentReferer = '';
|
||||
Object.entries(headers || {}).forEach(([key, value]) => {
|
||||
if (value == null || value === '') {
|
||||
return;
|
||||
}
|
||||
const lowerKey = key.toLowerCase();
|
||||
if (lowerKey === 'referer') {
|
||||
currentReferer = String(value);
|
||||
return;
|
||||
}
|
||||
if (this.IMAGE_REQUEST_HEADERS_TO_DROP.has(lowerKey) || lowerKey.startsWith('sec-ch-ua')) {
|
||||
@ -90,25 +88,12 @@ export default class HttpUtil {
|
||||
}
|
||||
nextHeaders[key] = String(value);
|
||||
});
|
||||
const finalReferer = referer || currentReferer || this.getDefaultImageRequestReferer(imageUrl);
|
||||
if (finalReferer) {
|
||||
nextHeaders.Referer = finalReferer;
|
||||
if (referer) {
|
||||
nextHeaders.Referer = referer;
|
||||
}
|
||||
return nextHeaders;
|
||||
}
|
||||
|
||||
private static getDefaultImageRequestReferer(imageUrl?: string): string {
|
||||
if (!imageUrl) {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
const url = new URL(imageUrl);
|
||||
return `${url.protocol}//${url.host}/`;
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static parse(url: string): { protocol: string, host: string, port: string, path: string } {
|
||||
const regex = /^(.*?):\/\/([^\/:]+)(?::(\d+))?([^?]*)$/;
|
||||
@ -140,10 +125,10 @@ export default class HttpUtil {
|
||||
* @param str
|
||||
*/
|
||||
public static extractURLFromString(str: string): string {
|
||||
const urlRegex = /(?:!\[.*?\]\()?(https?:\/\/[^\s)]+)/;
|
||||
const matches = urlRegex.exec(str);
|
||||
if (matches && matches[1]) {
|
||||
return matches[1];
|
||||
const urlRegex = /(?:!\[.*?\]\()?(https?:\/\/[^\s)]+)/g;
|
||||
const matches = str.match(urlRegex);
|
||||
if (matches && matches.length > 0) {
|
||||
return matches[0];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user