كيفية نشر الصور عبر API؟

لقد كنت أعمل على إضافة لـ Obsidian وكنت أضرب رأسي بالحائط محاولًا تحميل الصور. هذا ما لدي حتى الآن:


	async uploadImages(imageReferences: string[]): Promise<string[]> {
		const imageUrls = [];
		for (const ref of imageReferences) {
			const filePath = this.app.metadataCache.getFirstLinkpathDest(ref, this.activeFile.name)?.path;
			if (filePath) {
				const file = this.app.vault.getAbstractFileByPath(filePath) as TFile;
				if (file) {
					try {
						const arrayBuffer = await this.app.vault.readBinary(file);
						const blob = new Blob([arrayBuffer]);
						const boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW';
						let body = '';

						body += `--${boundary}\r\n`;
						body += `Content-Disposition: form-data; name=\"type\"\r\n\r\n`;
						body += "composer\r\n";
						body += `--${boundary}\r\n`;
						body += `Content-Disposition: form-data; name=\"synchronous\"\r\n\r\n`;
						body += "true\r\n";

						body += `--${boundary}\r\n`;
						body += `Content-Disposition: form-data; name=\"files[]\"; filename=\"${file.name}\"\r\n`;
						body += `Content-Type: image/jpg\r\n\r\n`
						body += blob + '\r\n';
						body += `--${boundary}--\r\n`;
						console.log(body)
						const formData = new TextEncoder().encode(body)

						const url = `${this.settings.baseUrl}/uploads.json`;
						const headers = {
							"Api-Key": this.settings.apiKey,
							"Api-Username": this.settings.disUser,
							"Content-Type": `multipart/form-data; boundary=${boundary}`
						};

						const response = await requestUrl({
							url: url,
							method: "POST",
							body: formData,
							throw: false,
							headers: headers,
						});

						//const response = await fetch(url, {
						//	method: "POST",
						//	body: formData,
						//	headers: new Headers(headers),
						//});

						console.log(`Upload Image response: ${response.status}`);
						//if (response.ok) {
						if (response.status == 200) {
							const jsonResponse = response.json();
							console.log(`Upload Image jsonResponse: ${JSON.stringify(jsonResponse)}`);
							imageUrls.push(jsonResponse.url);
						} else {
							new NotifyUser(this.app, `Error uploading image: ${response.status}`).open();
							console.error(`Error uploading image: ${JSON.stringify(response.json)}`);
							//console.error("Error uploading image:", response.status, await response.text());
						}
					} catch (error) {
						new NotifyUser(this.app, `Exception while uploading image: ${error}`).open();
						console.error("Exception while uploading image:", error);
					}
				} else {
					new NotifyUser(this.app, `File not found in vault: ${ref}`).open();
					console.error(`File not found in vault: ${ref}`);
				}
			} else {
				new NotifyUser(this.app, `Unable to resolve file path for: ${ref}`).open();
				console.error(`Unable to resolve file path for: ${ref}`);
			}
		}
		return imageUrls;
	}

أقوم بإنشاء multipart/form-data لأن requestURL() لا يمكنها قبول formData() كمعامل. فقط string أو arrayBuffer. لا يمكنني استخدام fetch() لأنني أحصل على خطأ CORS. مع هذا الكود (والعديد من التعديلات الطفيفة على body) أحصل على الخطأ التالي:

خطأ في تحميل الصورة: {“errors”:[“لقد قدمت معلمات غير صالحة للطلب: Discourse::InvalidParameters”],“error_type”:“invalid_parameters”}

إعجاب واحد (1)

