Sylvatica.

Back to Library

Pinterest Downloader

js 1/27/2026
11
const axios = require("axios");

class Pinterest {
  constructor() {
    this.api = {
      base: "https://www.pinterest.com",
      endpoints: {
        pin: "/resource/PinResource/get/",
      },
    };
    this.headers = {
      accept: "application/json, text/javascript, */*, q=0.01",
      referer: "https://www.pinterest.com/",
      "user-agent":
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
      "x-app-version": "f1222d7",
      "x-pinterest-appstate": "active",
      "x-pinterest-pws-handler": "www/[username]/[slug].js",
      "x-pinterest-source-url": "/search/pins/?rs=typed&q=xxx/",
      "x-requested-with": "XMLHttpRequest",
    };
    this.client = axios.create({
      baseURL: this.api.base,
      headers: this.headers,
    });
    this.cookies = "";
    
    this.client.interceptors.response.use(
      (response) => {
        const setCookieHeaders = response.headers["set-cookie"];
        if (setCookieHeaders) {
          const newCookies = setCookieHeaders.map((cookieString) => {
            const cp = cookieString.split(";");
            return cp[0].trim();
          });
          this.cookies = newCookies.join("; ");
          this.client.defaults.headers.cookie = this.cookies;
        }
        return response;
      },
      (error) => Promise.reject(error)
    );
  }

  isUrl(str) {
    try {
      new URL(str);
      return true;
    } catch (_) {
      return false;
    }
  }

  isPin(url) {
    if (!url) return false;
    const patterns = [
      /^https?:\/\/(?:[\w-]+\.)?pinterest\.[\w.]+\/pin\/[\w.-]+/,
      /^https?:\/\/pin\.it\/[\w.-]+/,
      /^https?:\/\/(?:[\w-]+\.)?pinterest\.[\w.]+\/pin\/[\d]+(?:\/)?/,
    ];
    const clean = url.trim().toLowerCase();
    return patterns.some((pattern) => pattern.test(clean));
  }

  async followRedirects(url, maxRedirects = 2) {
    try {
      let currentUrl = url;
      let redirectCount = 0;
      while (redirectCount < maxRedirects) {
        const response = await axios.head(currentUrl, {
          maxRedirects: 0,
          validateStatus: (status) => status < 400 || (status >= 300 && status < 400),
          timeout: 10000,
        });
        if (response.status >= 300 && response.status < 400 && response.headers.location) {
          currentUrl = response.headers.location;
          if (!currentUrl.startsWith("http")) {
            const baseUrl = new URL(url);
            currentUrl = new URL(currentUrl, baseUrl.origin).href;
          }
          redirectCount++;
        } else {
          break;
        }
      }
      return currentUrl;
    } catch (error) {
      if (error.response && error.response.status >= 300 && error.response.status < 400) {
        return error.response.headers.location || url;
      }
      return url;
    }
  }

  async initCookies() {
    try {
      await this.client.get("/");
      return true;
    } catch (error) {
      return false;
    }
  }

  async download(pinUrl) {
    if (!pinUrl || !this.isUrl(pinUrl)) throw new Error("Invalid URL");
    
    try {
      const finalUrl = await this.followRedirects(pinUrl, 2);
      if (!this.isPin(finalUrl)) throw new Error("Not a valid Pinterest URL");

      const pinId = finalUrl.split("/pin/")[1]?.split("/")[0]?.split("?")[0];
      if (!pinId) throw new Error("Could not extract Pin ID");

      if (!this.cookies) await this.initCookies();

      const params = {
        source_url: `/pin/${pinId}/`,
        data: JSON.stringify({
          options: { field_set_key: "detailed", id: pinId },
          context: {},
        }),
        _: Date.now(),
      };

      const { data } = await this.client.get(this.api.endpoints.pin, { params });

      if (!data.resource_response.data) throw new Error("Pin not found");

      const pd = data.resource_response.data;
      const mediaUrls = [];

      // Video
      if (pd.videos?.video_list) {
        const firstVideoKey = Object.keys(pd.videos.video_list)[0];
        let videoUrl = pd.videos.video_list[firstVideoKey]?.url;
        if (videoUrl && firstVideoKey.includes("HLS") && videoUrl.includes("m3u8")) {
          videoUrl = videoUrl.replace("hls", "720p").replace("m3u8", "mp4");
        }
        mediaUrls.push({
          type: "video",
          url: videoUrl,
          thumbnail: pd.videos.video_list[firstVideoKey].thumbnail || pd.images?.orig?.url,
        });
      }

      // Images
      if (pd.images) {
         mediaUrls.push({
            type: "image",
            url: pd.images.orig.url,
         });
      }

      return {
          id: pd.id,
          title: pd.title || pd.grid_title || "No Title",
          description: pd.description || "",
          media: mediaUrls
      };

    } catch (error) {
      throw error;
    }
  }
}

// Export fungsi simple
const pinterestInstance = new Pinterest();
async function pinterestdl(url) {
    return await pinterestInstance.download(url);
}

module.exports = { pinterestdl };