완성(준)

This commit is contained in:
2025-02-01 23:50:49 +09:00
parent 7e43900f22
commit 0cb7ea8225
20 changed files with 432 additions and 128 deletions

1
Dockerfile Normal file
View File

@@ -0,0 +1 @@
FROM alpine

View File

@@ -8,13 +8,19 @@
namespace bumbleBee { namespace bumbleBee {
class MusicPlayManager { class MusicPlayManager {
public: public:
MusicPlayManager(std::shared_ptr<dpp::cluster> cluster) { MusicPlayManager(std::shared_ptr<dpp::cluster> cluster, std::vector<dpp::snowflake> GIDs) :
this->cluster = cluster; cluster(cluster), GIDs(GIDs) {
this->queue = std::make_unique<MusicQueue>(); queueMap = std::unordered_map<dpp::snowflake, std::shared_ptr<MusicQueue>>();
queueEmptyMutex = std::unordered_map<dpp::snowflake, std::shared_ptr<std::mutex>>();
cluster->on_voice_ready([this](const dpp::voice_ready_t &event){on_voice_ready(event);}); cluster->on_voice_ready([this](const dpp::voice_ready_t &event){on_voice_ready(event);});
cluster->on_voice_track_marker([this](const dpp::voice_track_marker_t &event){on_voice_track_marker(event);}); cluster->on_voice_track_marker([this](const dpp::voice_track_marker_t &event){on_voice_track_marker(event);});
cluster->on_voice_client_disconnect([this](const dpp::voice_client_disconnect_t& event){on_voice_client_disconnect(event);}); cluster->on_voice_client_disconnect([this](const dpp::voice_client_disconnect_t& event){on_voice_client_disconnect(event);});
for (auto GID : GIDs) {
queueMap[GID] = std::make_shared<MusicQueue>();
queueEmptyMutex[GID] = std::make_shared<std::mutex>();
}
} }
/** /**
@@ -33,18 +39,31 @@ public:
**/ **/
void on_voice_client_disconnect(const dpp::voice_client_disconnect_t& event); void on_voice_client_disconnect(const dpp::voice_client_disconnect_t& event);
void queue_music(const std::shared_ptr<MusicQueueElement> music); void play(dpp::discord_voice_client* client);
void queue_music(const dpp::snowflake guildId, const std::shared_ptr<MusicQueueElement> music);
void clear(const dpp::snowflake guildId);
std::shared_ptr<MusicQueueElement> remove(const dpp::snowflake guildId, dpp::discord_voice_client* client, int index);
int size(const dpp::snowflake guildId);
void setRepeat(const dpp::snowflake guildId, const bool value);
bool getRepeat(const dpp::snowflake guildId);
std::list<MusicQueueElement> getQueue(const dpp::snowflake guildId);
MusicQueueElement getNowPlaying(const dpp::snowflake guildId);
std::condition_variable queuedCondition; std::condition_variable queuedCondition;
private: private:
std::shared_ptr<dpp::cluster> cluster; std::shared_ptr<dpp::cluster> cluster;
std::vector<dpp::snowflake> GIDs;
/// @brief 음악 큐 /// @brief 음악 큐
std::unique_ptr<MusicQueue> queue; std::unordered_map<dpp::snowflake, std::shared_ptr<MusicQueue>> queueMap;
std::mutex queueEmptyMutex; std::unordered_map<dpp::snowflake, std::shared_ptr<std::mutex>> queueEmptyMutex;
void send_audio_to_voice(const MusicQueueElement& music, const dpp::voice_ready_t &event); void send_audio_to_voice(const MusicQueueElement& music, dpp::discord_voice_client* client);
void send_audio_to_voice(const MusicQueueElement& music, const dpp::voice_track_marker_t& event);
}; };
} }
#endif #endif

View File

@@ -41,6 +41,8 @@ public:
/// @brief DPP 기본 클러스터 객체 /// @brief DPP 기본 클러스터 객체
std::shared_ptr<dpp::cluster> cluster; std::shared_ptr<dpp::cluster> cluster;
/// @brief guild id 배열
std::vector<dpp::snowflake> GIDs;
private: private:
/// @brief Command 목록 /// @brief Command 목록
std::unordered_map<std::string, std::shared_ptr<commands::ICommand>> commands; std::unordered_map<std::string, std::shared_ptr<commands::ICommand>> commands;

View File

@@ -62,7 +62,7 @@ protected: \
_DECLARE_BUMBLEBEE_COMMAND(Delete, d, "큐의 해당하는 번호의 노래를 지웁니다") _DECLARE_BUMBLEBEE_COMMAND(Delete, d, "큐의 해당하는 번호의 노래를 지웁니다")
_DECLARE_BUMBLEBEE_COMMAND(Leave, l, "음성 채팅방을 떠납니다") _DECLARE_BUMBLEBEE_COMMAND(Leave, l, "음성 채팅방을 떠납니다")
_DECLARE_BUMBLEBEE_COMMAND(Play, p, "노래 재생") _DECLARE_BUMBLEBEE_COMMAND(Play, p, "노래 재생")
_DECLARE_BUMBLEBEE_COMMAND(Queue, q, "노래 예약 큐를 출력합니다") _DECLARE_BUMBLEBEE_COMMAND(Queue, q, "노래 예약 큐를 확인합니다")
_DECLARE_BUMBLEBEE_COMMAND(Repeat, r, "반복을 켜거나 끕니다") _DECLARE_BUMBLEBEE_COMMAND(Repeat, r, "반복을 켜거나 끕니다")
_DECLARE_BUMBLEBEE_COMMAND(Skip, s, "현재 재생중인 곡을 스킵합니다") _DECLARE_BUMBLEBEE_COMMAND(Skip, s, "현재 재생중인 곡을 스킵합니다")
_DECLARE_BUMBLEBEE_COMMAND(Shuffle, shuffle, "큐를 섞습니다") _DECLARE_BUMBLEBEE_COMMAND(Shuffle, shuffle, "큐를 섞습니다")