لقد فكرت في المتابعة والتحديث حيث أنني أتلقى الآن رسالة خطأ مختلفة:

	async uploadImages(imageReferences: string[]): Promise<string[]> {
		const imageUrls = [];
		for (const ref of imageReferences) {
			const filePath = this.app.metadataCache.getFirstLinkpathDest(ref, this.activeFile.name)?.path;
			if (filePath) {
				const file = this.app.vault.getAbstractFileByPath(filePath) as TFile;
				if (file) {
					try {
						const imgfile = await this.app.vault.readBinary(file);
						const boundary = genBoundary();
						const sBoundary = '--' + boundary + '\r\n';
						let body = '';
						body += `${sBoundary}Content-Disposition: form-data; name=\"type\"\r\n\r\ncomposer\r\n`;
						body += `${sBoundary}Content-Disposition: form-data; name=\"synchronous\"\r\n\r\ntrue\r\n`;
						body += `${sBoundary}Content-Disposition: form-data; name=\"files[]\"; filename=\"${file.name}\"\r\nContent-Type: image/jpg`;
						console.log(body);

						const eBoundary = '\r\n--' + boundary + '--\\r\\n';
						const bodyArray = new TextEncoder().encode(body);
						const endBoundaryArray = new TextEncoder().encode(eBoundary);

						const formDataArray = new Uint8Array(bodyArray.length + imgfile.byteLength + endBoundaryArray.length);
						formDataArray.set(bodyArray, 0);
						formDataArray.set(new Uint8Array(imgfile), bodyArray.length);
						formDataArray.set(endBoundaryArray, bodyArray.length + imgfile.byteLength);

						const url = `${this.settings.baseUrl}/uploads.json`;
						const headers = {
							"Api-Key": this.settings.apiKey,
							"Api-Username": this.settings.disUser,
							"Content-Type": `multipart/form-data; boundary=${boundary}`
						};

						const response = await requestUrl({
							url: url,
							method: "POST",
							body: formDataArray.buffer,
							throw: false,
							headers: headers,
						});

						console.log(`Upload Image response: ${response.status}`);
						if (response.status == 200) {
							const jsonResponse = response.json();
							console.log(`Upload Image jsonResponse: ${JSON.stringify(jsonResponse)}`);
							imageUrls.push(jsonResponse.url);
						} else {
							new NotifyUser(this.app, `Error uploading image: ${response.status}`).open();
							console.error(`Error uploading image: ${JSON.stringify(response.json)}`);
						}
					} catch (error) {
						new NotifyUser(this.app, `Exception while uploading image: ${error}`).open();
						console.error("Exception while uploading image:", error);
					}
				} else {
					new NotifyUser(this.app, `File not found in vault: ${ref}`).open();
					console.error(`File not found in vault: ${ref}`);
				}
			} else {
				new NotifyUser(this.app, `Unable to resolve file path for: ${ref}`).open();
				console.error(`Unable to resolve file path for: ${ref}`);
			}
		}
		return imageUrls;
	}

رسالة الخطأ التي أتلقاها الآن هي:

Exception while uploading image: SyntaxError: Unexpected token ‘I’, “Invalid request” is not valid JSON

هذا مربك بالنسبة لي لأن واجهة برمجة التطبيقات تنص على أننا بحاجة إلى إرسال multipart/form-data، ولكنها تقول إنها JSON غير صالح؟ ربما يتعلق الأمر بـ requestAPI()

لذلك، فكرت في تجربة نهج مختلف باستخدام تخزين S3. اتبعت التعليمات هنا لإعداد حاوية AWS S3. إليك إعداداتي:

وهذا هو الكود الخاص بي:

	async uploadExternalImage(imageReferences: string[]): Promise<string[]> {
		const imageUrls: string[] = [];
		for (const ref of imageReferences) {
			const filePath = this.app.metadataCache.getFirstLinkpathDest(ref, this.activeFile.name)?.path;
			if (filePath) {
				const file = this.app.vault.getAbstractFileByPath(filePath) as TFile;
				if (file) {
					try {
						const url = `${this.settings.baseUrl}/uploads/generate-presigned-put.json`;
						//const imgfile = await this.app.vault.readBinary(file);
						const img = {
							type: "composer",
							file_name: file.name,
							file_size: file.stat.size,
						}
						console.log(JSON.stringify(img));
						const headers = {
							"Content-Type": "application/json",
							"Api-Key": this.settings.apiKey,
							"Api-Username": this.settings.disUser,
						};
						const response = await requestUrl({
							url: url,
							method: "POST",
							body: JSON.stringify(img),
							throw: false,
							headers: headers
						})
						console.log(response.json)
					} catch (error) {
						console.error(`Error uploading: ${error}`);
						//console.log(response.json)
					}
				} else {
					console.error('error')
				}
			} else {
				console.error('error')
			}
		}
		return imageUrls;
	}

