mirror of
https://github.com/HappyTanuki/BumbleCee.git
synced 2025-10-25 17:35:58 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c59fef1d0b | |||
| 125fbcc49c | |||
| 4915a5c3b8 | |||
| 4435337f40 | |||
| 77d16c1cdb | |||
| 5760f1afdc |
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -102,6 +102,8 @@
|
||||
"hash_set": "cpp",
|
||||
"regex": "cpp",
|
||||
"stack": "cpp",
|
||||
"__memory": "cpp"
|
||||
"__memory": "cpp",
|
||||
"__verbose_abort": "cpp",
|
||||
"print": "cpp"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
cmake_minimum_required (VERSION 3.6)
|
||||
|
||||
INCLUDE_DIRECTORIES(include . ${Boost_INCLUDE_DIR})
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
|
||||
set(BOT_NAME "BumbleCee")
|
||||
|
||||
@@ -18,8 +20,11 @@ set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
||||
add_definitions( -DBOOST_ALL_NO_LIB )
|
||||
set( Boost_USE_STATIC_LIBS ON )
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
find_package(Boost REQUIRED)
|
||||
|
||||
target_include_directories(${BOT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
@@ -35,6 +40,7 @@ target_link_libraries(${BOT_NAME}
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${OPENSSL_CRYPTO_LIBRARY}
|
||||
${OPENSSL_SSL_LIBRARY}
|
||||
${Boost_LIBRARIES}
|
||||
)
|
||||
|
||||
link_directories(/usr/lib)
|
||||
@@ -7,8 +7,10 @@ RUN apt-get update && \
|
||||
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 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-legacy.deb
|
||||
RUN rm dpp.deb
|
||||
RUN curl -LO https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp
|
||||
RUN chmod +x ./yt-dlp
|
||||
@@ -16,4 +18,4 @@ COPY ./build/BumbleCee /BumbleCee
|
||||
COPY ./streamOpus.sh /streamOpus.sh
|
||||
RUN chmod +x BumbleCee
|
||||
RUN chmod +x streamOpus.sh
|
||||
ENTRYPOINT ["/usr/bin/tini", "--", "./BumbleCee"]
|
||||
ENTRYPOINT ["/usr/bin/tini", "--", "./BumbleCee"]
|
||||
|
||||
18
include/Audio/MusicPlayerThreadManager.hpp
Normal file
18
include/Audio/MusicPlayerThreadManager.hpp
Normal 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;
|
||||
};
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <queue>
|
||||
#include <regex>
|
||||
#include <boost/process.hpp>
|
||||
|
||||
namespace bumbleBee {
|
||||
class ConsoleUtils {
|
||||
@@ -9,28 +11,36 @@ public:
|
||||
/**
|
||||
* @brief 명령어를 쉘에서 실행하고 결과를 EOF 전까지 읽어 \n을 구분자로 토큰화하여 반환합니다
|
||||
* @param cmd 실행할 명령
|
||||
* @param args 아규먼트
|
||||
* @return std::queue<std::string> tokens
|
||||
*/
|
||||
static std::queue<std::string> getResultFromCommand(std::string cmd) {
|
||||
std::string result, token;
|
||||
static std::queue<std::string> safe_execute_command(const std::string& cmd, const std::vector<std::string>& args) {
|
||||
std::queue<std::string> tokens;
|
||||
FILE* stream;
|
||||
const int maxBuffer = 12; // 적당한 크기
|
||||
char buffer[maxBuffer];
|
||||
cmd.append(" 2>&1"); // 표준에러를 표준출력으로 redirect
|
||||
try {
|
||||
boost::process::ipstream output; // 명령의 출력을 받을 스트림
|
||||
boost::process::child c(cmd, boost::process::args(args), boost::process::std_out > output);
|
||||
|
||||
stream = popen(cmd.c_str(), "r"); // 주어진 command를 shell로 실행하고 파이프 연결 (fd 반환)
|
||||
if (stream) {
|
||||
while (fgets(buffer, maxBuffer, stream) != NULL) result.append(buffer); // fgets: fd (stream)를 길이 (maxBuffer)만큼 읽어 버퍼 (buffer)에 저장
|
||||
pclose(stream);
|
||||
}
|
||||
std::string line;
|
||||
while (!output.eof() && std::getline(output, line))
|
||||
tokens.push(line);
|
||||
|
||||
std::stringstream ss(result);
|
||||
while (std::getline(ss, token, '\n')) {
|
||||
tokens.push(token);
|
||||
c.wait(); // 프로세스가 종료될 때까지 대기
|
||||
return tokens;
|
||||
} 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");
|
||||
}
|
||||
};
|
||||
}
|
||||
25
include/Utils/ThreadPool.hpp
Normal file
25
include/Utils/ThreadPool.hpp
Normal 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_;
|
||||
};
|
||||
}
|
||||
@@ -7,7 +7,7 @@ namespace bumbleBee {
|
||||
class VersionsCheckUtils {
|
||||
public:
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
@@ -16,7 +16,7 @@ public:
|
||||
}
|
||||
|
||||
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();
|
||||
if (front[0] != 'f' ||
|
||||
front[1] != 'f' ||
|
||||
@@ -43,7 +43,7 @@ public:
|
||||
}
|
||||
|
||||
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();
|
||||
if ((front[0]-'0' < 0 || front[0]-'0' > 9) ||
|
||||
(front[1]-'0' < 0 || front[1]-'0' > 9) ||
|
||||
@@ -72,7 +72,7 @@ public:
|
||||
|
||||
static void updateytdlp(std::shared_ptr<dpp::cluster> cluster) {
|
||||
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()) {
|
||||
std::string front = result.front();
|
||||
result.pop();
|
||||
|
||||
@@ -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) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
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 ";
|
||||
command += SettingsManager::getYTDLP_CMD() + " ";
|
||||
command += SettingsManager::getFFMPEG_CMD() + " ";
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
#include <Settings/SettingsManager.hpp>
|
||||
#include <dpp/nlohmann/json.hpp>
|
||||
#include <Utils/QueuedMusicListEmbedProvider.hpp>
|
||||
#include <Utils/ConsoleUtils.hpp>
|
||||
#include <variant>
|
||||
|
||||
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);
|
||||
|
||||
if (!g) { //wtf?
|
||||
@@ -23,12 +24,20 @@ namespace bumbleBee::commands {
|
||||
return;
|
||||
}
|
||||
std::string query = std::get<std::string>(event.get_parameter("query"));
|
||||
query = "\"" + query + "\"";
|
||||
// query = "\"" + query + "\"";
|
||||
|
||||
std::queue<std::string> ids =
|
||||
ConsoleUtils::getResultFromCommand(
|
||||
SettingsManager::getYTDLP_CMD() +
|
||||
" --default-search ytsearch --flat-playlist --skip-download --quiet --ignore-errors --print id " + query);
|
||||
ConsoleUtils::safe_execute_command(
|
||||
SettingsManager::getYTDLP_CMD(), {
|
||||
"--default-search",
|
||||
"ytsearch",
|
||||
"--flat-playlist",
|
||||
"--skip-download",
|
||||
"--quiet",
|
||||
"--ignore-errors",
|
||||
"--print",
|
||||
"id",
|
||||
query});
|
||||
|
||||
std::queue<std::shared_ptr<MusicQueueElement>> musics;
|
||||
|
||||
@@ -41,24 +50,23 @@ namespace bumbleBee::commands {
|
||||
else
|
||||
msg.content = "큐에 다음 곡을 추가했습니다:";
|
||||
|
||||
if (!ids.empty()) {
|
||||
if (!ids.empty()) { // TODO : 이거 멀티스레드로 바꿔서 더 빨리 정보를 받아올 수 있도록 개선할 것것
|
||||
if (ids.front() == "") {
|
||||
ids.pop();
|
||||
}
|
||||
|
||||
FILE* file = popen((SettingsManager::getYTDLP_CMD() +
|
||||
" --default-search ytsearch --flat-playlist --skip-download --quiet --ignore-errors -J http://youtu.be/" + ids.front()).c_str(), "r");
|
||||
|
||||
std::ostringstream oss;
|
||||
char buffer[1024];
|
||||
size_t bytesRead;
|
||||
|
||||
while ((bytesRead = fread(buffer, 1, sizeof(buffer), file)) > 0) {
|
||||
oss.write(buffer, bytesRead);
|
||||
}
|
||||
pclose(file);
|
||||
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()
|
||||
}).front();
|
||||
|
||||
std::istringstream iss(oss.str());
|
||||
std::istringstream iss(jsonData);
|
||||
nlohmann::json videoDataJson;
|
||||
iss >> videoDataJson;
|
||||
|
||||
@@ -108,19 +116,18 @@ namespace bumbleBee::commands {
|
||||
continue;
|
||||
}
|
||||
|
||||
FILE* file = popen((SettingsManager::getYTDLP_CMD() +
|
||||
" --default-search ytsearch --flat-playlist --skip-download --quiet --ignore-errors -J http://youtu.be/" + ids.front()).c_str(), "r");
|
||||
|
||||
std::ostringstream oss;
|
||||
char buffer[1024];
|
||||
size_t bytesRead;
|
||||
|
||||
while ((bytesRead = fread(buffer, 1, sizeof(buffer), file)) > 0) {
|
||||
oss.write(buffer, bytesRead);
|
||||
}
|
||||
pclose(file);
|
||||
|
||||
std::istringstream iss(oss.str());
|
||||
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()
|
||||
}).front();
|
||||
|
||||
std::istringstream iss(jsonData);
|
||||
nlohmann::json videoDataJson;
|
||||
iss >> videoDataJson;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace bumbleBee::commands {
|
||||
void Shuffle::execute(const dpp::slashcommand_t &event) {
|
||||
event.edit_original_response(dpp::message("shuffle"));
|
||||
// TODO : 구현
|
||||
}
|
||||
|
||||
void Shuffle::init() {
|
||||
|
||||
0
src/Manager/ThreadManager.cpp
Normal file
0
src/Manager/ThreadManager.cpp
Normal file
@@ -14,13 +14,19 @@ bool SettingsManager::REGISTER_COMMAND = false;
|
||||
|
||||
bool SettingsManager::validateToken() {
|
||||
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;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string stresult = ConsoleUtils::getResultFromCommand("curl -sX GET \"https://discord.com/api/v10/users/@me\" -H \"Authorization: Bot " +
|
||||
TOKEN + "\"").front();
|
||||
std::string stresult = ConsoleUtils::safe_execute_command(curl, {
|
||||
"-sX",
|
||||
"GET",
|
||||
"https://discord.com/api/v10/users/@me",
|
||||
"-H",
|
||||
"Authorization: Bot " + TOKEN + ""
|
||||
}).front();
|
||||
std::stringstream ss(stresult);
|
||||
ss >> response;
|
||||
|
||||
|
||||
@@ -38,8 +38,17 @@ void AsyncDownloadManager::downloadWorker() {
|
||||
cluster->log(dpp::ll_info, "AsyncDownloadManager: " + query + " accepted.");
|
||||
|
||||
std::queue<std::string> ids =
|
||||
ConsoleUtils::getResultFromCommand(SettingsManager::getYTDLP_CMD() +
|
||||
" --default-search ytsearch --flat-playlist --skip-download --quiet --ignore-errors --print id " + query);
|
||||
ConsoleUtils::safe_execute_command(
|
||||
SettingsManager::getYTDLP_CMD(), {
|
||||
"--default-search",
|
||||
"ytsearch",
|
||||
"--flat-playlist",
|
||||
"--skip-download",
|
||||
"--quiet",
|
||||
"--ignore-errors",
|
||||
"--print",
|
||||
"id",
|
||||
query});
|
||||
|
||||
if (ids.size() >= 2) {
|
||||
cluster->log(dpp::ll_info, query + " is playlist");
|
||||
@@ -56,8 +65,13 @@ void AsyncDownloadManager::downloadWorker() {
|
||||
}
|
||||
|
||||
std::queue<std::string> urls =
|
||||
ConsoleUtils::getResultFromCommand(SettingsManager::getYTDLP_CMD() +
|
||||
" -f ba* --print urls https://youtu.be/" + ids.front());
|
||||
ConsoleUtils::safe_execute_command(SettingsManager::getYTDLP_CMD(), {
|
||||
"-f",
|
||||
"ba*",
|
||||
"--print",
|
||||
"urls",
|
||||
"https://youtu.be/" + ids.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.");
|
||||
|
||||
std::string command = std::string("./streamOpus.sh " + SettingsManager::getYTDLP_CMD() + " " + downloadID + " " + SettingsManager::getFFMPEG_CMD());
|
||||
stream = popen(command.c_str(), "r");
|
||||
stream = ConsoleUtils::safe_open_pipe("./streamOpus.sh", {
|
||||
SettingsManager::getYTDLP_CMD(),
|
||||
downloadID,
|
||||
SettingsManager::getFFMPEG_CMD()
|
||||
});
|
||||
|
||||
cluster->log(dpp::ll_info, "Thread id: " + tid.str() + " Opened stream: " + downloadID);
|
||||
});
|
||||
|
||||
28
src/Utils/ThreadPool.cpp
Normal file
28
src/Utils/ThreadPool.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@
|
||||
#include <BumbleBee.hpp>
|
||||
#include <thread>
|
||||
|
||||
int main() {
|
||||
int main(int argc, char* argv[]) {
|
||||
bumbleBee::BumbleBee bot;
|
||||
bot.start();
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user