View File

@@ -15,20 +15,25 @@ public:
MusicQueue() { MusicQueue() {
queue = std::list<std::shared_ptr<MusicQueueElement>>(); queue = std::list<std::shared_ptr<MusicQueueElement>>();
currentPlayingPosition = queue.begin(); currentPlayingPosition = queue.begin();
repeat = false; repeat = true;
} }
void enqueue(std::shared_ptr<MusicQueueElement> Element); void enqueue(std::shared_ptr<MusicQueueElement> Element);
std::shared_ptr<MusicQueueElement> dequeue(); std::shared_ptr<MusicQueueElement> dequeue();
std::list<std::shared_ptr<MusicQueueElement>>::iterator findById(std::string id); std::list<std::shared_ptr<MusicQueueElement>>::iterator findById(std::string id);
std::list<std::shared_ptr<MusicQueueElement>>::iterator findByIndex(int index);
std::shared_ptr<MusicQueueElement> nowplaying(); std::shared_ptr<MusicQueueElement> nowplaying();
std::list<std::shared_ptr<MusicQueueElement>>::iterator next_music(); std::list<std::shared_ptr<MusicQueueElement>>::iterator next_music();
std::shared_ptr<MusicQueueElement> jump_to_index(int idx); std::shared_ptr<MusicQueueElement> jump_to_index(int idx);
void clear();
std::shared_ptr<MusicQueueElement> erase(std::list<std::shared_ptr<MusicQueueElement>>::iterator it);
std::list<std::shared_ptr<MusicQueueElement>> getQueueCopy();
int size();
bool repeat; bool repeat;
std::list<std::shared_ptr<MusicQueueElement>>::iterator currentPlayingPosition; std::list<std::shared_ptr<MusicQueueElement>>::iterator currentPlayingPosition;
std::list<std::shared_ptr<MusicQueueElement>> queue;
private: private:
std::list<std::shared_ptr<MusicQueueElement>> queue;
std::mutex queueMutex; std::mutex queueMutex;
}; };
} }

View File

@@ -10,12 +10,14 @@ public:
MusicQueueElement( MusicQueueElement(
std::string id, std::string id,
std::string query, std::string query,
dpp::user issuingUser) : dpp::user issuingUser,
id(id), query(query), issuingUser(issuingUser) {} dpp::embed embed) :
id(id), query(query), issuingUser(issuingUser), embed(embed) {}
const std::string id; const std::string id;
const std::string query; const std::string query;
const dpp::user issuingUser; const dpp::user issuingUser;
const dpp::embed embed;
}; };
} }
#endif #endif

View File