الآن، أدرك أن هذا لن يعمل حاليًا لأنني لا أقوم فعليًا بتحميل الملف بعد. بناءً على ما قرأته في المستندات، سأرسل كائن JSON يحتوي على النوع واسم الملف وحجم الملف. يجب أن يرد واجهة برمجة التطبيقات بمفتاح وعنوان URL لنقلهما للملف الفعلي. ولكن في هذه المرحلة، أحصل على الخطأ التالي:

{
    "errors": [
        "The requested URL or resource could not be found."
    ],
    "error_type": "not_found"
}

[
“The requested URL or resource could not be found.”
]

لقد نظرت في مفتاح واجهة برمجة التطبيقات الخاص بي للتأكد من أنه يمتلك الأذونات، وهو كذلك. لكنني أنشأت واحدًا جديدًا على أي حال. وواحدًا عامًا. لا يعمل أي منها. نفس رمز الخطأ. ما الخطأ الذي أرتكبه؟

تعديل، هذا هو كائن الصورة:

{"type":"composer","file_name":"face2.jpg","file_size":17342}

لست على دراية كبيرة بهذا النظام البيئي، ولكن ربما يساعد هذا؟

خطأ CORS…

هل اتبعت

؟

إعجاب واحد (1)

أنا أخمن هنا بعض الشيء، لكنني لا أعتقد أن تكوين CORS سيعمل في هذه الحالة. المصدر (على الأقل من تطبيق Obsidian Desktop) هو 'app://obsidian.md'. أعتقد أن CORS يمكن تكوينه فقط على Discourse للتعامل مع طلبات HTTP.

@maxtim، هل تحتاج إلى أن يعمل هذا من الهاتف المحمول، أم يكفي أن تكون قادرًا على النشر إلى Discourse من تطبيق سطح المكتب؟ أنا أخمن مرة أخرى بعض الشيء، ولكن… فهمي هو أن تطبيق سطح المكتب هو تطبيق Electron. إنه يعمل على مزيج من Chromium و Node.js. قد تتمكن من استخدام node-fetch لإجراء طلبات من جانب الخادم إلى Discourse. إذا نجح ذلك، فسوف يعالج مشكلة CORS ويسمح لك باستخدام FormData في الطلبات.

لقد مضيت قدمًا وحاولت (رمي المعكرونة المبللة مجازيًا لمعرفة ما الذي سيلتصق). لكنني كنت متخوفًا بالفعل و @simon على حق.

من الناحية المثالية، يجب أن يكون المكون الإضافي متاحًا على الهاتف المحمول أيضًا. ولكن في الوقت الحالي، إذا كان سطح المكتب فقط هو ما نحصل عليه، فهو ما نحصل عليه.

بالطبع، قد يكون هناك حل آخر: Obsidian Vault على الهاتف المحمول → المزامنة إلى سطح المكتب → واجهة سطر الأوامر للتحميل إلى Discourse. ولكن هذا يبدو معقدًا بعض الشيء.

بشكل أساسي، الوضع المثالي هو أن يحل منتدى Discourse محل Obsidian Vault. بهذه الطريقة، يمكن للمستخدمين الذين يفضلون المنتدى استخدامه. يمكن للمستخدمين الذين يفضلون (أو يحتاجون بالفعل إلى حل غير متصل بالإنترنت) استخدام Obsidian. لدي بالفعل بعض الأفكار حول كيفية عمل المزامنة ثنائية الاتجاه. ولكن أعتقد أن الصور/الملفات تحتاج إلى معالجة بطريقة ما أولاً.

