OhMyDitzzy commited on
Commit
54f33eb
·
1 Parent(s): e8e03dd

Add: twitter downloader

Browse files
src/server/plugins/downloader/twitter.js ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import axios from "axios";
2
+ import { sendSuccess, ErrorResponses } from "../../lib/response-helper.js";
3
+ import * as cheerio from 'cheerio';
4
+ import FormData from 'form-data';
5
+
6
+ export class Twitter {
7
+ constructor() {
8
+ this.CREATED_BY = "Ditzzy";
9
+ this.NOTE = "Thank you for using this scrape, I hope you appreciate me for making this scrape by not deleting wm";
10
+ }
11
+
12
+ wrapResponse(data) {
13
+ return {
14
+ created_by: this.CREATED_BY,
15
+ note: this.NOTE,
16
+ results: data
17
+ };
18
+ }
19
+
20
+ async download(link) {
21
+ try {
22
+ const formData = new FormData();
23
+ formData.append('page', link);
24
+ formData.append('ftype', 'all');
25
+ formData.append('ajax', '1');
26
+
27
+ const response = await axios.post('https://twmate.com/id2/', formData, {
28
+ headers: {
29
+ ...formData.getHeaders(),
30
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
31
+ }
32
+ });
33
+
34
+ const html = response.data;
35
+ const $ = cheerio.load(html);
36
+
37
+ const result = {
38
+ media: []
39
+ };
40
+
41
+ const videoTable = $('.files-table tbody tr');
42
+
43
+ if (videoTable.length > 0) {
44
+ const titleElement = $('.info-container h4');
45
+ if (titleElement.length) {
46
+ const fullTitle = titleElement.text().trim();
47
+ const separatorIndex = fullTitle.indexOf(' - ');
48
+
49
+ if (separatorIndex !== -1) {
50
+ result.username = fullTitle.substring(0, separatorIndex).trim();
51
+ result.caption = fullTitle.substring(separatorIndex + 3).trim();
52
+ } else {
53
+ result.caption = fullTitle;
54
+ }
55
+ }
56
+
57
+ const thumbnailElement = $('.thumb-container img');
58
+ if (thumbnailElement.length) {
59
+ result.thumbnail = thumbnailElement.attr('src');
60
+ }
61
+
62
+ const likesElement = $('.info-container p span:contains("Suka")');
63
+ if (likesElement.length) {
64
+ result.likes = likesElement.text().replace('Suka : ', '').trim();
65
+ }
66
+
67
+ videoTable.each((_, element) => {
68
+ const quality = $(element).find('td:nth-child(1)').text().trim();
69
+ const type = $(element).find('td:nth-child(2)').text().trim();
70
+ const url = $(element).find('td:nth-child(3) a').attr('href');
71
+
72
+ if (url) {
73
+ result.media.push({
74
+ type,
75
+ quality,
76
+ url
77
+ });
78
+ }
79
+ });
80
+ } else {
81
+ $('.card.icard').each((_, card) => {
82
+ $(card).find('.card-body a.btn-dl').each((_, link) => {
83
+ const downloadUrl = $(link).attr('href');
84
+ const qualityText = $(link).text().trim();
85
+ const quality = qualityText.replace('Unduh ', '').replace(/\s+/g, '');
86
+
87
+ if (downloadUrl) {
88
+ result.media.push({
89
+ type: 'image',
90
+ quality,
91
+ url: downloadUrl
92
+ });
93
+ }
94
+ });
95
+ });
96
+ }
97
+
98
+ return this.wrapResponse(result);
99
+
100
+ } catch (error) {
101
+ throw new Error(`Failed to download: ${error instanceof Error ? error.message : 'Unknown error'}`);
102
+ }
103
+ }
104
+ }
105
+
106
+ /** @type {import("../../types/plugin").ApiPluginHandler} */
107
+ const handler = {
108
+ name: "Twitter Downloader",
109
+ description: "Download Twitter Media, Support Photo too",
110
+ version: "1.0.0",
111
+ method: "GET",
112
+ category: ["downloader"],
113
+ alias: ["twitter", "tw"],
114
+ tags: ["social-media", "video", "downloader"],
115
+ parameters: {
116
+ query: [
117
+ {
118
+ name: "url",
119
+ type: "string",
120
+ required: true,
121
+ description: "Your Twitter URL",
122
+ example: "https://x.com/ClashofClans/status/2013235147978494164?s=20"
123
+ }
124
+ ],
125
+ body: [],
126
+ headers: []
127
+ },
128
+ responses: {
129
+ 200: {
130
+ status: 200,
131
+ description: "Successfully retrieved Twitter data",
132
+ example: {
133
+ status: 200,
134
+ author: "Ditzzy",
135
+ note: "Thank you for using this API!",
136
+ results: {}
137
+ }
138
+ },
139
+ 400: {
140
+ status: 400,
141
+ description: "Invalid Twitter URL provided",
142
+ example: {
143
+ status: 400,
144
+ message: "Invalid URL - must be a valid Twitter URL"
145
+ }
146
+ },
147
+ 404: {
148
+ status: 404,
149
+ description: "Missing required parameter",
150
+ example: {
151
+ status: 404,
152
+ message: "Missing required parameter: ..."
153
+ }
154
+ },
155
+ 500: {
156
+ status: 500,
157
+ description: "Server error or Twitter API unavailable",
158
+ example: {
159
+ status: 500,
160
+ message: "An error occurred, please try again later."
161
+ }
162
+ }
163
+ },
164
+ exec: async (req, res) => {
165
+ const { url } = req.query
166
+
167
+ if (!url) return ErrorResponses.missingParameter(res, "url");
168
+
169
+ const regex = /^https?:\/\/(?:www\.)?(?:x\.com|twitter\.com)\/[a-zA-Z0-9_]+\/status\/(\d+)(?:\?.*)?$/;
170
+
171
+ if (!regex.test(url)) return ErrorResponses.invalidUrl(res, "Invalid URL - must be a valid Twitter URL");
172
+
173
+ const tw = new Twitter();
174
+ try {
175
+ const download = await tw.download(url);
176
+
177
+ if (download.results.media.length === 0) return ErrorResponses.notFound(res, "Twitter link is invalid, private or Server returned null data");
178
+
179
+ return sendSuccess(res, download.results);
180
+ } catch (e) {
181
+ console.error("Twitter download error:", e);
182
+ return ErrorResponses.notFound(res, "Twitter link is invalid or Server returned null data");
183
+ }
184
+ }
185
+ }
186
+
187
+ export default handler;