@@ -12,7 +12,7 @@ public:\
namespace bumbleBee { namespace bumbleBee {
/// @brief 모든 설정은 이 객체를 통해 스태틱하게 제공됨. /// @brief 모든 설정은 이 객체를 통해 스태틱하게 제공됨.
class settingsManager { class SettingsManager {
/// @brief 봇 토큰 /// @brief 봇 토큰
_DECLARE_DEFAULT_ACCESSER_STATIC_VARIABLE(std::string, TOKEN, TOKEN) _DECLARE_DEFAULT_ACCESSER_STATIC_VARIABLE(std::string, TOKEN, TOKEN)
/// @brief yt-dlp 실행 명령어 /// @brief yt-dlp 실행 명령어
@@ -22,7 +22,7 @@ class settingsManager {
/// @brief 로그레벨 /// @brief 로그레벨
_DECLARE_DEFAULT_ACCESSER_STATIC_VARIABLE(dpp::loglevel, LOGLEVEL, LOGLEVEL) _DECLARE_DEFAULT_ACCESSER_STATIC_VARIABLE(dpp::loglevel, LOGLEVEL, LOGLEVEL)
/// @brief 이전에 있던 명령을 지우고 등록할지 선택합니다. /// @brief 이전에 있던 명령을 지우고 등록할지 선택합니다.
_DECLARE_DEFAULT_ACCESSER_STATIC_VARIABLE(bool, CLCOMMAND, CLCOMMAND) _DECLARE_DEFAULT_ACCESSER_STATIC_VARIABLE(bool, REGISTER_COMMAND, REGISTER_COMMAND)
public: public:
/// @brief 설정 로드하기, 설정은 이 load()를 부르기 전까지는 적절하지 못한 값을 가지고 있음. /// @brief 설정 로드하기, 설정은 이 load()를 부르기 전까지는 적절하지 못한 값을 가지고 있음.
/// @return 로드 성공 시 true, 실패 시 false 반환. /// @return 로드 성공 시 true, 실패 시 false 반환.

View File

@@ -19,7 +19,7 @@ public:
static void validateYTDLPFFMPEGBinary(std::shared_ptr<dpp::cluster> cluster) { static void validateYTDLPFFMPEGBinary(std::shared_ptr<dpp::cluster> cluster) {
cluster->log(dpp::ll_info, "Checking if yt-dlp and ffmpeg is available..."); cluster->log(dpp::ll_info, "Checking if yt-dlp and ffmpeg is available...");
std::queue<std::string> result = ConsoleUtils::getResultFromCommand(settingsManager::getFFMPEG_CMD() + " -version"); std::queue<std::string> result = ConsoleUtils::getResultFromCommand(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' ||
@@ -40,9 +40,9 @@ public:
system("tar -xf ffmpeg-master-latest-linux64-gpl.tar.xz"); system("tar -xf ffmpeg-master-latest-linux64-gpl.tar.xz");
system("rm ffmpeg-master-latest-linux64-gpl.tar.xz"); system("rm ffmpeg-master-latest-linux64-gpl.tar.xz");
system("mv ffmpeg-master-latest-linux64-gpl ffmpeg"); system("mv ffmpeg-master-latest-linux64-gpl ffmpeg");
settingsManager::setFFMPEG_CMD("./ffmpeg/bin/ffmpeg"); SettingsManager::setFFMPEG_CMD("./ffmpeg/bin/ffmpeg");
} }
result = ConsoleUtils::getResultFromCommand(settingsManager::getYTDLP_CMD() + " --version"); result = ConsoleUtils::getResultFromCommand(SettingsManager::getYTDLP_CMD() + " --version");
front = result.front(); 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) ||
@@ -59,7 +59,7 @@ public:
system("curl -LO https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp"); system("curl -LO https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp");
system("chmod +x ./yt-dlp"); system("chmod +x ./yt-dlp");
settingsManager::setYTDLP_CMD("./yt-dlp"); SettingsManager::setYTDLP_CMD("./yt-dlp");
} }
} }

View File

@@ -1,23 +1,97 @@
#include <Audio/MusicPlayManager.hpp> #include <Audio/MusicPlayManager.hpp>
#include <ogg/ogg.h> #include <ogg/ogg.h>
#include <oggz/oggz.h> #include <oggz/oggz.h>
#include <algorithm>
namespace bumbleBee { namespace bumbleBee {
void MusicPlayManager::on_voice_ready(const dpp::voice_ready_t& event) { void MusicPlayManager::on_voice_ready(const dpp::voice_ready_t& event) {
std::unique_lock<std::mutex> queueEmptyLock(queueEmptyMutex); play(event.voice_client);
queuedCondition.wait(queueEmptyLock, [&]{ }
return queue->queue.size() != 0;
});
auto np = queue->nowplaying(); void MusicPlayManager::on_voice_track_marker(const dpp::voice_track_marker_t& event) {
auto music = *np; play(event.voice_client);
}
void MusicPlayManager::on_voice_client_disconnect(const dpp::voice_client_disconnect_t& event) { // 안 불리는 듯?
dpp::snowflake gid = dpp::find_channel(event.voice_client->channel_id)->guild_id;
event.voice_client->stop_audio();
queueMap[gid]->clear();
}
void MusicPlayManager::play(dpp::discord_voice_client* client) {
std::thread t([&](dpp::discord_voice_client* client){
dpp::snowflake gid = dpp::find_channel(client->channel_id)->guild_id;
std::unique_lock<std::mutex> queueEmptyLock(*queueEmptyMutex[gid]);
queuedCondition.wait(queueEmptyLock, [&]{ return queueMap[gid]->size() != 0; });
auto np = queueMap[gid]->next_music();
auto music = **np;
send_audio_to_voice(music, client);
}, client);
t.detach();
}
void MusicPlayManager::queue_music(const dpp::snowflake guildId, const std::shared_ptr<MusicQueueElement> music) {
queueMap[guildId]->enqueue(music);
}
void MusicPlayManager::clear(const dpp::snowflake guildId) {
queueMap[guildId]->clear();
}
std::shared_ptr<MusicQueueElement> MusicPlayManager::remove(const dpp::snowflake guildId, dpp::discord_voice_client* client, int index) {
auto queue = queueMap[guildId];
auto foundIterator = queue->findByIndex(index);
if (queue->currentPlayingPosition == foundIterator) {
auto removed = queue->erase(queue->findByIndex(0));
if (client == nullptr)
return removed;
client->pause_audio(true);
client->stop_audio();
client->pause_audio(false);
client->insert_marker("end");
return removed;
}
return queue->erase(queue->findByIndex(index));
}
int MusicPlayManager::size(const dpp::snowflake guildId) {
return queueMap[guildId]->size();
}
void MusicPlayManager::setRepeat(const dpp::snowflake guildId, const bool value) {
queueMap[guildId]->repeat = value;
}
bool MusicPlayManager::getRepeat(const dpp::snowflake guildId) {
return queueMap[guildId]->repeat;
}
std::list<MusicQueueElement> MusicPlayManager::getQueue(const dpp::snowflake guildId){
std::list<std::shared_ptr<MusicQueueElement>> queue = queueMap[guildId]->getQueueCopy();
std::list<MusicQueueElement> returnValue;
for (auto iter = queue.begin(); iter != queue.end(); iter++)
returnValue.push_back(**iter);
return returnValue;
}
MusicQueueElement MusicPlayManager::getNowPlaying(const dpp::snowflake guildId) {
std::shared_ptr<MusicQueueElement> nowplaying = queueMap[guildId]->nowplaying();
MusicQueueElement returnValue(*nowplaying);
return returnValue;
}
void MusicPlayManager::send_audio_to_voice(const MusicQueueElement& music, dpp::discord_voice_client* client) {
std::string command = "./streamOpus.sh ./yt-dlp ffmpeg https://youtu.be/"; std::string command = "./streamOpus.sh ./yt-dlp ffmpeg https://youtu.be/";
command += music.id; command += music.id;
OGGZ* og = oggz_open_stdio(popen(command.c_str(), "r"), OGGZ_READ); OGGZ* og = oggz_open_stdio(popen(command.c_str(), "r"), OGGZ_READ);
client->stop_audio();
oggz_set_read_callback( oggz_set_read_callback(
og, -1, og, -1,
[](OGGZ *oggz, oggz_packet *packet, long serialno, void *user_data) { [](OGGZ *oggz, oggz_packet *packet, long serialno, void *user_data) {
@@ -27,10 +101,10 @@ void MusicPlayManager::on_voice_ready(const dpp::voice_ready_t& event) {
return 0; return 0;
}, },
(void *)event.voice_client (void *)client
); );
while (event.voice_client && !event.voice_client->terminating) { while (client && !client->terminating) {
static const constexpr long CHUNK_READ = BUFSIZ * 2; static const constexpr long CHUNK_READ = BUFSIZ * 2;
const long read_bytes = oggz_read(og, CHUNK_READ); const long read_bytes = oggz_read(og, CHUNK_READ);
@@ -41,28 +115,11 @@ void MusicPlayManager::on_voice_ready(const dpp::voice_ready_t& event) {
} }
} }
//event.from->creator->log(dpp::ll_info, std::string("Sending ") + music.id + " complete!"); client->creator->log(dpp::ll_info, "Sending " + music.id + " complete!");
oggz_close(og); oggz_close(og);
event.voice_client->insert_marker("end"); client->insert_marker();
queueEmptyLock.unlock();
}
void MusicPlayManager::on_voice_track_marker(const dpp::voice_track_marker_t& event) {
queue->next_music();
}
void MusicPlayManager::on_voice_client_disconnect(const dpp::voice_client_disconnect_t& event) {
event.voice_client->stop_audio();
}
void MusicPlayManager::queue_music(const std::shared_ptr<MusicQueueElement> music) {
queue->enqueue(music);
}
void MusicPlayManager::send_audio_to_voice(const MusicQueueElement& music, const dpp::voice_ready_t &event) {
} }
} }

View File

@@ -7,15 +7,15 @@
namespace bumbleBee{ namespace bumbleBee{
BumbleBee::BumbleBee() { BumbleBee::BumbleBee() {
if (!settingsManager::load()) { if (!SettingsManager::load()) {
std::cout << "Please configure confing.json" << std::endl; std::cout << "Please configure confing.json" << std::endl;
exit(1); exit(1);
} }
cluster = std::make_shared<dpp::cluster>(settingsManager::getTOKEN()); cluster = std::make_shared<dpp::cluster>(SettingsManager::getTOKEN());
cluster->on_log([](const dpp::log_t& event) { cluster->on_log([](const dpp::log_t& event) {
if (event.severity >= settingsManager::getLOGLEVEL()) { if (event.severity >= SettingsManager::getLOGLEVEL()) {
std::cout << "[" << dpp::utility::current_date_time() << "] " << dpp::utility::loglevel(event.severity) << ": " << event.message << "\n"; std::cout << "[" << dpp::utility::current_date_time() << "] " << dpp::utility::loglevel(event.severity) << ": " << event.message << "\n";
} }
}); });
@@ -24,22 +24,6 @@ BumbleBee::BumbleBee() {
VersionsCheckUtils::validateYTDLPFFMPEGBinary(cluster); VersionsCheckUtils::validateYTDLPFFMPEGBinary(cluster);
VersionsCheckUtils::updateytdlp(cluster); VersionsCheckUtils::updateytdlp(cluster);
musicManager = std::make_shared<MusicPlayManager>(cluster);
commands["d"] = std::make_shared<commands::Delete> (cluster->cluster_id, musicManager);
commands["l"] = std::make_shared<commands::Leave> (cluster->cluster_id, musicManager);
commands["p"] = std::make_shared<commands::Play> (cluster->cluster_id, musicManager);
commands["q"] = std::make_shared<commands::Queue> (cluster->cluster_id, musicManager);
commands["r"] = std::make_shared<commands::Repeat> (cluster->cluster_id, musicManager);
commands["s"] = std::make_shared<commands::Skip> (cluster->cluster_id, musicManager);
for (auto command : commands) {
for (auto aliase : command.second->aliases) {
commands[aliase] = std::shared_ptr<commands::ICommand>(command.second);
commands[aliase]->set_name(aliase);
}
}
} }
void BumbleBee::start() { this->cluster->start(dpp::st_wait); } void BumbleBee::start() { this->cluster->start(dpp::st_wait); }
@@ -56,7 +40,16 @@ void BumbleBee::on_slashcommand(const dpp::slashcommand_t &event) {
} }
void BumbleBee::on_ready(const dpp::ready_t &event) { void BumbleBee::on_ready(const dpp::ready_t &event) {
cluster->log(dpp::loglevel::ll_info, "Bot ready."); GIDs = event.guilds;
musicManager = std::make_shared<MusicPlayManager>(cluster, GIDs);
commands["d"] = std::make_shared<commands::Delete> (cluster->cluster_id, musicManager);
commands["l"] = std::make_shared<commands::Leave> (cluster->cluster_id, musicManager);
commands["p"] = std::make_shared<commands::Play> (cluster->cluster_id, musicManager);
commands["q"] = std::make_shared<commands::Queue> (cluster->cluster_id, musicManager);
commands["r"] = std::make_shared<commands::Repeat> (cluster->cluster_id, musicManager);
commands["s"] = std::make_shared<commands::Skip> (cluster->cluster_id, musicManager);
commands["shuffle"] = std::make_shared<commands::Shuffle> (cluster->cluster_id, musicManager);
if (event.guilds.size() == 0) { if (event.guilds.size() == 0) {
cluster->log(dpp::loglevel::ll_info, "Bot is not on any server! Please invite this bot to any server."); cluster->log(dpp::loglevel::ll_info, "Bot is not on any server! Please invite this bot to any server.");
@@ -64,18 +57,24 @@ void BumbleBee::on_ready(const dpp::ready_t &event) {
} }
if (dpp::run_once<struct register_bot_commands>()) { if (dpp::run_once<struct register_bot_commands>()) {
if (settingsManager::getCLCOMMAND()) { if (SettingsManager::getREGISTER_COMMAND()) {
cluster->log(dpp::loglevel::ll_info, "Clear Pre-installed commands"); cluster->log(dpp::loglevel::ll_info, "Clear Pre-installed commands");
cluster->global_bulk_command_delete([](const dpp::confirmation_callback_t &t){
std::cout << "cleared Pre-installed commands. Please restart Bot" << std::endl; cluster->global_bulk_command_create({
settingsManager::setCLCOMMAND(false); *commands["d"],
exit(0); *commands["l"],
*commands["p"],
*commands["q"],
*commands["r"],
*commands["s"],
*commands["shuffle"]
}, [&](const dpp::confirmation_callback_t &t){
cluster->log(dpp::loglevel::ll_info, "Command created.");
SettingsManager::setREGISTER_COMMAND(false);
}); });
return;
} }
for (auto command : commands)
cluster->global_command_create(*command.second);
} }
cluster->log(dpp::loglevel::ll_info, "Command created.");
cluster->log(dpp::loglevel::ll_info, "Bot ready.");
} }
} }

View File

@@ -1,11 +1,39 @@
#include <Commands/BumbleBeeCommand.hpp> #include <Commands/BumbleBeeCommand.hpp>
#include <format>
namespace bumbleBee::commands { namespace bumbleBee::commands {
void Delete::execute(const dpp::slashcommand_t &event) { void Delete::execute(const dpp::slashcommand_t &event) {
event.edit_original_response(dpp::message("delete")); if (std::holds_alternative<std::monostate>(event.get_parameter("pos"))) // 여기 들어올 일 있나?
{
event.edit_original_response(dpp::message("위치를 제공하여 주십시오"));
event.reply("");
return;
}
int pos = std::get<std::int64_t>(event.get_parameter("pos"));
if (pos < 0 || pos > musicManager->size(event.command.guild_id))
{
event.edit_original_response(dpp::message(std::string("이상한 인덱스 위치. Pos :") + std::to_string(pos)));
return;
}
dpp::voiceconn* v = event.from->get_voice(event.command.guild_id);
std::shared_ptr<MusicQueueElement> removed;
if (!v) // v-> 로 nullptr을 참조하면 안 되므로.
removed = musicManager->remove(event.command.guild_id, nullptr, pos);
else
removed = musicManager->remove(event.command.guild_id, v->voiceclient, pos);
dpp::message msg("다음 항목을 큐에서 삭제했습니다!:");
msg.add_embed(removed->embed);
event.edit_original_response(msg);
} }
void Delete::init() { void Delete::init() {
aliases.push_back("delete"); add_option(dpp::command_option(dpp::co_integer, "pos", "지울 위치(위치는 1부터 시작, 현재 재생곡은 0)", true));
} }
} }

View File

@@ -1,8 +1,17 @@
#include <Commands/BumbleBeeCommand.hpp> #include <Commands/BumbleBeeCommand.hpp>
namespace bumbleBee::commands { namespace bumbleBee::commands {
void Leave::execute(const dpp::slashcommand_t &event) { void Leave::execute(const dpp::slashcommand_t &event) { // 왜 read loop ended가 뜨는가...
event.edit_original_response(dpp::message("leave")); dpp::voiceconn* v = event.from->get_voice(event.command.guild_id);
if (!v || !v->voiceclient || !v->voiceclient->is_ready()) {
return;
}
musicManager->clear(event.command.guild_id);
event.from->disconnect_voice(event.command.guild_id);
event.edit_original_response(dpp::message("음성 채팅방을 떠납니다!"));
} }
void Leave::init() { void Leave::init() {

View File

@@ -1,5 +1,7 @@
#include <Commands/BumbleBeeCommand.hpp> #include <Commands/BumbleBeeCommand.hpp>
#include <Utils/ConsoleUtils.hpp> #include <Utils/ConsoleUtils.hpp>
#include <Settings/SettingsManager.hpp>
#include <dpp/nlohmann/json.hpp>
#include <variant> #include <variant>
namespace bumbleBee::commands { namespace bumbleBee::commands {
@@ -10,7 +12,7 @@ namespace bumbleBee::commands {
event.edit_original_response(dpp::message("GUILD NOT FOUND!! WHAT IS THIS SORCERY??")); event.edit_original_response(dpp::message("GUILD NOT FOUND!! WHAT IS THIS SORCERY??"));
return; return;
} }
if (std::holds_alternative<std::monostate>(event.get_parameter("query"))) if (std::holds_alternative<std::monostate>(event.get_parameter("query"))) // 이거 필요한가???
{ {
event.reply("노래를 재생하려면 검색어 또는 링크를 입력해 주십시오."); event.reply("노래를 재생하려면 검색어 또는 링크를 입력해 주십시오.");
return; return;
@@ -21,11 +23,18 @@ namespace bumbleBee::commands {
} }
std::string query = std::get<std::string>(event.get_parameter("query")); std::string query = std::get<std::string>(event.get_parameter("query"));
std::queue<std::string> ids = ConsoleUtils::getResultFromCommand("./yt-dlp --default-search ytsearch --flat-playlist --skip-download --quiet --ignore-errors --print id " + query); std::queue<std::string> ids = ConsoleUtils::getResultFromCommand(SettingsManager::getYTDLP_CMD() + " --default-search ytsearch --flat-playlist --skip-download --quiet --ignore-errors --print id " + query);
std::queue<MusicQueueElement> musics; std::queue<std::shared_ptr<MusicQueueElement>> musics;
dpp::message origMsg; int initialQueuedCount = musicManager->size(event.command.guild_id);
dpp::message msg;
if (musicManager->size(event.command.guild_id) == 0)
msg.content = "다음 곡을 재생합니다:";
else
msg.content = "큐에 다음 곡을 추가했습니다:";
if (ids.size() >= 2) { if (ids.size() >= 2) {
event.from->creator->log(dpp::ll_info, "Playlist detected."); event.from->creator->log(dpp::ll_info, "Playlist detected.");
@@ -34,34 +43,122 @@ namespace bumbleBee::commands {
ids.pop(); ids.pop();
continue; continue;
} }
event.from->creator->log(dpp::ll_info, "Enqueuing " + ids.front());
MusicQueueElement music( 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");
ids.front(),
query, std::ostringstream oss;
event.command.usr char buffer[1024];
); size_t bytesRead;
while ((bytesRead = fread(buffer, 1, sizeof(buffer), file)) > 0) {
oss.write(buffer, bytesRead);
}
fclose(file);
musics.push(music); std::istringstream iss(oss.str());
nlohmann::json videoDataJson;
iss >> videoDataJson;
// std::string dump = videoDataJson.dump(4);
time_t SongLength = int(videoDataJson["duration"]);
char SongLengthStr[10];
tm t;
t.tm_mday = SongLength / 86400;
t.tm_hour = (SongLength % 86400)/3600;
t.tm_min = (SongLength % 3600)/60;
t.tm_sec = SongLength%60;
strftime(SongLengthStr, sizeof(SongLengthStr), "%X", &t);
dpp::embed embed = dpp::embed()
.set_color(dpp::colors::sti_blue)
.set_title(std::string(videoDataJson["title"]))
.set_description(std::string(videoDataJson["uploader"]))
.set_url(std::string(videoDataJson["webpage_url"]))
.set_image(std::string(videoDataJson["thumbnail"]))
.add_field(
"길이",
SongLengthStr,
true
);
musics.push(std::make_shared<MusicQueueElement>(ids.front(), query, event.command.usr, embed));
ids.pop(); ids.pop();
} }
} }
MusicQueueElement music( if (!ids.empty()) {
ids.front(), 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");
query,
event.command.usr std::ostringstream oss;
); char buffer[1024];
musics.push(music); size_t bytesRead;
while ((bytesRead = fread(buffer, 1, sizeof(buffer), file)) > 0) {
oss.write(buffer, bytesRead);
}
fclose(file);
while (!musics.empty()) { std::istringstream iss(oss.str());
auto element = musics.front(); nlohmann::json videoDataJson;
musicManager->queue_music(std::make_shared<MusicQueueElement>(element)); iss >> videoDataJson;
musics.pop();
time_t SongLength = int(videoDataJson["duration"]);
char SongLengthStr[10];
tm t;
t.tm_mday = SongLength / 86400;
t.tm_hour = (SongLength % 86400)/3600;
t.tm_min = (SongLength % 3600)/60;
t.tm_sec = SongLength%60;
strftime(SongLengthStr, sizeof(SongLengthStr), "%X", &t);
dpp::embed embed = dpp::embed()
.set_color(dpp::colors::sti_blue)
.set_title(std::string(videoDataJson["title"]))
.set_description(std::string(videoDataJson["uploader"]))
.set_url(std::string(videoDataJson["webpage_url"]))
.set_image(std::string(videoDataJson["thumbnail"]))
.add_field(
"길이",
SongLengthStr,
true
);
musics.push(std::make_shared<MusicQueueElement>(ids.front(), query, event.command.usr, embed));
} }
event.edit_original_response(dpp::message("play")); if (musics.size() == 1) {
musicManager->queuedCondition.notify_one(); event.from->creator->log(dpp::ll_info, "Enqueuing " + musics.front()->id);
musicManager->queue_music(event.command.guild_id, musics.front());
msg.add_embed(musics.front()->embed);
musics.pop();
event.edit_original_response(msg);
musicManager->queuedCondition.notify_all();
}
else if (musics.size() > 1) {
event.from->creator->log(dpp::ll_info, "Enqueuing " + musics.front()->id);
musicManager->queue_music(event.command.guild_id, musics.front());
msg.add_embed(musics.front()->embed);
musics.pop();
event.edit_original_response(msg);
musicManager->queuedCondition.notify_all();
while (!musics.empty()) {
event.from->creator->log(dpp::ll_info, "Enqueuing " + musics.front()->id);
dpp::message followMsg(event.command.channel_id, "");
followMsg.add_embed(musics.front()->embed);
event.from->creator->message_create(followMsg); // 어차피 원래 메시지를 지정해서 수정할 것이기 때문에 먼저 팔로잉 메시지를 작성해도 상관없음.
musicManager->queue_music(event.command.guild_id, musics.front());
musics.pop();
}
}
else { // ??
event.from->creator->log(dpp::ll_error, "??? not queueed any music");
}
} }
void Play::init() { void Play::init() {

View File

@@ -2,7 +2,31 @@
namespace bumbleBee::commands { namespace bumbleBee::commands {
void Queue::execute(const dpp::slashcommand_t &event) { void Queue::execute(const dpp::slashcommand_t &event) {
event.edit_original_response(dpp::message("queue")); auto queue = musicManager->getQueue(event.command.guild_id);
auto nowplaying = musicManager->getNowPlaying(event.command.guild_id);
dpp::message msg;
dpp::embed embed;
if (queue.size() == 0) {
embed
.set_title("큐가 비었습니다!")
.set_timestamp(time(0));
msg.add_embed(embed);
event.edit_original_response(msg);
return;
}
msg.content = "지금 재생 중:";
msg.add_embed(nowplaying.embed);
for (auto iter = queue.begin(); iter != queue.end(); iter++) {
dpp::message followMsg(event.command.channel_id, "");
followMsg.add_embed(iter->embed);
event.from->creator->message_create(followMsg); // 어차피 원래 메시지를 지정해서 수정할 것이기 때문에 먼저 팔로잉 메시지를 작성해도 상관없음.
}
event.edit_original_response(msg);
} }
void Queue::init() { void Queue::init() {

View File

@@ -2,9 +2,15 @@
namespace bumbleBee::commands { namespace bumbleBee::commands {
void Repeat::execute(const dpp::slashcommand_t &event) { void Repeat::execute(const dpp::slashcommand_t &event) {
event.edit_original_response(dpp::message("repeat")); if (musicManager->getRepeat(event.command.guild_id)) {
event.edit_original_response(dpp::message("반복을 껐습니다."));
musicManager->setRepeat(event.command.guild_id, false);
}
else {
event.edit_original_response(dpp::message("반복을 켰습니다."));
musicManager->setRepeat(event.command.guild_id, true);
}
} }
void Repeat::init() { void Repeat::init() {}
}
} }

View File

@@ -2,7 +2,17 @@
namespace bumbleBee::commands { namespace bumbleBee::commands {
void Skip::execute(const dpp::slashcommand_t &event) { void Skip::execute(const dpp::slashcommand_t &event) {
event.edit_original_response(dpp::message("skip")); dpp::voiceconn* v = event.from->get_voice(event.command.guild_id);
if (!v || !v->voiceclient || !v->voiceclient->is_ready()) {
return;
}
v->voiceclient->pause_audio(true);
v->voiceclient->stop_audio();
v->voiceclient->pause_audio(false);
v->voiceclient->insert_marker("end");
event.edit_original_response(dpp::message("스킵했습니다!"));
} }
void Skip::init() { void Skip::init() {

View File

@@ -1,5 +1,6 @@
#include <Queue/MusicQueue.hpp> #include <Queue/MusicQueue.hpp>
#include <iostream> #include <iostream>
#include <algorithm>
namespace bumbleBee { namespace bumbleBee {
@@ -25,14 +26,23 @@ std::list<std::shared_ptr<MusicQueueElement>>::iterator MusicQueue::findById(std
} }
return queue.end(); return queue.end();
} }
std::list<std::shared_ptr<MusicQueueElement>>::iterator MusicQueue::findByIndex(int index) {
std::lock_guard<std::mutex> lock(queueMutex);
if (index < 0)
return queue.end();
if (index == 0)
return currentPlayingPosition;
return std::next(queue.begin(), index - 1);
}
std::shared_ptr<MusicQueueElement> MusicQueue::nowplaying() { std::shared_ptr<MusicQueueElement> MusicQueue::nowplaying() {
std::lock_guard<std::mutex> lock(queueMutex);
return *currentPlayingPosition; return *currentPlayingPosition;
} }
std::list<std::shared_ptr<MusicQueueElement>>::iterator MusicQueue::next_music() { std::list<std::shared_ptr<MusicQueueElement>>::iterator MusicQueue::next_music() {
std::lock_guard<std::mutex> lock(queueMutex); std::lock_guard<std::mutex> lock(queueMutex);
if (currentPlayingPosition == std::prev(queue.end()) || !repeat) if (currentPlayingPosition == --queue.end() && !repeat)
return queue.end(); return queue.end();
if (currentPlayingPosition == std::prev(queue.end()) || repeat) if (currentPlayingPosition == --queue.end() && repeat)
currentPlayingPosition = queue.begin(); currentPlayingPosition = queue.begin();
else else
++currentPlayingPosition; ++currentPlayingPosition;
@@ -49,4 +59,39 @@ std::shared_ptr<MusicQueueElement> MusicQueue::jump_to_index(int idx) {
} }
return std::shared_ptr<MusicQueueElement>(); return std::shared_ptr<MusicQueueElement>();
} }
void MusicQueue::clear() {
std::lock_guard<std::mutex> lock(queueMutex);
queue.clear();
currentPlayingPosition = queue.begin();
}
std::shared_ptr<MusicQueueElement> MusicQueue::erase(std::list<std::shared_ptr<MusicQueueElement>>::iterator it) {
std::lock_guard<std::mutex> lock(queueMutex);
if (it == queue.end())
return nullptr;
if (currentPlayingPosition == it) {
auto removedValue = *it;
auto after = queue.erase(currentPlayingPosition++);
if (after == queue.end())
currentPlayingPosition = --queue.end();
return removedValue;
}
else {
auto removedValue = *it;
queue.erase(it);
return removedValue;
}
}
std::list<std::shared_ptr<MusicQueueElement>> MusicQueue::getQueueCopy(){
std::lock_guard<std::mutex> lock(queueMutex);
std::list<std::shared_ptr<MusicQueueElement>> returnValue;
std::copy(queue.begin(), queue.end(), std::back_inserter(returnValue));
return returnValue;
}
int MusicQueue::size() {
std::lock_guard<std::mutex> lock(queueMutex);
return queue.size();
}
} }

View File

@@ -6,13 +6,13 @@
namespace bumbleBee { namespace bumbleBee {
std::string settingsManager::TOKEN = ""; std::string SettingsManager::TOKEN = "";
std::string settingsManager::YTDLP_CMD = "./yt-dlp"; std::string SettingsManager::YTDLP_CMD = "./yt-dlp";
std::string settingsManager::FFMPEG_CMD = "./ffmpeg/bin/ffmpeg"; std::string SettingsManager::FFMPEG_CMD = "./ffmpeg/bin/ffmpeg";
dpp::loglevel settingsManager::LOGLEVEL = dpp::ll_debug; dpp::loglevel SettingsManager::LOGLEVEL = dpp::ll_debug;
bool settingsManager::CLCOMMAND = false; bool SettingsManager::REGISTER_COMMAND = false;
bool settingsManager::validateToken() { bool SettingsManager::validateToken() {
nlohmann::json response; nlohmann::json response;
if (ConsoleUtils::getResultFromCommand("which curl").size() == 0) { if (ConsoleUtils::getResultFromCommand("which curl").size() == 0) {
std::cout << "curl is unavaliable. unresolable error please install curl." << std::endl; std::cout << "curl is unavaliable. unresolable error please install curl." << std::endl;
@@ -32,7 +32,7 @@ bool settingsManager::validateToken() {
return true; return true;
} }
bool settingsManager::load() { bool SettingsManager::load() {
nlohmann::json configdocument; nlohmann::json configdocument;
try { try {
std::ifstream configfile("config.json"); std::ifstream configfile("config.json");
@@ -66,7 +66,7 @@ bool settingsManager::load() {
else // 값이 병신같을때 기본값으로 ll_info 부여 else // 값이 병신같을때 기본값으로 ll_info 부여
LOGLEVEL = dpp::ll_info; LOGLEVEL = dpp::ll_info;
CLCOMMAND = configdocument["CLEAR_PREVIOUS_COMMAND"]; REGISTER_COMMAND = configdocument["CLEAR_PREVIOUS_COMMAND"];
} }
catch (const nlohmann::json::type_error& e) { catch (const nlohmann::json::type_error& e) {
saveToFile(); saveToFile();
@@ -79,7 +79,7 @@ bool settingsManager::load() {
return true; return true;
} }
void settingsManager::saveToFile() { void SettingsManager::saveToFile() {
nlohmann::json configdocument; nlohmann::json configdocument;
configdocument["TOKEN"] = TOKEN; configdocument["TOKEN"] = TOKEN;
@@ -107,7 +107,7 @@ void settingsManager::saveToFile() {
break; break;
} }
configdocument["CLEAR_PREVIOUS_COMMAND"] = CLCOMMAND; configdocument["CLEAR_PREVIOUS_COMMAND"] = REGISTER_COMMAND;
std::ofstream configFile("config.json"); std::ofstream configFile("config.json");
if (configFile.is_open()) { if (configFile.is_open()) {

View File

@@ -38,7 +38,7 @@ 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::getResultFromCommand(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);
if (ids.size() >= 2) { if (ids.size() >= 2) {
@@ -56,7 +56,7 @@ void AsyncDownloadManager::downloadWorker() {
} }
std::queue<std::string> urls = std::queue<std::string> urls =
ConsoleUtils::getResultFromCommand(settingsManager::getYTDLP_CMD() + ConsoleUtils::getResultFromCommand(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,7 +75,7 @@ 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()); std::string command = std::string("./streamOpus.sh " + SettingsManager::getYTDLP_CMD() + " " + downloadID + " " + SettingsManager::getFFMPEG_CMD());
stream = popen(command.c_str(), "r"); stream = popen(command.c_str(), "r");
cluster->log(dpp::ll_info, "Thread id: " + tid.str() + " Opened stream: " + downloadID); cluster->log(dpp::ll_info, "Thread id: " + tid.str() + " Opened stream: " + downloadID);

View File

@@ -4,9 +4,9 @@ YTDLP_CMD=$1
FFMPEG_CMD=$2 FFMPEG_CMD=$2
URL=$3 URL=$3
$YTDLP_CMD -o - --quiet --ignore-errors -f bestaudio $URL | \ $YTDLP_CMD -o - --quiet --ignore-errors -f bestaudio $URL 2>/dev/null | \
$FFMPEG_CMD -hwaccel vaapi -i - -hide_banner -c:a copy -f opus - || \ $FFMPEG_CMD -hwaccel vaapi -i - -loglevel quiet -hide_banner -c:a copy -f opus - || \
$YTDLP_CMD -o - --quiet --ignore-errors -f bestaudio $URL | \ $YTDLP_CMD -o - --quiet --ignore-errors -f bestaudio $URL 2>/dev/null | \
$FFMPEG_CMD -hwaccel vaapi -i - -hide_banner -f opus -c:a libopus -b:a 128k -ar 48000 -ac 2 - $FFMPEG_CMD -hwaccel vaapi -i - -loglevel quiet -hide_banner -f opus -c:a libopus -b:a 128k -ar 48000 -ac 2 -
# -loglevel quiet # -loglevel quiet