تعديل:
أنا مقتنع تمامًا بأن هذا سيعمل، ولكن لا يبدو أنني أستطيع الحصول على المعلمات الصحيحة:

                    try {
                        const imgfile = await this.app.vault.readBinary(file);
                        const boundary = genBoundary();
                        const sBoundary = '--' + boundary + '\r\n';
                        let body = '';
                        body += `${sBoundary}Content-Disposition: form-data; name=\"type\"\r\n\r\ncomposer\r\n`;
                        body += `${sBoundary}Content-Disposition: form-data; name=\"synchronous\"\r\n\r\ntrue\r\n`;
                        body += `${sBoundary}Content-Disposition: form-data; name=\"files[]\"; filename=\"${file.name}\"\r\nContent-Type: image/jpg\r\n`;

                        const eBoundary = '\r\n--' + boundary + '--\\r\n';
                        const bodyArray = new TextEncoder().encode(body);
                        const endBoundaryArray = new TextEncoder().encode(eBoundary);

                        const formDataArray = new Uint8Array(bodyArray.length + imgfile.byteLength + endBoundaryArray.length);
                        formDataArray.set(bodyArray, 0);
                        formDataArray.set(new Uint8Array(imgfile), bodyArray.length);
                        formDataArray.set(endBoundaryArray, bodyArray.length + imgfile.byteLength);

                        const url = `${this.settings.baseUrl}/uploads.json`;
                        const headers = {
                            "Api-Key": this.settings.apiKey,
                            "Api-Username": this.settings.disUser,
                            "Content-Type": `multipart/form-data; boundary=${boundary}`
                        };

                        const response = await requestUrl({
                            url: url,
                            method: "POST",
                            body: formDataArray,
                            throw: false,
                            headers: headers,
                        });

                        if (response.status == 200) {
                            const jsonResponse = response.json();
                            console.log(`Upload Image jsonResponse: ${JSON.stringify(jsonResponse)}`);
                            imageUrls.push(jsonResponse.url);
                        } else {
                            new NotifyUser(this.app, `Error uploading image: ${response.status}`).open();
                            console.error(`Error uploading image: ${JSON.stringify(response.json)}`);
                        }

أجل!! لقد فعلتها!

	async uploadImages(imageReferences: string[]): Promise<string[]> {
		const imageUrls = [];
		for (const ref of imageReferences) {
			const filePath = this.app.metadataCache.getFirstLinkpathDest(ref, this.activeFile.name)?.path;
			if (filePath) {
				const file = this.app.vault.getAbstractFileByPath(filePath) as TFile;
				if (file) {
					try {
						const imgfile = await this.app.vault.readBinary(file);
						const boundary = genBoundary();
						const sBoundary = '--' + boundary + '\r\n';
						const imgForm = `${sBoundary}Content-Disposition: form-data; name=\"file\"; filename=\"${file.name}\"\r\nContent-Type: image/${file.extension}\r\n\r\n`;


						let body = '';
						body += `\r\n${sBoundary}Content-Disposition: form-data; name=\"type\"\r\n\r\ncomposer\r\n`;
						body += `${sBoundary}Content-Disposition: form-data; name=\"synchronous\"\r\n\r\ntrue\r\n`;

						const eBoundary = '\r\n--' + boundary + '--\\r\\n';
						const imgFormArray = new TextEncoder().encode(imgForm);
						const bodyArray = new TextEncoder().encode(body);
						const endBoundaryArray = new TextEncoder().encode(eBoundary);

						const formDataArray = new Uint8Array(imgFormArray.length + imgfile.byteLength + bodyArray.length + endBoundaryArray.length);
						formDataArray.set(imgFormArray, 0);
						formDataArray.set(new Uint8Array(imgfile), imgFormArray.length);
						formDataArray.set(bodyArray, imgFormArray.length + imgfile.byteLength);
						formDataArray.set(endBoundaryArray, imgFormArray.length + bodyArray.length + imgfile.byteLength);

						const url = `${this.settings.baseUrl}/uploads.json`;
						const headers = {
							"Api-Key": this.settings.apiKey,
							"Api-Username": this.settings.disUser,
							"Content-Type": `multipart/form-data; boundary=${boundary}`,
						};

						const response = await requestUrl({
							url: url,
							method: "POST",
							body: formDataArray.buffer,
							throw: false,
							headers: headers,
						});

						if (response.status == 200) {
							const jsonResponse = response.json;
							console.log(`Upload Image jsonResponse: ${JSON.stringify(jsonResponse)}`);
							imageUrls.push(jsonResponse.url);
						} else {
							new NotifyUser(this.app, `Error uploading image: ${response.status}`).open();
							console.error(`Error uploading image: ${JSON.stringify(response.json)}`);
						}
					} catch (error) {
						new NotifyUser(this.app, `Exception while uploading image: ${error}`).open();
						console.error("Exception while uploading image:", error);
					}
				} else {
					new NotifyUser(this.app, `File not found in vault: ${ref}`).open();
					console.error(`File not found in vault: ${ref}`);
				}
			} else {
				new NotifyUser(this.app, `Unable to resolve file path for: ${ref}`).open();
				console.error(`Unable to resolve file path for: ${ref}`);
			}
		}
		return imageUrls;
	}

كانت المشكلة في ترتيب بناء بيانات النموذج. أحتاج إليها أن تكون:

  • معلمات الصورة
  • ثنائي الصورة
  • المعلمات

كنت أضع المعلمات قبل الصورة سابقًا.

لقد حللت ذلك عن طريق تحليل تحميل ناجح باستخدام بايثون:

import requests
from requests_toolbelt.multipart.encoder import MultipartEncoder
from requests.models import PreparedRequest


class Discourse:
    def __init__(self):
        self.base_url = "CENSORED"
        self.api_key = "CENSORED"
        self.api_username = "CENSORED"
        self.category = 2

    def post_uploads(self, file_path):
        headers = {
            "Content-Type": "multipart/form-data",
            "Api-Key": self.api_key,
            "Api-Username": self.api_username
        }

        multi = MultipartEncoder(
            fields = {
                'file': ('filename', open(file_path, 'rb'), 'image/jpg'),
                'type': 'composer',
                'synchronous': 'true'
            }
        )

        headers['Content-Type'] = multi.content_type

        request = requests.Request(
            method = "POST",
            url = f"{self.base_url}/uploads.json",
            headers = headers,
            data = multi
        )
        prepared_request = request.prepare()

        print("Headers:")
        for k, v in prepared_request.headers.items():
            print(f"{k}: {v}")

        print(multi.to_string())

        response = requests.post(
            f"{self.base_url}/uploads.json",
            headers=headers,
            params=params,
            data=m
        )

        return response.json()


if __name__ == "__main__":
    ds = Discourse()
    response = ds.post_uploads("/home/tfinley/Pictures/face2.jpg")
    print(response)

الآن كيف أحذف التحميلات اليتيمة؟ ^_o

3 إعجابات

حسب فهمي، سيقوم ديسكورس بالتعامل مع ذلك نيابة عنك:

نعم رأيت ذلك. GG EZ WP

لقد تأخرت في الحضور، لكنني كتبت واجهة برمجة تطبيقات discourse-api خارجية لـ nodejs من قبل:

باستخدام هذه المكتبة، يمكنك إنشاء تحميلات بسهولة. فقط قم بما يلي:

const { DiscourseApi } = require("node-discourse-api");
const api = new DiscourseApi("https://discourse.example.com");
api.options.api_username = "API_USERNAME";
api.options.api_key = "API_KEY";

api.createUpload(file_path_or_buffer, { filename: "filename" })

(ملاحظة: هذه المكتبة ليست كاملة)

إعجابَين (2)

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.