| import axios from "axios"; |
| import { sendSuccess, ErrorResponses } from "../../lib/response-helper.js"; |
| import * as cheerio from 'cheerio'; |
| import FormData from 'form-data'; |
|
|
| export class Twitter { |
| constructor() { |
| this.CREATED_BY = "Ditzzy"; |
| this.NOTE = "Thank you for using this scrape, I hope you appreciate me for making this scrape by not deleting wm"; |
| } |
| |
| wrapResponse(data) { |
| return { |
| created_by: this.CREATED_BY, |
| note: this.NOTE, |
| results: data |
| }; |
| } |
|
|
| async download(link) { |
| try { |
| const formData = new FormData(); |
| formData.append('page', link); |
| formData.append('ftype', 'all'); |
| formData.append('ajax', '1'); |
|
|
| const response = await axios.post('https://twmate.com/id2/', formData, { |
| headers: { |
| ...formData.getHeaders(), |
| 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' |
| } |
| }); |
|
|
| const html = response.data; |
| const $ = cheerio.load(html); |
|
|
| const result = { |
| media: [] |
| }; |
|
|
| const videoTable = $('.files-table tbody tr'); |
| |
| if (videoTable.length > 0) { |
| const titleElement = $('.info-container h4'); |
| if (titleElement.length) { |
| const fullTitle = titleElement.text().trim(); |
| const separatorIndex = fullTitle.indexOf(' - '); |
| |
| if (separatorIndex !== -1) { |
| result.username = fullTitle.substring(0, separatorIndex).trim(); |
| result.caption = fullTitle.substring(separatorIndex + 3).trim(); |
| } else { |
| result.caption = fullTitle; |
| } |
| } |
|
|
| const thumbnailElement = $('.thumb-container img'); |
| if (thumbnailElement.length) { |
| result.thumbnail = thumbnailElement.attr('src'); |
| } |
|
|
| const likesElement = $('.info-container p span:contains("Suka")'); |
| if (likesElement.length) { |
| result.likes = likesElement.text().replace('Suka : ', '').trim(); |
| } |
|
|
| videoTable.each((_, element) => { |
| const quality = $(element).find('td:nth-child(1)').text().trim(); |
| const type = $(element).find('td:nth-child(2)').text().trim(); |
| const url = $(element).find('td:nth-child(3) a').attr('href'); |
|
|
| if (url) { |
| result.media.push({ |
| type, |
| quality, |
| url |
| }); |
| } |
| }); |
| } else { |
| $('.card.icard').each((_, card) => { |
| $(card).find('.card-body a.btn-dl').each((_, link) => { |
| const downloadUrl = $(link).attr('href'); |
| const qualityText = $(link).text().trim(); |
| const quality = qualityText.replace('Unduh ', '').replace(/\s+/g, ''); |
|
|
| if (downloadUrl) { |
| result.media.push({ |
| type: 'image', |
| quality, |
| url: downloadUrl |
| }); |
| } |
| }); |
| }); |
| } |
|
|
| return this.wrapResponse(result); |
|
|
| } catch (error) { |
| throw new Error(`Failed to download: ${error instanceof Error ? error.message : 'Unknown error'}`); |
| } |
| } |
| } |
|
|
| |
| const handler = { |
| name: "Twitter Downloader", |
| description: "Download Twitter Media, Support Photo too", |
| version: "1.0.0", |
| method: "GET", |
| category: ["downloader"], |
| alias: ["twitter", "tw"], |
| tags: ["social-media", "video", "downloader"], |
| parameters: { |
| query: [ |
| { |
| name: "url", |
| type: "string", |
| required: true, |
| description: "Your Twitter URL", |
| example: "https://x.com/ClashofClans/status/2013235147978494164?s=20" |
| } |
| ], |
| body: [], |
| headers: [] |
| }, |
| responses: { |
| 200: { |
| status: 200, |
| description: "Successfully retrieved Twitter data", |
| example: { |
| status: 200, |
| author: "Ditzzy", |
| note: "Thank you for using this API!", |
| results: {} |
| } |
| }, |
| 400: { |
| status: 400, |
| description: "Invalid Twitter URL provided", |
| example: { |
| status: 400, |
| message: "Invalid URL - must be a valid Twitter URL" |
| } |
| }, |
| 404: { |
| status: 404, |
| description: "Missing required parameter", |
| example: { |
| status: 404, |
| message: "Missing required parameter: ..." |
| } |
| }, |
| 500: { |
| status: 500, |
| description: "Server error or Twitter API unavailable", |
| example: { |
| status: 500, |
| message: "An error occurred, please try again later." |
| } |
| } |
| }, |
| exec: async (req, res) => { |
| const { url } = req.query |
| |
| if (!url) return ErrorResponses.missingParameter(res, "url"); |
| |
| const regex = /^https?:\/\/(?:www\.)?(?:x\.com|twitter\.com)\/[a-zA-Z0-9_]+\/status\/(\d+)(?:\?.*)?$/; |
| |
| if (!regex.test(url)) return ErrorResponses.invalidUrl(res, "Invalid URL - must be a valid Twitter URL"); |
| |
| const tw = new Twitter(); |
| try { |
| const download = await tw.download(url); |
| |
| if (download.results.media.length === 0) return ErrorResponses.notFound(res, "Twitter link is invalid, private or Server returned null data"); |
| |
| return sendSuccess(res, download.results); |
| } catch (e) { |
| console.error("Twitter download error:", e); |
| return ErrorResponses.notFound(res, "Twitter link is invalid or Server returned null data"); |
| } |
| } |
| } |
|
|
| export default handler; |