Hey, Im building a plugin with chatgpt to export any image directly to tumblr tagged, but I’m new to coding and don’t know anymore why my code says that “No items selected or found” in the log.
Full log:
Summary
{“level”:30,“time”:1735582632617,“type”:“renderer”,“name”:“project”,“msg”:“plugins scanned: 1”}
{“level”:30,“time”:1735582632636,“type”:“renderer”,“name”:“project”,“msg”:“plugins loaded: 1”}
{“level”:30,“time”:1735582632773,“type”:“renderer”,“name”:“project”,“msg”:“restoring projectFiles@tropy”}
{“level”:30,“time”:1735582632774,“type”:“renderer”,“name”:“project”,“msg”:“restoring recent@tropy”}
{“level”:30,“time”:1735582632774,“type”:“renderer”,“name”:“project”,“msg”:“restoring settings@tropy”}
{“level”:30,“time”:1735582632774,“type”:“renderer”,“name”:“project”,“msg”:“restoring ui@tropy”}
{“level”:30,“time”:1735582632775,“type”:“renderer”,“name”:“project”,“mode”:“w+”,“msg”:“open db C:\Users\j\AppData\Roaming\Tropy\ontology.db”}
{“level”:30,“time”:1735582632777,“type”:“browser”,“name”:“main”,“msg”:“ready after 148.94091796875ms”}
{“level”:30,“time”:1735582632847,“type”:“renderer”,“name”:“project”,“mode”:“webgl”,“resolution”:1,“msg”:“Esper.instance created with webgl renderer”}
{“level”:30,“time”:1735582632856,“type”:“renderer”,“name”:“project”,“mode”:“w”,“msg”:“open db C:\Users\j\Documents\Catalog56.tropy\project.tpy”}
{“level”:30,“time”:1735582632872,“type”:“renderer”,“name”:“project”,“msg”:“project ready 371ms [dom:108ms init:37ms load:129ms]”}
{“level”:30,“time”:1735582632888,“type”:“renderer”,“name”:“project”,“msg”:“restoring project.watch@3a2bcb3a-747a-4915-a577-356835c2f44c”}
{“level”:30,“time”:1735582632889,“type”:“renderer”,“name”:“project”,“msg”:“restoring project.watch@3a2bcb3a-747a-4915-a577-356835c2f44c”}
{“level”:30,“time”:1735582632889,“type”:“renderer”,“name”:“project”,“msg”:“restoring nav@3a2bcb3a-747a-4915-a577-356835c2f44c”}
{“level”:30,“time”:1735582632889,“type”:“renderer”,“name”:“project”,“msg”:“restoring notepad@3a2bcb3a-747a-4915-a577-356835c2f44c”}
{“level”:30,“time”:1735582632889,“type”:“renderer”,“name”:“project”,“msg”:“restoring esper@3a2bcb3a-747a-4915-a577-356835c2f44c”}
{“level”:30,“time”:1735582632889,“type”:“renderer”,“name”:“project”,“msg”:“restoring imports@3a2bcb3a-747a-4915-a577-356835c2f44c”}
{“level”:30,“time”:1735582632889,“type”:“renderer”,“name”:“project”,“msg”:“restoring sidebar@3a2bcb3a-747a-4915-a577-356835c2f44c”}
{“level”:30,“time”:1735582632889,“type”:“renderer”,“name”:“project”,“msg”:“restoring panel@3a2bcb3a-747a-4915-a577-356835c2f44c”}
{“level”:30,“time”:1735582632891,“type”:“renderer”,“name”:“project”,“mode”:“w”,“msg”:“open db C:\Users\j\Documents\Catalog56.tropy\project.tpy”}
{“level”:30,“time”:1735582632892,“type”:“renderer”,“name”:“project”,“mode”:“w”,“msg”:“open db C:\Users\j\Documents\Catalog56.tropy\project.tpy”}
{“level”:30,“time”:1735582636192,“type”:“renderer”,“name”:“project”,“plugin”:“tropy-to-tumblr”,“msg”:“Export items:”}
{“level”:30,“time”:1735582636192,“type”:“renderer”,“name”:“project”,“plugin”:“tropy-to-tumblr”,“msg”:“No items selected or found.”}
Also I’ll paste the index.js:
// index.js
const fs = require(‘fs’);
const path = require(‘path’);
const FormData = require(‘form-data’);
const OAuth = require(‘oauth-1.0a’);
const crypto = require(‘crypto’);
// node-fetch 2.x syntax:
const fetch = require(‘node-fetch’);
class TropyToTumblr {
/**
- Tropy plugin constructor.
- @param {Object} options - Config from package.json ‘options’
- @param {Object} context - Tropy context (logger, dialog, etc.)
*/
constructor(options, context) {
this.options = options;
this.context = context;
this.logger = context.logger;
// Load user-defined fields, or defaults from package.json "options"
this.blogName = options.blogIdentifier || 'cataleg56';
this.consumerKey = options.consumerKey || '';
this.consumerSecret = options.consumerSecret || '';
this.token = options.token || '';
this.tokenSecret = options.tokenSecret || '';
// Initialize OAuth 1.0
this.oauth = OAuth({
consumer: { key: this.consumerKey, secret: this.consumerSecret },
signature_method: 'HMAC-SHA1',
hash_function(baseString, key) {
return crypto
.createHmac('sha1', key)
.update(baseString)
.digest('base64');
}
});
}
/**
- The export() hook is called when user chooses
- File → Export → TropyToTumblr
- @param {Array} items - selected Tropy items (JSON-LD), or all items if none selected
*/
async export(items) {
// Log what Tropy actually passes
this.logger.info(‘Export items:’, items);
// If Tropy passes nothing or an empty array, log and return
if (!Array.isArray(items) || items.length === 0) {
this.logger.info('No items selected or found.');
return;
}
for (const item of items) {
// Gather local photo file paths via .photo[].path
const photoPaths = this.findLocalPhotos(item);
if (photoPaths.length === 0) {
this.logger.info(`No photos found in item: ${item.title || '[Untitled]'}`);
continue;
}
// Post each item as a single Tumblr photo post
this.logger.info(`Posting ${photoPaths.length} photo(s) from: ${item.title || '[Untitled]'}`);
await this.postPhotosToTumblr(photoPaths, item.title || '');
}
}
/**
- Extract local file paths from Tropy’s JSON-LD:
-
- item[“@graph”] is an array of nodes
-
- each node may have a “photo” array
-
- each photo entry has “path”: “C:\Users\j\…”
*/
findLocalPhotos(item) {
const photoPaths = ;
const graph = item[‘@graph’];
- each photo entry has “path”: “C:\Users\j\…”
if (!Array.isArray(graph)) {
return photoPaths;
}
for (const node of graph) {
if (Array.isArray(node.photo)) {
for (const p of node.photo) {
// Check that p.path is valid and file exists
if (p.path && fs.existsSync(p.path)) {
photoPaths.push(p.path);
}
}
}
}
return photoPaths;
}
/**
- Post photos to Tumblr using OAuth 1.0
- @param {Array} photoPaths - array of local image paths
- @param {string} caption - optional text for the Tumblr post (the Tropy item title)
*/
async postPhotosToTumblr(photoPaths, caption) {
// Tumblr API endpoint
const url =https://api.tumblr.com/v2/blog/${this.blogName}.tumblr.com/post
;
// Build the multipart form data
const form = new FormData();
form.append('type', 'photo');
form.append('caption', caption);
// "data[]" param for each image
for (const filepath of photoPaths) {
form.append('data[]', fs.createReadStream(filepath));
}
// Prepare request data for OAuth signature
const requestData = {
url,
method: 'POST',
data: {}
};
const token = {
key: this.token,
secret: this.tokenSecret
};
// Generate OAuth header
const oauthHeaders = this.oauth.toHeader(
this.oauth.authorize(requestData, token)
);
// Merge OAuth header + form-data headers
const fetchHeaders = {
...oauthHeaders,
...form.getHeaders()
};
try {
const response = await fetch(url, {
method: 'POST',
headers: fetchHeaders,
body: form
});
const json = await response.json();
if (!response.ok) {
this.logger.error('Tumblr API error:', json);
} else {
this.logger.info('Posted to Tumblr successfully:', json);
}
} catch (err) {
this.logger.error('Failed to post photos to Tumblr:', err);
}
}
}
module.exports = TropyToTumblr;
and the package.json:
{
“name”: “tropy-to-tumblr”,
“productName”: “TropyToTumblr”,
“version”: “1.0.0”,
“description”: “A Tropy plugin to export images to Tumblr (no tumblr.js)”,
“author”: “Your Name”,
“main”: “./index.js”,
“icon”: “./icon.svg”,
“license”: “MIT”,
“hooks”: {
“export”: true,
“import”: false
},
“options”: [
{
“field”: “blogIdentifier”,
“type”: “string”,
“default”: “cataleg56”,
“label”: “Your Tumblr blog name (without .tumblr.com)”
},
{
“field”: “consumerKey”,
“type”: “string”,
“default”: “kg5w6bS33HkFcnrx7orlSxtoxmtbowiARhiDQM3udZlUhmfSMG”,
“label”: “Consumer Key”
},
{
“field”: “consumerSecret”,
“type”: “string”,
“default”: “ZWmbU11xdOT9vsqDVkYZv9BWXl75bafDNICmvQZeGwBVFr54Yw”,
“label”: “Consumer Secret”
},
{
“field”: “token”,
“type”: “string”,
“default”: “sVNtX9LFaP6H7Xf3E4WWHduIPJFRnZY9zuKk1uvqWFK2jQ2fZb”,
“label”: “Access Token”
},
{
“field”: “tokenSecret”,
“type”: “string”,
“default”: “kRIjPATQD3x4tjoN6wMpAyiZmNbDmSuLQZwerWCzcLKWWCSIuf”,
“label”: “Token Secret”
}
],
“dependencies”: {
“oauth-1.0a”: “^2.2.6”,
“node-fetch”: “^2.6.9”,
“form-data”: “^4.0.0”
}
}
Thanks for the help!