goat
command
pin
Search Pinterest images/videos
0
views
0
likes
0
installs
raw source
const axios = require("axios");
// Set this to your deployed Pinterest-xdi base URL
const BASE_URL = process.env.PINXDI_BASE_URL || "https://pinterest-xdi.onrender.com";
// axios error responses come back as streams; read the body to surface the
// server's JSON detail (e.g. the 502 from a failed ffmpeg run).
async function readErrorBody(err) {
const stream = err.response?.data;
if (!stream || typeof stream.on !== "function") return null;
const chunks = [];
for await (const chunk of stream) chunks.push(chunk);
try {
const parsed = JSON.parse(Buffer.concat(chunks).toString());
return typeof parsed.detail === "string" ? parsed.detail : JSON.stringify(parsed.detail);
} catch {
return null;
}
}
async function fetchAttachment(url, filename) {
const res = await axios.get(url, {
responseType: "stream",
timeout: 90000,
validateStatus: (s) => s === 200
});
res.data.path = filename;
return res.data;
}
module.exports = {
config: {
name: "pin",
version: "1.1.0",
author: "Tzua",
countDown: 5,
role: 0,
shortDescription: "Search Pinterest images/videos",
longDescription: "Search Pinterest via Pinterest-xdi API and send images or videos (with sound).",
category: "media",
guide: {
en: "{pn} <query> [video|image] [-N]\nEx: {pn} sunset video -7\nEx: {pn} flowers -3"
}
},
onStart: async function ({ api, event, args, message }) {
if (!args.length) return message.reply("⚠️ Usage: !pin <query> [video|image] [-N]");
let mode = "image";
let num = 10;
const queryParts = [];
for (const arg of args) {
if (arg === "video" || arg === "image") {
mode = arg;
} else if (/^-\d+$/.test(arg)) {
num = Math.min(10, Math.max(1, parseInt(arg.slice(1), 10)));
} else {
queryParts.push(arg);
}
}
const query = queryParts.join(" ").trim();
if (!query) return message.reply("⚠️ Please provide a search query.");
// "video" in the query doubles as a search hint for Pinterest's own
// ranking — without it video searches often come back empty.
const searchQuery = mode === "video" ? `${query} video` : query;
const waitMsg = await message.reply(`🔍 Searching Pinterest for "${query}" (${mode}, ${num})...`);
try {
const doSearch = () =>
axios.get(`${BASE_URL}/api/search`, {
params: { q: searchQuery, mode, num },
timeout: 60000
});
let { data } = await doSearch();
let results = Array.isArray(data) ? data : data.results;
if (!results || !results.length) {
// one retry — Render free tier cold start / transient empty scrape
({ data } = await doSearch());
results = Array.isArray(data) ? data : data.results;
}
if (!results || !results.length) {
return message.reply(`❌ No ${mode} results found for "${query}".`);
}
if (mode === "image") {
// Images are small — send them all directly, no picker needed.
const attachments = [];
for (const item of results) {
try {
attachments.push(await fetchAttachment(item.download_url, `${item.id}.jpg`));
} catch {
// skip failed image
}
}
if (!attachments.length) return message.reply("❌ Failed to fetch images.");
return message.reply({
body: `✅ Found ${attachments.length} image(s) for "${query}"`,
attachment: attachments
});
}
// Video mode: send thumbnails with numbers, user replies with a number
// to pick which video to download.
const thumbs = [];
const list = [];
results.forEach((item, i) => {
const dur = item.duration_ms ? ` (${Math.round(item.duration_ms / 1000)}s)` : "";
list.push(`${i + 1}.${dur} ${item.alt || "No title"}`.slice(0, 80));
});
for (const item of results) {
try {
thumbs.push(await fetchAttachment(item.thumbnail, `${item.id}.jpg`));
} catch {
// skip failed thumbnail
}
}
if (!thumbs.length) return message.reply("❌ Failed to load thumbnails.");
const pickMsg = await message.reply({
body: `🎬 Found ${thumbs.length} video(s) for "${query}"\n\n${list.join("\n")}\n\n👉 Reply with a number (1-${thumbs.length}) to download`,
attachment: thumbs
});
global.GoatBot.onReply.set(pickMsg.messageID, {
commandName: this.config.name,
messageID: pickMsg.messageID,
author: event.senderID,
results
});
} catch (err) {
const reason = await readErrorBody(err) || err.message;
return message.reply(`❌ Error: ${reason}`);
} finally {
if (waitMsg && waitMsg.messageID) {
try { api.unsendMessage(waitMsg.messageID); } catch {}
}
}
},
onReply: async function ({ api, event, Reply, message }) {
if (event.senderID !== Reply.author) return;
const choice = parseInt(event.body?.trim(), 10);
const item = Reply.results[choice - 1];
if (!choice || !item) {
return message.reply(`⚠️ Please reply with a number between 1 and ${Reply.results.length}.`);
}
global.GoatBot.onReply.delete(Reply.messageID);
const waitMsg = await message.reply(`⬇️ Downloading video ${choice}...`);
try {
const dlUrl = item.download_url.startsWith("http")
? item.download_url
: `${BASE_URL}${item.download_url}`;
const attachment = await fetchAttachment(dlUrl, `${item.id}.mp4`);
await message.reply({ attachment });
} catch (e) {
const reason = await readErrorBody(e) || e.message;
await message.reply(`❌ Failed to download video ${choice}.\nReason: ${reason}`);
} finally {
if (waitMsg && waitMsg.messageID) {
try { api.unsendMessage(waitMsg.messageID); } catch {}
}
}
}
};