Skip to content

Commit

Permalink
plugin: add youtube download plugin
Browse files Browse the repository at this point in the history
Signed-off-by: staylightblow8 <liudf0716@gmail.com>
  • Loading branch information
liudf0716 committed Dec 9, 2023
1 parent 46e3f7d commit b283733
Show file tree
Hide file tree
Showing 6 changed files with 312 additions and 5 deletions.
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ set(src_xfrpc
set(src_xfrpc_plugins
plugins/telnetd.c
plugins/instaloader.c
plugins/httpd.c)
plugins/httpd.c
plugins/youtubedl.c)

set(libs
ssl
Expand Down
13 changes: 10 additions & 3 deletions config.c
Original file line number Diff line number Diff line change
Expand Up @@ -325,21 +325,28 @@ process_plugin_conf(struct proxy_service *ps)
if (ps->local_port == 0)
ps->local_port = XFRPC_PLUGIN_INSTALOADER_PORT;
if (ps->remote_port == 0)
ps->remote_port = XFRPC_PLUGIN_INSTALOADER_ROMOTE_PORT;
ps->remote_port = XFRPC_PLUGIN_INSTALOADER_REMOTE_PORT;
if (ps->local_ip == NULL)
ps->local_ip = strdup("127.0.0.1");
} else if (strcmp(ps->plugin, "instaloader_client") == 0) {
if (ps->local_port == 0)
ps->local_port = XFRPC_PLUGIN_INSTALOADER_PORT;
if (ps->remote_port == 0)
ps->remote_port == XFRPC_PLUGIN_INSTALOADER_ROMOTE_PORT;
ps->remote_port == XFRPC_PLUGIN_INSTALOADER_REMOTE_PORT;
if (ps->local_ip == NULL)
ps->local_ip = strdup("0.0.0.0");
} else if (strcmp(ps->plugin, "youtubedl") == 0) {
if (ps->local_port == 0)
ps->local_port = XFRPC_PLUGIN_YOUTUBEDL_PORT;
if (ps->remote_port == 0)
ps->remote_port = XFRPC_PLUGIN_YOUTUBEDL_REMOTE_PORT;
if (ps->local_ip == NULL)
ps->local_ip = strdup("127.0.0.1");
} else if (strcmp(ps->plugin, "httpd") == 0) {
if (ps->local_port == 0)
ps->local_port = XFRPC_PLUGIN_HTTPD_PORT;
if (ps->local_ip == NULL)
ps->local_ip = strdup("0.0.0.0");
ps->local_ip = strdup("127.0.0.1");
if (ps->remote_port == 0)
ps->remote_port = XFRPC_PLUGIN_HTTPD_REMOTE_PORT;
if (ps->s_root_dir == NULL)
Expand Down
4 changes: 3 additions & 1 deletion config.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
#define DEFAULT_SOCKS5_PORT 1980
#define XFRPC_PLUGIN_TELNETD_PORT 23
#define XFRPC_PLUGIN_INSTALOADER_PORT 10000
#define XFRPC_PLUGIN_INSTALOADER_ROMOTE_PORT 10001
#define XFRPC_PLUGIN_INSTALOADER_REMOTE_PORT 10001
#define XFRPC_PLUGIN_YOUTUBEDL_PORT 20002
#define XFRPC_PLUGIN_YOUTUBEDL_REMOTE_PORT 20003
#define XFRPC_PLUGIN_HTTPD_PORT 8000
#define XFRPC_PLUGIN_HTTPD_REMOTE_PORT 8001

Expand Down
288 changes: 288 additions & 0 deletions plugins/youtubedl.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@


#include <json-c/json.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <pthread.h>

#include <event2/http.h>

#include "../common.h"
#include "../debug.h"
#include "../config.h"
#include "youtubedl.h"

struct yt_dlp_param {
char action[10];
char profile[100];
};

// define yt-dlp worker function
static void *
yt_dlp_worker(void *param)
{
struct yt_dlp_param *p = (struct yt_dlp_param *)param;
debug(LOG_DEBUG, "yt-dlp: action: %s, url: %s\n", p->action, p->profile);
char cmd[512] = {0};

// create directory yt-dlp and change current directory to it
snprintf(cmd, sizeof(cmd), "mkdir -p yt-dlp && cd yt-dlp");
debug(LOG_DEBUG, "yt-dlp: cmd: %s\n", cmd);
system(cmd);

if (strcmp(p->action, "download") == 0) {
// download profile
snprintf(cmd, sizeof(cmd), "yt-dlp %s", p->profile);
debug(LOG_DEBUG, "yt-dlp: cmd: %s\n", cmd);
// use popen to execute cmd and get its output
FILE *fp = popen(cmd, "r");
if (fp == NULL) {
debug(LOG_ERR, "yt-dlp: popen failed\n");
free(param);
return NULL;
}
char buf[512] = {0};
while (fgets(buf, sizeof(buf), fp) != NULL) {
debug(LOG_DEBUG, "yt-dlp: %s", buf);
memset(buf, 0, sizeof(buf));
}
pclose(fp);
} else {
debug(LOG_ERR, "yt-dlp: unknown action: %s\n", p->action);
}

// free param
free(param);

return 0;
}

static int
parse_yt_dlp_command(char *json_data, struct yt_dlp_param *param)
{
// parse json data with json-c to param
json_object *jobj = json_tokener_parse(json_data);
if (jobj == NULL) {
debug(LOG_ERR, "yt-dlp: json_tokener_parse failed\n");
return -1;
}

// get action
json_object *jaction = NULL;
if (!json_object_object_get_ex(jobj, "action", &jaction)) {
debug(LOG_ERR, "yt-dlp: json_object_object_get_ex failed\n");
json_object_put(jobj);
return -1;
}
strcpy(param->action, json_object_get_string(jaction));
if (strcmp(param->action, "stop") == 0) {
json_object_put(jobj);
return 0;
}

// get profile
json_object *jprofile = NULL;
if (!json_object_object_get_ex(jobj, "profile", &jprofile)) {
debug(LOG_ERR, "yt-dlp: json_object_object_get_ex failed\n");
json_object_put(jobj);
return -1;
}
strcpy(param->profile, json_object_get_string(jprofile));

// free json object
json_object_put(jobj);

return 0;
}

static void
yt_dlp_response(struct evhttp_request *req, char *result)
{
struct evbuffer *resp = evbuffer_new();
evbuffer_add_printf(resp, "{\"status\": \"%s\"}", result);
evhttp_add_header(evhttp_request_get_output_headers(req), "Content-Type", "application/json");
evhttp_send_reply(req, HTTP_OK, "OK", resp);
}

// define yt-dlp read callback function
static void
yt_dlp_read_cb(struct evhttp_request *req, void *args)
{
#define BUFF_LEN 4096
// read data from bufferevent
char data[BUFF_LEN] = {0};
struct evbuffer *input = evhttp_request_get_input_buffer(req);
size_t len = evbuffer_get_length(input);
assert(len < BUFF_LEN);
if (len >= BUFF_LEN) {
debug(LOG_ERR, "yt-dlp: data length is too long\n");
yt_dlp_response(req, "data length is too long");
return;
}
debug(LOG_DEBUG, "yt-dlp: data: %s\n", data);

// parse http post and get its json data
evbuffer_copyout(input, data, len);
debug(LOG_DEBUG, "yt-dlp: data: %s\n", data);

struct yt_dlp_param *param = (struct yt_dlp_param *)malloc(sizeof(struct yt_dlp_param));
assert(param != NULL);
memset(param, 0, sizeof(struct yt_dlp_param));

int nret = parse_yt_dlp_command (data, param);
if (nret != 0) {
debug(LOG_ERR, "yt-dlp: parse_command failed\n");
free(param);
yt_dlp_response(req, "failed to parse command");
return;
}

// create a thread
pthread_t thread;
// create a thread attribute
pthread_attr_t attr;
// initialize thread attribute
pthread_attr_init(&attr);
// set thread attribute to detach
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// create a thread
pthread_create(&thread, &attr, yt_dlp_worker, param);
// destroy thread attribute
pthread_attr_destroy(&attr);

yt_dlp_response(req, "ok");
}

// define yt-dlp http post callback function
static void
http_post_cb(struct evhttp_request *req, void *arg)
{
// check http request method
if (evhttp_request_get_command(req) != EVHTTP_REQ_POST) {
debug(LOG_ERR, "yt-dlp: http request method is not POST\n");
evhttp_send_error(req, HTTP_BADMETHOD, "Method Not Allowed");
return;
}

// Check the HTTP request content type
const char *content_type = evhttp_find_header(evhttp_request_get_input_headers(req), "Content-Type");
if (content_type == NULL || strcmp(content_type, "application/json") != 0) {
debug(LOG_ERR, "yt-dlp: http request content type is not application/json\n");
evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
return;
}

// get json data from http request
yt_dlp_read_cb(req, arg);

}

static int
install_yt_dlp()
{
// if yt-dlp exists, return
if (access("/usr/local/bin/yt-dlp", F_OK) == 0) {
debug(LOG_DEBUG, "yt-dlp: yt-dlp exists\n");
return 0;
}

// install yt-dlp to /usr/local/bin
// download yt-dlp through curl or wget if any of them exists
char cmd[512] = {0};
if (access("/usr/bin/curl", F_OK) == 0) {
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");
} else if (access("/usr/bin/wget", F_OK) == 0) {
snprintf(cmd, sizeof(cmd), "sudo wget https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -O /usr/local/bin/yt-dlp");
} else {
debug(LOG_ERR, "yt-dlp: curl and wget are not installed\n");
return -1;
}
debug(LOG_DEBUG, "yt-dlp: cmd: %s\n", cmd);
int nret = system(cmd);
if (nret != 0) {
debug(LOG_ERR, "yt-dlp: system failed\n");
return -1;
}
// change yt-dlp to executable
snprintf(cmd, sizeof(cmd), "sudo chmod a+rx /usr/local/bin/yt-dlp");
debug(LOG_DEBUG, "yt-dlp: cmd: %s\n", cmd);
nret = system(cmd);
if (nret != 0) {
debug(LOG_ERR, "yt-dlp: system failed\n");
return -1;
}

return 0;
}

// define yt-dlp service
static void *
yt_dlp_service(void *local_port)
{
// install yt-dlp
int nret = install_yt_dlp();
if (nret != 0) {
debug(LOG_ERR, "yt-dlp: install_yt_dlp failed\n");
return NULL;
}

uint16_t port = *(uint16_t *)local_port;
free(local_port);
// Initialize libevent
struct event_base *base = event_base_new();
if (!base) {
debug(LOG_ERR, "yt-dlp: Failed to initialize libevent\n");
return NULL;
}

// Create a new HTTP server
struct evhttp *http = evhttp_new(base);
if (!http) {
debug(LOG_ERR, "yt-dlp: Failed to create HTTP server\n");
return NULL;
}


if (evhttp_bind_socket(http, "0.0.0.0", port) != 0) {
debug(LOG_ERR, "yt-dlp: Failed to bind HTTP server to port %d\n", port);
return NULL;
}

debug(LOG_DEBUG, "yt-dlp: start youtube download service on port %d\n", port);

// Set up a callback function for handling HTTP requests
evhttp_set_cb(http, "/", http_post_cb, NULL);

// Start the event loop
event_base_dispatch(base);

// Clean up
evhttp_free(http);
event_base_free(base);
return NULL;
}

int
start_youtubedl_service(uint16_t local_port)
{
uint16_t *p = (uint16_t *)malloc(sizeof(uint16_t));
assert(p != NULL);
*p = local_port;
// create a thread
pthread_t thread;
// create a thread attribute
pthread_attr_t attr;
// initialize thread attribute
pthread_attr_init(&attr);
// set thread attribute to detach
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// create a thread
pthread_create(&thread, &attr, yt_dlp_service, (void *)p);
// destroy thread attribute
pthread_attr_destroy(&attr);

return 0;
}
6 changes: 6 additions & 0 deletions plugins/youtubedl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef _YOUTUBE_DL_H_
#define _YOUTUBE_DL_H_

int start_youtubedl_service(uint16_t local_port);

#endif
3 changes: 3 additions & 0 deletions xfrpc.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ static void start_xfrpc_local_service()
} else if (strcmp(ps->plugin, "instaloader") == 0) {
// start instaloader service
start_instaloader_service(ps->local_port);
} else if (strcmp(ps->plugin, "youtubedl") == 0) {
// start youtubedl service
start_youtubedl_service(ps->local_port);
} else if (strcmp(ps->plugin, "instaloader_redir") == 0) {
start_tcp_redir_service(ps);
} else if (strcmp(ps->plugin, "httpd") == 0) {
Expand Down

0 comments on commit b283733

Please sign in to comment.