Skip to content

Commit 2a4ecee

Browse files
committed
plugin: add youtube download plugin
Signed-off-by: staylightblow8 <liudf0716@gmail.com>
1 parent 46e3f7d commit 2a4ecee

File tree

6 files changed

+313
-5
lines changed

6 files changed

+313
-5
lines changed

CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ set(src_xfrpc
8686
set(src_xfrpc_plugins
8787
plugins/telnetd.c
8888
plugins/instaloader.c
89-
plugins/httpd.c)
89+
plugins/httpd.c
90+
plugins/youtubedl.c)
9091

9192
set(libs
9293
ssl

config.c

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -325,21 +325,28 @@ process_plugin_conf(struct proxy_service *ps)
325325
if (ps->local_port == 0)
326326
ps->local_port = XFRPC_PLUGIN_INSTALOADER_PORT;
327327
if (ps->remote_port == 0)
328-
ps->remote_port = XFRPC_PLUGIN_INSTALOADER_ROMOTE_PORT;
328+
ps->remote_port = XFRPC_PLUGIN_INSTALOADER_REMOTE_PORT;
329329
if (ps->local_ip == NULL)
330330
ps->local_ip = strdup("127.0.0.1");
331331
} else if (strcmp(ps->plugin, "instaloader_client") == 0) {
332332
if (ps->local_port == 0)
333333
ps->local_port = XFRPC_PLUGIN_INSTALOADER_PORT;
334334
if (ps->remote_port == 0)
335-
ps->remote_port == XFRPC_PLUGIN_INSTALOADER_ROMOTE_PORT;
335+
ps->remote_port == XFRPC_PLUGIN_INSTALOADER_REMOTE_PORT;
336336
if (ps->local_ip == NULL)
337337
ps->local_ip = strdup("0.0.0.0");
338+
} else if (strcmp(ps->plugin, "youtubedl") == 0) {
339+
if (ps->local_port == 0)
340+
ps->local_port = XFRPC_PLUGIN_YOUTUBEDL_PORT;
341+
if (ps->remote_port == 0)
342+
ps->remote_port = XFRPC_PLUGIN_YOUTUBEDL_REMOTE_PORT;
343+
if (ps->local_ip == NULL)
344+
ps->local_ip = strdup("127.0.0.1");
338345
} else if (strcmp(ps->plugin, "httpd") == 0) {
339346
if (ps->local_port == 0)
340347
ps->local_port = XFRPC_PLUGIN_HTTPD_PORT;
341348
if (ps->local_ip == NULL)
342-
ps->local_ip = strdup("0.0.0.0");
349+
ps->local_ip = strdup("127.0.0.1");
343350
if (ps->remote_port == 0)
344351
ps->remote_port = XFRPC_PLUGIN_HTTPD_REMOTE_PORT;
345352
if (ps->s_root_dir == NULL)

config.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@
3333
#define DEFAULT_SOCKS5_PORT 1980
3434
#define XFRPC_PLUGIN_TELNETD_PORT 23
3535
#define XFRPC_PLUGIN_INSTALOADER_PORT 10000
36-
#define XFRPC_PLUGIN_INSTALOADER_ROMOTE_PORT 10001
36+
#define XFRPC_PLUGIN_INSTALOADER_REMOTE_PORT 10001
37+
#define XFRPC_PLUGIN_YOUTUBEDL_PORT 20002
38+
#define XFRPC_PLUGIN_YOUTUBEDL_REMOTE_PORT 20003
3739
#define XFRPC_PLUGIN_HTTPD_PORT 8000
3840
#define XFRPC_PLUGIN_HTTPD_REMOTE_PORT 8001
3941

plugins/youtubedl.c

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
2+
3+
#include <json-c/json.h>
4+
#include <string.h>
5+
#include <stdlib.h>
6+
#include <stdio.h>
7+
#include <assert.h>
8+
#include <errno.h>
9+
#include <pthread.h>
10+
#include <unistd.h>
11+
12+
#include <event2/http.h>
13+
14+
#include "../common.h"
15+
#include "../debug.h"
16+
#include "../config.h"
17+
#include "youtubedl.h"
18+
19+
struct yt_dlp_param {
20+
char action[10];
21+
char profile[100];
22+
};
23+
24+
// define yt-dlp worker function
25+
static void *
26+
yt_dlp_worker(void *param)
27+
{
28+
struct yt_dlp_param *p = (struct yt_dlp_param *)param;
29+
debug(LOG_DEBUG, "yt-dlp: action: %s, url: %s\n", p->action, p->profile);
30+
char cmd[512] = {0};
31+
32+
// create directory yt-dlp and change current directory to it
33+
snprintf(cmd, sizeof(cmd), "mkdir -p yt-dlp && cd yt-dlp");
34+
debug(LOG_DEBUG, "yt-dlp: cmd: %s\n", cmd);
35+
system(cmd);
36+
37+
if (strcmp(p->action, "download") == 0) {
38+
// download profile
39+
snprintf(cmd, sizeof(cmd), "yt-dlp %s", p->profile);
40+
debug(LOG_DEBUG, "yt-dlp: cmd: %s\n", cmd);
41+
// use popen to execute cmd and get its output
42+
FILE *fp = popen(cmd, "r");
43+
if (fp == NULL) {
44+
debug(LOG_ERR, "yt-dlp: popen failed\n");
45+
free(param);
46+
return NULL;
47+
}
48+
char buf[512] = {0};
49+
while (fgets(buf, sizeof(buf), fp) != NULL) {
50+
debug(LOG_DEBUG, "yt-dlp: %s", buf);
51+
memset(buf, 0, sizeof(buf));
52+
}
53+
pclose(fp);
54+
} else {
55+
debug(LOG_ERR, "yt-dlp: unknown action: %s\n", p->action);
56+
}
57+
58+
// free param
59+
free(param);
60+
61+
return 0;
62+
}
63+
64+
static int
65+
parse_yt_dlp_command(char *json_data, struct yt_dlp_param *param)
66+
{
67+
// parse json data with json-c to param
68+
json_object *jobj = json_tokener_parse(json_data);
69+
if (jobj == NULL) {
70+
debug(LOG_ERR, "yt-dlp: json_tokener_parse failed\n");
71+
return -1;
72+
}
73+
74+
// get action
75+
json_object *jaction = NULL;
76+
if (!json_object_object_get_ex(jobj, "action", &jaction)) {
77+
debug(LOG_ERR, "yt-dlp: json_object_object_get_ex failed\n");
78+
json_object_put(jobj);
79+
return -1;
80+
}
81+
strcpy(param->action, json_object_get_string(jaction));
82+
if (strcmp(param->action, "stop") == 0) {
83+
json_object_put(jobj);
84+
return 0;
85+
}
86+
87+
// get profile
88+
json_object *jprofile = NULL;
89+
if (!json_object_object_get_ex(jobj, "profile", &jprofile)) {
90+
debug(LOG_ERR, "yt-dlp: json_object_object_get_ex failed\n");
91+
json_object_put(jobj);
92+
return -1;
93+
}
94+
strcpy(param->profile, json_object_get_string(jprofile));
95+
96+
// free json object
97+
json_object_put(jobj);
98+
99+
return 0;
100+
}
101+
102+
static void
103+
yt_dlp_response(struct evhttp_request *req, char *result)
104+
{
105+
struct evbuffer *resp = evbuffer_new();
106+
evbuffer_add_printf(resp, "{\"status\": \"%s\"}", result);
107+
evhttp_add_header(evhttp_request_get_output_headers(req), "Content-Type", "application/json");
108+
evhttp_send_reply(req, HTTP_OK, "OK", resp);
109+
}
110+
111+
// define yt-dlp read callback function
112+
static void
113+
yt_dlp_read_cb(struct evhttp_request *req, void *args)
114+
{
115+
#define BUFF_LEN 4096
116+
// read data from bufferevent
117+
char data[BUFF_LEN] = {0};
118+
struct evbuffer *input = evhttp_request_get_input_buffer(req);
119+
size_t len = evbuffer_get_length(input);
120+
assert(len < BUFF_LEN);
121+
if (len >= BUFF_LEN) {
122+
debug(LOG_ERR, "yt-dlp: data length is too long\n");
123+
yt_dlp_response(req, "data length is too long");
124+
return;
125+
}
126+
debug(LOG_DEBUG, "yt-dlp: data: %s\n", data);
127+
128+
// parse http post and get its json data
129+
evbuffer_copyout(input, data, len);
130+
debug(LOG_DEBUG, "yt-dlp: data: %s\n", data);
131+
132+
struct yt_dlp_param *param = (struct yt_dlp_param *)malloc(sizeof(struct yt_dlp_param));
133+
assert(param != NULL);
134+
memset(param, 0, sizeof(struct yt_dlp_param));
135+
136+
int nret = parse_yt_dlp_command (data, param);
137+
if (nret != 0) {
138+
debug(LOG_ERR, "yt-dlp: parse_command failed\n");
139+
free(param);
140+
yt_dlp_response(req, "failed to parse command");
141+
return;
142+
}
143+
144+
// create a thread
145+
pthread_t thread;
146+
// create a thread attribute
147+
pthread_attr_t attr;
148+
// initialize thread attribute
149+
pthread_attr_init(&attr);
150+
// set thread attribute to detach
151+
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
152+
// create a thread
153+
pthread_create(&thread, &attr, yt_dlp_worker, param);
154+
// destroy thread attribute
155+
pthread_attr_destroy(&attr);
156+
157+
yt_dlp_response(req, "ok");
158+
}
159+
160+
// define yt-dlp http post callback function
161+
static void
162+
http_post_cb(struct evhttp_request *req, void *arg)
163+
{
164+
// check http request method
165+
if (evhttp_request_get_command(req) != EVHTTP_REQ_POST) {
166+
debug(LOG_ERR, "yt-dlp: http request method is not POST\n");
167+
evhttp_send_error(req, HTTP_BADMETHOD, "Method Not Allowed");
168+
return;
169+
}
170+
171+
// Check the HTTP request content type
172+
const char *content_type = evhttp_find_header(evhttp_request_get_input_headers(req), "Content-Type");
173+
if (content_type == NULL || strcmp(content_type, "application/json") != 0) {
174+
debug(LOG_ERR, "yt-dlp: http request content type is not application/json\n");
175+
evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
176+
return;
177+
}
178+
179+
// get json data from http request
180+
yt_dlp_read_cb(req, arg);
181+
182+
}
183+
184+
static int
185+
install_yt_dlp()
186+
{
187+
// if yt-dlp exists, return
188+
if (access("/usr/local/bin/yt-dlp", F_OK) == 0) {
189+
debug(LOG_DEBUG, "yt-dlp: yt-dlp exists\n");
190+
return 0;
191+
}
192+
193+
// install yt-dlp to /usr/local/bin
194+
// download yt-dlp through curl or wget if any of them exists
195+
char cmd[512] = {0};
196+
if (access("/usr/bin/curl", F_OK) == 0) {
197+
snprintf(cmd, sizeof(cmd), "sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp");
198+
} else if (access("/usr/bin/wget", F_OK) == 0) {
199+
snprintf(cmd, sizeof(cmd), "sudo wget https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -O /usr/local/bin/yt-dlp");
200+
} else {
201+
debug(LOG_ERR, "yt-dlp: curl and wget are not installed\n");
202+
return -1;
203+
}
204+
debug(LOG_DEBUG, "yt-dlp: cmd: %s\n", cmd);
205+
int nret = system(cmd);
206+
if (nret != 0) {
207+
debug(LOG_ERR, "yt-dlp: system failed\n");
208+
return -1;
209+
}
210+
// change yt-dlp to executable
211+
snprintf(cmd, sizeof(cmd), "sudo chmod a+rx /usr/local/bin/yt-dlp");
212+
debug(LOG_DEBUG, "yt-dlp: cmd: %s\n", cmd);
213+
nret = system(cmd);
214+
if (nret != 0) {
215+
debug(LOG_ERR, "yt-dlp: system failed\n");
216+
return -1;
217+
}
218+
219+
return 0;
220+
}
221+
222+
// define yt-dlp service
223+
static void *
224+
yt_dlp_service(void *local_port)
225+
{
226+
// install yt-dlp
227+
int nret = install_yt_dlp();
228+
if (nret != 0) {
229+
debug(LOG_ERR, "yt-dlp: install_yt_dlp failed\n");
230+
return NULL;
231+
}
232+
233+
uint16_t port = *(uint16_t *)local_port;
234+
free(local_port);
235+
// Initialize libevent
236+
struct event_base *base = event_base_new();
237+
if (!base) {
238+
debug(LOG_ERR, "yt-dlp: Failed to initialize libevent\n");
239+
return NULL;
240+
}
241+
242+
// Create a new HTTP server
243+
struct evhttp *http = evhttp_new(base);
244+
if (!http) {
245+
debug(LOG_ERR, "yt-dlp: Failed to create HTTP server\n");
246+
return NULL;
247+
}
248+
249+
250+
if (evhttp_bind_socket(http, "0.0.0.0", port) != 0) {
251+
debug(LOG_ERR, "yt-dlp: Failed to bind HTTP server to port %d\n", port);
252+
return NULL;
253+
}
254+
255+
debug(LOG_DEBUG, "yt-dlp: start youtube download service on port %d\n", port);
256+
257+
// Set up a callback function for handling HTTP requests
258+
evhttp_set_cb(http, "/", http_post_cb, NULL);
259+
260+
// Start the event loop
261+
event_base_dispatch(base);
262+
263+
// Clean up
264+
evhttp_free(http);
265+
event_base_free(base);
266+
return NULL;
267+
}
268+
269+
int
270+
start_youtubedl_service(uint16_t local_port)
271+
{
272+
uint16_t *p = (uint16_t *)malloc(sizeof(uint16_t));
273+
assert(p != NULL);
274+
*p = local_port;
275+
// create a thread
276+
pthread_t thread;
277+
// create a thread attribute
278+
pthread_attr_t attr;
279+
// initialize thread attribute
280+
pthread_attr_init(&attr);
281+
// set thread attribute to detach
282+
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
283+
// create a thread
284+
pthread_create(&thread, &attr, yt_dlp_service, (void *)p);
285+
// destroy thread attribute
286+
pthread_attr_destroy(&attr);
287+
288+
return 0;
289+
}

plugins/youtubedl.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#ifndef _YOUTUBE_DL_H_
2+
#define _YOUTUBE_DL_H_
3+
4+
int start_youtubedl_service(uint16_t local_port);
5+
6+
#endif

xfrpc.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ static void start_xfrpc_local_service()
6565
} else if (strcmp(ps->plugin, "instaloader") == 0) {
6666
// start instaloader service
6767
start_instaloader_service(ps->local_port);
68+
} else if (strcmp(ps->plugin, "youtubedl") == 0) {
69+
// start youtubedl service
70+
start_youtubedl_service(ps->local_port);
6871
} else if (strcmp(ps->plugin, "instaloader_redir") == 0) {
6972
start_tcp_redir_service(ps);
7073
} else if (strcmp(ps->plugin, "httpd") == 0) {

0 commit comments

Comments
 (0)