6 Commits

Author SHA1 Message Date
c59fef1d0b 일단 저장 2025-08-28 20:46:37 +09:00
125fbcc49c TODO 추가가 2025-03-09 17:29:09 +09:00
4915a5c3b8 TODO리스트는 늘어만 간다.. 2025-03-09 17:27:42 +09:00
4435337f40 TODO 추가가 2025-03-07 17:25:58 +09:00
77d16c1cdb 아마도? 쉘 인젝션 취약점 픽스 2025-03-07 17:19:58 +09:00
5760f1afdc todo 추가가 2025-02-13 17:42:17 +09:00
16 changed files with 188 additions and 65 deletions

View File

@@ -102,6 +102,8 @@
"hash_set": "cpp", "hash_set": "cpp",
"regex": "cpp", "regex": "cpp",
"stack": "cpp", "stack": "cpp",
"__memory": "cpp" "__memory": "cpp",
"__verbose_abort": "cpp",
"print": "cpp"
} }
} }

View File

@@ -1,5 +1,7 @@
cmake_minimum_required (VERSION 3.6) cmake_minimum_required (VERSION 3.6)
INCLUDE_DIRECTORIES(include . ${Boost_INCLUDE_DIR})
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
set(BOT_NAME "BumbleCee") set(BOT_NAME "BumbleCee")
@@ -18,8 +20,11 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(THREADS_PREFER_PTHREAD_FLAG TRUE) set(THREADS_PREFER_PTHREAD_FLAG TRUE)
add_definitions( -DBOOST_ALL_NO_LIB )
set( Boost_USE_STATIC_LIBS ON )
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
find_package(Boost REQUIRED)
target_include_directories(${BOT_NAME} PUBLIC target_include_directories(${BOT_NAME} PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/include
@@ -35,6 +40,7 @@ target_link_libraries(${BOT_NAME}
${CMAKE_THREAD_LIBS_INIT} ${CMAKE_THREAD_LIBS_INIT}
${OPENSSL_CRYPTO_LIBRARY} ${OPENSSL_CRYPTO_LIBRARY}
${OPENSSL_SSL_LIBRARY} ${OPENSSL_SSL_LIBRARY}
${Boost_LIBRARIES}
) )
link_directories(/usr/lib) link_directories(/usr/lib)

View File

@@ -7,8 +7,10 @@ RUN apt-get update && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
RUN pip3 install --break-system-packages --no-cache-dir curl_cffi RUN pip3 install --break-system-packages --no-cache-dir curl_cffi
RUN pip3 install --break-system-packages --no-cache-dir pycryptodome RUN pip3 install --break-system-packages --no-cache-dir pycryptodome
RUN curl -Lo dpp.deb https://dl.dpp.dev/ RUN curl -Lo dpp.deb https://dl.dpp.dev/latest
RUN curl -Lo dpp-legacy.deb https://github.com/brainboxdotcc/DPP/releases/download/v10.0.35/libdpp-10.0.35-linux-x64.deb
RUN dpkg -i dpp.deb RUN dpkg -i dpp.deb
RUN dpkg -i dpp-legacy.deb
RUN rm dpp.deb RUN rm dpp.deb
RUN curl -LO https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp RUN curl -LO https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp
RUN chmod +x ./yt-dlp RUN chmod +x ./yt-dlp
@@ -16,4 +18,4 @@ COPY ./build/BumbleCee /BumbleCee
COPY ./streamOpus.sh /streamOpus.sh COPY ./streamOpus.sh /streamOpus.sh
RUN chmod +x BumbleCee RUN chmod +x BumbleCee
RUN chmod +x streamOpus.sh RUN chmod +x streamOpus.sh
ENTRYPOINT ["/usr/bin/tini", "--", "./BumbleCee"] ENTRYPOINT ["/usr/bin/tini", "--", "./BumbleCee"]

View File

@@ -0,0 +1,18 @@
#pragma once
#include <memory>
#include <dpp/dpp.h>
#include <Queue/MusicQueueElement.hpp>
#include "Utils/ThreadPool.hpp"
namespace bumbleBee {
class ThreadManager : public ThreadPool<dpp::snowflake, int, int> {
public:
bool addMusic(std::shared_ptr<bumbleBee::MusicQueueElement> music, dpp::discord_voice_client* client);
void stopSending(dpp::snowflake gid);
private:
// GID, 쓰레드
std::unordered_map<dpp::snowflake, std::thread> threadPool;
// GID, 쓰레드 종료
std::unordered_map<dpp::snowflake, bool> terminating;
};
}

View File

@@ -2,6 +2,8 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <queue> #include <queue>
#include <regex>
#include <boost/process.hpp>
namespace bumbleBee { namespace bumbleBee {
class ConsoleUtils { class ConsoleUtils {
@@ -9,28 +11,36 @@ public:
/** /**
* @brief 명령어를 쉘에서 실행하고 결과를 EOF 전까지 읽어 \n을 구분자로 토큰화하여 반환합니다 * @brief 명령어를 쉘에서 실행하고 결과를 EOF 전까지 읽어 \n을 구분자로 토큰화하여 반환합니다
* @param cmd 실행할 명령 * @param cmd 실행할 명령
* @param args 아규먼트
* @return std::queue<std::string> tokens * @return std::queue<std::string> tokens
*/ */
static std::queue<std::string> getResultFromCommand(std::string cmd) { static std::queue<std::string> safe_execute_command(const std::string& cmd, const std::vector<std::string>& args) {
std::string result, token;
std::queue<std::string> tokens; std::queue<std::string> tokens;
FILE* stream; try {
const int maxBuffer = 12; // 적당한 크기 boost::process::ipstream output; // 명령의 출력을 받을 스트림
char buffer[maxBuffer]; boost::process::child c(cmd, boost::process::args(args), boost::process::std_out > output);
cmd.append(" 2>&1"); // 표준에러를 표준출력으로 redirect
stream = popen(cmd.c_str(), "r"); // 주어진 command를 shell로 실행하고 파이프 연결 (fd 반환) std::string line;
if (stream) { while (!output.eof() && std::getline(output, line))
while (fgets(buffer, maxBuffer, stream) != NULL) result.append(buffer); // fgets: fd (stream)를 길이 (maxBuffer)만큼 읽어 버퍼 (buffer)에 저장 tokens.push(line);
pclose(stream);
}
std::stringstream ss(result); c.wait(); // 프로세스가 종료될 때까지 대기
while (std::getline(ss, token, '\n')) { return tokens;
tokens.push(token); } catch (const std::exception& e) {
return tokens;
} }
}
/**
* @brief 명령어를 쉘에서 실행하고 결과를 파이프로 연결하여 반환합니다
* @param cmd 실행할 명령
* @param args 아규먼트
* @return FILE* fd
*/
static FILE* safe_open_pipe(const std::string& cmd, const std::vector<std::string>& args) {
boost::process::pipe pipe;
boost::process::child c(cmd, boost::process::args(args), boost::process::std_out > pipe);
return tokens; return fdopen(pipe.native_source(), "r");
} }
}; };
} }

View File

@@ -0,0 +1,25 @@
#pragma once
#include <memory>
#include <functional>
#include <thread>
#include <condition_variable>
#include <queue>
namespace bumbleBee {
template <typename FuncRet, typename FuncParam>
class ThreadPool {
public:
ThreadPool() = delete;
ThreadPool(std::int32_t threadCount);
std::thread::id execute(std::function<FuncRet(FuncParam)> function);
void gracefullStop(std::thread::id thread);
void gracefullAllStop();
private:
std::mutex mutex_;
std::condition_variable condition_;
std::int32_t threadCount_;
std::vector<std::thread> threadPool_;
std::unordered_map<std::thread, std::bool> terminating_;
};
}

View File

@@ -7,7 +7,7 @@ namespace bumbleBee {
class VersionsCheckUtils { class VersionsCheckUtils {
public: public:
static bool isThereCMD(std::shared_ptr<dpp::cluster> cluster, std::string cmd) { static bool isThereCMD(std::shared_ptr<dpp::cluster> cluster, std::string cmd) {
if (ConsoleUtils::getResultFromCommand("which " + cmd).size() == 0) { if (ConsoleUtils::safe_execute_command("/usr/bin/which", {cmd}).size() == 0) {
cluster->log(dpp::ll_error, cmd + " is unavaliable. unresolable error please install " + cmd); cluster->log(dpp::ll_error, cmd + " is unavaliable. unresolable error please install " + cmd);
return false; return false;
} }
@@ -16,7 +16,7 @@ public:
} }
static void validateFFMPEG(std::shared_ptr<dpp::cluster> cluster) { static void validateFFMPEG(std::shared_ptr<dpp::cluster> cluster) {
std::queue<std::string> result = ConsoleUtils::getResultFromCommand(SettingsManager::getFFMPEG_CMD() + " -version"); std::queue<std::string> result = ConsoleUtils::safe_execute_command(SettingsManager::getFFMPEG_CMD(), {"-version"});
std::string front = result.front(); std::string front = result.front();
if (front[0] != 'f' || if (front[0] != 'f' ||
front[1] != 'f' || front[1] != 'f' ||
@@ -43,7 +43,7 @@ public:
} }
static void validateYTDLP(std::shared_ptr<dpp::cluster> cluster) { static void validateYTDLP(std::shared_ptr<dpp::cluster> cluster) {
std::queue<std::string> result = ConsoleUtils::getResultFromCommand(SettingsManager::getYTDLP_CMD() + " --version"); std::queue<std::string> result = ConsoleUtils::safe_execute_command(SettingsManager::getYTDLP_CMD(), {"--version"});
std::string front = result.front(); std::string front = result.front();
if ((front[0]-'0' < 0 || front[0]-'0' > 9) || if ((front[0]-'0' < 0 || front[0]-'0' > 9) ||
(front[1]-'0' < 0 || front[1]-'0' > 9) || (front[1]-'0' < 0 || front[1]-'0' > 9) ||
@@ -72,7 +72,7 @@ public:
static void updateytdlp(std::shared_ptr<dpp::cluster> cluster) { static void updateytdlp(std::shared_ptr<dpp::cluster> cluster) {
cluster->log(dpp::ll_info, "Checking if yt-dlp update is available..."); cluster->log(dpp::ll_info, "Checking if yt-dlp update is available...");
std::queue<std::string> result = ConsoleUtils::getResultFromCommand("./yt-dlp -U"); std::queue<std::string> result = ConsoleUtils::safe_execute_command("./yt-dlp", {"-U"});
while(!result.empty()) { while(!result.empty()) {
std::string front = result.front(); std::string front = result.front();
result.pop(); result.pop();

View File

@@ -12,7 +12,7 @@ void MusicPlayManager::on_voice_ready(const dpp::voice_ready_t& event) {
void MusicPlayManager::on_voice_track_marker(const dpp::voice_track_marker_t& event) { void MusicPlayManager::on_voice_track_marker(const dpp::voice_track_marker_t& event) {
dpp::snowflake gid = dpp::find_channel(event.voice_client->channel_id)->guild_id; dpp::snowflake gid = dpp::find_channel(event.voice_client->channel_id)->guild_id;
queueMap[gid]->next_music(); queueMap[gid]->next_music(); // TODO("repeat가 꺼져 있을 때 노래 큐에서 지우기")
play(event.voice_client); play(event.voice_client);
} }
@@ -87,7 +87,7 @@ MusicQueueElement MusicPlayManager::getNowPlaying(const dpp::snowflake guildId)
} }
void MusicPlayManager::send_audio_to_voice(std::shared_ptr<bumbleBee::MusicQueueElement> music, dpp::discord_voice_client* client) { void MusicPlayManager::send_audio_to_voice(std::shared_ptr<bumbleBee::MusicQueueElement> music, dpp::discord_voice_client* client) {
std::thread t([](std::shared_ptr<bumbleBee::MusicQueueElement> music, dpp::discord_voice_client* client) { std::thread t([](std::shared_ptr<bumbleBee::MusicQueueElement> music, dpp::discord_voice_client* client) { // TODO: thread 벡터 만들고 delete, leave, skip시에 전송 중지하도록 만들고. 또 노래 캐싱하고 ytdlp를 버퍼링하여 혹시 있을지 모르는 음성의 끊김을 방지할 것것
std::string command = "./streamOpus.sh "; std::string command = "./streamOpus.sh ";
command += SettingsManager::getYTDLP_CMD() + " "; command += SettingsManager::getYTDLP_CMD() + " ";
command += SettingsManager::getFFMPEG_CMD() + " "; command += SettingsManager::getFFMPEG_CMD() + " ";

View File

@@ -3,10 +3,11 @@
#include <Settings/SettingsManager.hpp> #include <Settings/SettingsManager.hpp>
#include <dpp/nlohmann/json.hpp> #include <dpp/nlohmann/json.hpp>
#include <Utils/QueuedMusicListEmbedProvider.hpp> #include <Utils/QueuedMusicListEmbedProvider.hpp>
#include <Utils/ConsoleUtils.hpp>
#include <variant> #include <variant>
namespace bumbleBee::commands { namespace bumbleBee::commands {
void Play::execute(const dpp::slashcommand_t &event) { void Play::execute(const dpp::slashcommand_t &event) { // TODO : 길드 단위로 잠구고 메타데이터 로딩 중엔 로딩 중임을 표시할 수 있는 UI 만들 것것
dpp::guild *g = dpp::find_guild(event.command.guild_id); dpp::guild *g = dpp::find_guild(event.command.guild_id);
if (!g) { //wtf? if (!g) { //wtf?
@@ -23,12 +24,20 @@ namespace bumbleBee::commands {
return; return;
} }
std::string query = std::get<std::string>(event.get_parameter("query")); std::string query = std::get<std::string>(event.get_parameter("query"));
query = "\"" + query + "\""; // query = "\"" + query + "\"";
std::queue<std::string> ids = std::queue<std::string> ids =
ConsoleUtils::getResultFromCommand( ConsoleUtils::safe_execute_command(
SettingsManager::getYTDLP_CMD() + SettingsManager::getYTDLP_CMD(), {
" --default-search ytsearch --flat-playlist --skip-download --quiet --ignore-errors --print id " + query); "--default-search",
"ytsearch",
"--flat-playlist",
"--skip-download",
"--quiet",
"--ignore-errors",
"--print",
"id",
query});
std::queue<std::shared_ptr<MusicQueueElement>> musics; std::queue<std::shared_ptr<MusicQueueElement>> musics;
@@ -41,24 +50,23 @@ namespace bumbleBee::commands {
else else
msg.content = "큐에 다음 곡을 추가했습니다:"; msg.content = "큐에 다음 곡을 추가했습니다:";
if (!ids.empty()) { if (!ids.empty()) { // TODO : 이거 멀티스레드로 바꿔서 더 빨리 정보를 받아올 수 있도록 개선할 것것
if (ids.front() == "") { if (ids.front() == "") {
ids.pop(); ids.pop();
} }
FILE* file = popen((SettingsManager::getYTDLP_CMD() + std::string jsonData = ConsoleUtils::safe_execute_command(SettingsManager::getYTDLP_CMD(), {
" --default-search ytsearch --flat-playlist --skip-download --quiet --ignore-errors -J http://youtu.be/" + ids.front()).c_str(), "r"); "--default-search",
"ytsearch",
std::ostringstream oss; "--flat-playlist",
char buffer[1024]; "--skip-download",
size_t bytesRead; "--quiet",
"--ignore-errors",
while ((bytesRead = fread(buffer, 1, sizeof(buffer), file)) > 0) { "-J",
oss.write(buffer, bytesRead); "http://youtu.be/" + ids.front()
} }).front();
pclose(file);
std::istringstream iss(oss.str()); std::istringstream iss(jsonData);
nlohmann::json videoDataJson; nlohmann::json videoDataJson;
iss >> videoDataJson; iss >> videoDataJson;
@@ -108,19 +116,18 @@ namespace bumbleBee::commands {
continue; continue;
} }
FILE* file = popen((SettingsManager::getYTDLP_CMD() + std::string jsonData = ConsoleUtils::safe_execute_command(SettingsManager::getYTDLP_CMD(), {
" --default-search ytsearch --flat-playlist --skip-download --quiet --ignore-errors -J http://youtu.be/" + ids.front()).c_str(), "r"); "--default-search",
"ytsearch",
std::ostringstream oss; "--flat-playlist",
char buffer[1024]; "--skip-download",
size_t bytesRead; "--quiet",
"--ignore-errors",
while ((bytesRead = fread(buffer, 1, sizeof(buffer), file)) > 0) { "-J",
oss.write(buffer, bytesRead); "http://youtu.be/" + ids.front()
} }).front();
pclose(file);
std::istringstream iss(jsonData);
std::istringstream iss(oss.str());
nlohmann::json videoDataJson; nlohmann::json videoDataJson;
iss >> videoDataJson; iss >> videoDataJson;

View File

@@ -3,6 +3,7 @@
namespace bumbleBee::commands { namespace bumbleBee::commands {
void Shuffle::execute(const dpp::slashcommand_t &event) { void Shuffle::execute(const dpp::slashcommand_t &event) {
event.edit_original_response(dpp::message("shuffle")); event.edit_original_response(dpp::message("shuffle"));
// TODO : 구현
} }
void Shuffle::init() { void Shuffle::init() {

View File

View File

@@ -14,13 +14,19 @@ bool SettingsManager::REGISTER_COMMAND = false;
bool SettingsManager::validateToken() { bool SettingsManager::validateToken() {
nlohmann::json response; nlohmann::json response;
if (ConsoleUtils::getResultFromCommand("which curl").size() == 0) { std::string curl = ConsoleUtils::safe_execute_command("/usr/bin/which", {"curl"}).front();
if (curl == "") {
std::cout << "curl is unavaliable. unresolable error please install curl." << std::endl; std::cout << "curl is unavaliable. unresolable error please install curl." << std::endl;
return false; return false;
} }
std::string stresult = ConsoleUtils::getResultFromCommand("curl -sX GET \"https://discord.com/api/v10/users/@me\" -H \"Authorization: Bot " + std::string stresult = ConsoleUtils::safe_execute_command(curl, {
TOKEN + "\"").front(); "-sX",
"GET",
"https://discord.com/api/v10/users/@me",
"-H",
"Authorization: Bot " + TOKEN + ""
}).front();
std::stringstream ss(stresult); std::stringstream ss(stresult);
ss >> response; ss >> response;

View File

@@ -38,8 +38,17 @@ void AsyncDownloadManager::downloadWorker() {
cluster->log(dpp::ll_info, "AsyncDownloadManager: " + query + " accepted."); cluster->log(dpp::ll_info, "AsyncDownloadManager: " + query + " accepted.");
std::queue<std::string> ids = std::queue<std::string> ids =
ConsoleUtils::getResultFromCommand(SettingsManager::getYTDLP_CMD() + ConsoleUtils::safe_execute_command(
" --default-search ytsearch --flat-playlist --skip-download --quiet --ignore-errors --print id " + query); SettingsManager::getYTDLP_CMD(), {
"--default-search",
"ytsearch",
"--flat-playlist",
"--skip-download",
"--quiet",
"--ignore-errors",
"--print",
"id",
query});
if (ids.size() >= 2) { if (ids.size() >= 2) {
cluster->log(dpp::ll_info, query + " is playlist"); cluster->log(dpp::ll_info, query + " is playlist");
@@ -56,8 +65,13 @@ void AsyncDownloadManager::downloadWorker() {
} }
std::queue<std::string> urls = std::queue<std::string> urls =
ConsoleUtils::getResultFromCommand(SettingsManager::getYTDLP_CMD() + ConsoleUtils::safe_execute_command(SettingsManager::getYTDLP_CMD(), {
" -f ba* --print urls https://youtu.be/" + ids.front()); "-f",
"ba*",
"--print",
"urls",
"https://youtu.be/" + ids.front()
});
cluster->log(dpp::ll_debug, "url: " + urls.front()); cluster->log(dpp::ll_debug, "url: " + urls.front());
@@ -75,8 +89,11 @@ void AsyncDownloadManager::downloadWorker() {
cluster->log(dpp::ll_info, "Thread id: " + tid.str() + ": " + downloadID + " accepted."); cluster->log(dpp::ll_info, "Thread id: " + tid.str() + ": " + downloadID + " accepted.");
std::string command = std::string("./streamOpus.sh " + SettingsManager::getYTDLP_CMD() + " " + downloadID + " " + SettingsManager::getFFMPEG_CMD()); stream = ConsoleUtils::safe_open_pipe("./streamOpus.sh", {
stream = popen(command.c_str(), "r"); SettingsManager::getYTDLP_CMD(),
downloadID,
SettingsManager::getFFMPEG_CMD()
});
cluster->log(dpp::ll_info, "Thread id: " + tid.str() + " Opened stream: " + downloadID); cluster->log(dpp::ll_info, "Thread id: " + tid.str() + " Opened stream: " + downloadID);
}); });

28
src/Utils/ThreadPool.cpp Normal file
View File

@@ -0,0 +1,28 @@
#include "Utils/ThreadPool.hpp"
namespace bumbleBee {
template <typename FuncRet, typename FuncParam>
ThreadPool<FuncRet, FuncParam>::ThreadPool(std::int32_t threadCount) {
this.threadCount = threadCount;
while (threadCount--) {
threadPool.
}
}
template <typename FuncRet, typename FuncParam>
void ThreadPool<FuncRet, FuncParam>::gracefullAllStop() {
std::unique_lock<std::mutex> lock(mutex_);
for (auto& thread : threadPool_) {
terminating_[thread] = true;
condition_.notify_all();
}
for (auto& thread : threadPool_) {
if (thread.joinable()) {
thread.join();
}
}
threadPool_.clear();
terminating_.clear();
}
}

View File

@@ -2,7 +2,8 @@
#include <BumbleBee.hpp> #include <BumbleBee.hpp>
#include <thread> #include <thread>
int main() { int main(int argc, char* argv[]) {
bumbleBee::BumbleBee bot; bumbleBee::BumbleBee bot;
bot.start(); bot.start();
return 0;
} }

BIN
yt-dlp

Binary file not shown.