|
|
|
@ -2,6 +2,7 @@
@@ -2,6 +2,7 @@
|
|
|
|
|
#include "cpr/parameters.h" |
|
|
|
|
#include "tgbot/net/BoostHttpOnlySslClient.h" |
|
|
|
|
#include "tgbot/net/HttpParser.h" |
|
|
|
|
#include <bits/c++config.h> |
|
|
|
|
#include <bits/stdint-intn.h> |
|
|
|
|
#include <bits/stdint-uintn.h> |
|
|
|
|
#include <csignal> |
|
|
|
@ -15,6 +16,7 @@
@@ -15,6 +16,7 @@
|
|
|
|
|
#include <memory> |
|
|
|
|
|
|
|
|
|
#include "Base64.hpp" |
|
|
|
|
#include <tuple> |
|
|
|
|
#include <sys/types.h> |
|
|
|
|
#include <tgbot/tgbot.h> |
|
|
|
|
#include <cpr/cpr.h> |
|
|
|
@ -22,6 +24,7 @@
@@ -22,6 +24,7 @@
|
|
|
|
|
#include <sqlite3.h> |
|
|
|
|
#include <cstdlib> |
|
|
|
|
#include <map> |
|
|
|
|
#include <set> |
|
|
|
|
#include <spdlog/spdlog.h> |
|
|
|
|
|
|
|
|
|
using namespace TgBot; |
|
|
|
@ -181,11 +184,19 @@ class spotify {
@@ -181,11 +184,19 @@ class spotify {
|
|
|
|
|
class songdb { |
|
|
|
|
/* need one table for every chat: keep it all in memory? */ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::string filepath; |
|
|
|
|
sqlite3 *db; |
|
|
|
|
|
|
|
|
|
public: |
|
|
|
|
|
|
|
|
|
struct runtime_vals { |
|
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
runtime_vals runtime_data; |
|
|
|
|
|
|
|
|
|
protected: |
|
|
|
|
|
|
|
|
|
enum error_codes { |
|
|
|
|
NOT_FOUND, |
|
|
|
|
ALREADY_ADDED |
|
|
|
@ -373,7 +384,6 @@ class songdb {
@@ -373,7 +384,6 @@ class songdb {
|
|
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::optional<track_entry> get_song(int64_t id) { |
|
|
|
|
std::string check_exist = "SELECT * FROM tracks WHERE id = ?;"; |
|
|
|
|
|
|
|
|
@ -554,6 +564,136 @@ class songdb {
@@ -554,6 +564,136 @@ class songdb {
|
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct vote { |
|
|
|
|
int song; |
|
|
|
|
int list; |
|
|
|
|
int64_t user; |
|
|
|
|
double value; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
std::vector<vote>
|
|
|
|
|
get_votes_list(int64_t song_list)
|
|
|
|
|
{ |
|
|
|
|
std::string query = "SELECT * FROM votes WHERE list = ?"; |
|
|
|
|
|
|
|
|
|
sqlite3_stmt *statement; |
|
|
|
|
int err; |
|
|
|
|
err = sqlite3_prepare_v2(db, query.c_str(), query.length(), &statement, NULL); |
|
|
|
|
check_error(err); |
|
|
|
|
err = sqlite3_bind_int64(statement, 1, song_list); |
|
|
|
|
check_error(err); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<vote> votes; |
|
|
|
|
|
|
|
|
|
err = sqlite3_step(statement); |
|
|
|
|
while (err == SQLITE_ROW) { |
|
|
|
|
int song = sqlite3_column_int(statement, 0); |
|
|
|
|
int list = sqlite3_column_int(statement, 1); |
|
|
|
|
int user = sqlite3_column_int(statement, 2); |
|
|
|
|
int value= sqlite3_column_int(statement, 3); |
|
|
|
|
|
|
|
|
|
votes.push_back({song, list,user,value}); |
|
|
|
|
err = sqlite3_step(statement); |
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (err != SQLITE_DONE) { |
|
|
|
|
check_error(err); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
sqlite3_finalize(statement); |
|
|
|
|
return votes; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
struct base_weight_vector { |
|
|
|
|
std::vector<int64_t> person_order; |
|
|
|
|
std::vector<size_t> song_order; |
|
|
|
|
std::vector<std::vector<double>> weights;
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
//std::map<int, std::map<int64_t, double>>
|
|
|
|
|
base_weight_vector |
|
|
|
|
get_base_weights (int64_t song_list)
|
|
|
|
|
{ |
|
|
|
|
std::vector<vote> list = get_votes_list(song_list); |
|
|
|
|
std::set<int64_t> chat_members; |
|
|
|
|
|
|
|
|
|
// {song, {user, vote}}
|
|
|
|
|
std::map<int, std::map<int64_t, double>> vote_info {}; |
|
|
|
|
|
|
|
|
|
for (auto v : list) { |
|
|
|
|
chat_members.insert(v.user); |
|
|
|
|
|
|
|
|
|
vote_info[v.song] = {{v.user, v.value}}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for (auto v : vote_info) { |
|
|
|
|
// Insert zero values for members who have not voted
|
|
|
|
|
for (auto m : chat_members) { |
|
|
|
|
if (!v.second.count(m)) { |
|
|
|
|
v.second.insert({m, 0.0}); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// normalise weightings
|
|
|
|
|
double total = 0; |
|
|
|
|
for (auto m : v.second) { |
|
|
|
|
total += m.second; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for (auto m : v.second) { |
|
|
|
|
v.second[m.first] = m.second / total; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (vote_info.size() == 0) { |
|
|
|
|
return {}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* turn it into a nice easy to use vector
|
|
|
|
|
* Relying on the fact maps are sorted and things will always be in the |
|
|
|
|
* same order. |
|
|
|
|
*/ |
|
|
|
|
base_weight_vector v; |
|
|
|
|
|
|
|
|
|
for (auto song : vote_info) { |
|
|
|
|
v.song_order.push_back(song.first); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
auto a = vote_info.begin(); |
|
|
|
|
for (auto user : a->second) { |
|
|
|
|
v.person_order.push_back(user.first); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for (auto song: vote_info) { |
|
|
|
|
std::vector<double> user_votes; |
|
|
|
|
for (auto user : song.second) { |
|
|
|
|
user_votes.push_back(user.second); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
assert(user_votes.size() == v.person_order.size()); |
|
|
|
|
|
|
|
|
|
v.weights.push_back(user_votes); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return v; |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
std::vector<track_entry> |
|
|
|
|
generate_track_list(int64_t song_list) |
|
|
|
|
{ |
|
|
|
|
auto base_weights = get_base_weights(song_list); |
|
|
|
|
|
|
|
|
|
for (int i = 0; i < base_weights.song_order.size(); i++) { |
|
|
|
|
spdlog::info("song {} nppl {}", base_weights.song_order[i], base_weights.weights.size()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
std::vector<track_entry> retlist; |
|
|
|
|
return retlist; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
songdb (std::string filepath): filepath(filepath) { |
|
|
|
|
int err = sqlite3_open(filepath.c_str(), &db); |
|
|
|
|
if (err) { |
|
|
|
@ -568,12 +708,6 @@ class songdb {
@@ -568,12 +708,6 @@ class songdb {
|
|
|
|
|
sqlite3_close(db); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool add_song(std::string spotify_track_id) { |
|
|
|
|
// request spotify api for info
|
|
|
|
|
// yeet into db
|
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
}; |
|
|
|
|
}; |
|
|
|
@ -620,9 +754,16 @@ int main() {
@@ -620,9 +754,16 @@ int main() {
|
|
|
|
|
|
|
|
|
|
Bot bot(teletoken); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
InlineKeyboardMarkup::Ptr keyboard(new InlineKeyboardMarkup); |
|
|
|
|
std::vector<InlineKeyboardButton::Ptr> row0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
InlineKeyboardButton::Ptr button5(new InlineKeyboardButton); |
|
|
|
|
button5->text = "0"; |
|
|
|
|
button5->callbackData= "0"; |
|
|
|
|
row0.push_back(button5); |
|
|
|
|
|
|
|
|
|
InlineKeyboardButton::Ptr button1(new InlineKeyboardButton); |
|
|
|
|
button1->text = "1"; |
|
|
|
|
button1->callbackData = "1"; |
|
|
|
@ -643,18 +784,12 @@ int main() {
@@ -643,18 +784,12 @@ int main() {
|
|
|
|
|
button4->callbackData = "4"; |
|
|
|
|
row0.push_back(button4); |
|
|
|
|
|
|
|
|
|
InlineKeyboardButton::Ptr button5(new InlineKeyboardButton); |
|
|
|
|
button5->text = "5"; |
|
|
|
|
button5->callbackData= "5"; |
|
|
|
|
row0.push_back(button5); |
|
|
|
|
|
|
|
|
|
keyboard->inlineKeyboard.push_back(row0); |
|
|
|
|
|
|
|
|
|
bot.getEvents().onCallbackQuery([&bot, &keyboard, &data](CallbackQuery::Ptr query) { |
|
|
|
|
|
|
|
|
|
spdlog::info("Query: {}, mesg {}", query->data, query->message->text); |
|
|
|
|
|
|
|
|
|
if ((query->data == "1") || (query->data == "2") || (query->data == "3") || (query->data == "4") || (query->data == "5")) { |
|
|
|
|
if ((query->data == "1") || (query->data == "2") || (query->data == "3") || (query->data == "4") || (query->data == "0")) { |
|
|
|
|
|
|
|
|
|
std::istringstream is {query->data}; |
|
|
|
|
int value; |
|
|
|
@ -679,7 +814,6 @@ int main() {
@@ -679,7 +814,6 @@ int main() {
|
|
|
|
|
spdlog::error ("bad song id"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
spdlog::info("Adding vote for {}: {}",song->name, value); |
|
|
|
|
|
|
|
|
|
data.insert_vote(query->from->id, query->message->chat->id, value, songid); |
|
|
|
|
|
|
|
|
@ -689,32 +823,49 @@ int main() {
@@ -689,32 +823,49 @@ int main() {
|
|
|
|
|
|
|
|
|
|
bot.getEvents().onCommand("add", [&bot, &keyboard, &data, s](Message::Ptr message) { |
|
|
|
|
|
|
|
|
|
std::string link = util::trim_whitespace(message->text.substr(message->text.find("add"))); |
|
|
|
|
auto resp = s->track_id_from_link(link); |
|
|
|
|
if (!resp) { |
|
|
|
|
bot.getApi().sendMessage(message->chat->id, "Sorry, I don't understand that link."); |
|
|
|
|
return; |
|
|
|
|
std::string title; |
|
|
|
|
std::string artist; |
|
|
|
|
int songid; |
|
|
|
|
|
|
|
|
|
if (message->text.find("spotify.com") != std::string::npos) { |
|
|
|
|
std::string link = util::trim_whitespace(message->text.substr(message->text.find("add") + 3)); |
|
|
|
|
auto resp = s->track_id_from_link(link); |
|
|
|
|
if (!resp) { |
|
|
|
|
bot.getApi().sendMessage(message->chat->id, "Sorry, I don't understand that link."); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
auto spot_resp = s->get_track(*resp); |
|
|
|
|
|
|
|
|
|
if (!spot_resp) { |
|
|
|
|
bot.getApi().sendMessage(message->chat->id, "Sorry, I cannot find that track in spotify."); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
json track_data = *spot_resp; |
|
|
|
|
|
|
|
|
|
title = track_data["name"]; |
|
|
|
|
artist = track_data["artists"][0]["name"]; |
|
|
|
|
auto song = data.insert_song(title, artist, *resp); |
|
|
|
|
songid = song->id; |
|
|
|
|
|
|
|
|
|
} else { |
|
|
|
|
title = util::trim_whitespace(message->text.substr(message->text.find("add") + 3)); |
|
|
|
|
artist = ""; |
|
|
|
|
auto song = data.insert_song(title, artist, {}); |
|
|
|
|
songid = song->id; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
auto spot_resp = s->get_track(*resp); |
|
|
|
|
std::string response = "Added song: " + title; |
|
|
|
|
|
|
|
|
|
if (!spot_resp) { |
|
|
|
|
bot.getApi().sendMessage(message->chat->id, "Sorry, I cannot find that track in spotify."); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
if (artist != "") |
|
|
|
|
response += ", by " + artist; |
|
|
|
|
|
|
|
|
|
json track_data = *spot_resp; |
|
|
|
|
|
|
|
|
|
std::cout << spot_resp->dump(4); |
|
|
|
|
std::string title = track_data["name"]; |
|
|
|
|
std::string artist = track_data["artists"][0]["name"]; |
|
|
|
|
|
|
|
|
|
auto song = data.insert_song(title, artist, *resp); |
|
|
|
|
|
|
|
|
|
std::string response = "Added song: " + title + ", by " + artist; |
|
|
|
|
response += "\n\n"; |
|
|
|
|
std::ostringstream os; |
|
|
|
|
os << song->id; |
|
|
|
|
response += "\n\rsongid:" + os.str() + "\n\r\n\r"; |
|
|
|
|
os << songid; |
|
|
|
|
|
|
|
|
|
response += "songid:" + os.str() + "\n\r\n\r"; |
|
|
|
|
response += "Everyone, please rate how well you know this song /5"; |
|
|
|
|
|
|
|
|
|
bot.getApi().sendMessage(message->chat->id, response, false, 0, keyboard, "Markdown"); |
|
|
|
@ -725,15 +876,24 @@ int main() {
@@ -725,15 +876,24 @@ int main() {
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bot.getEvents().onCommand("start", [&bot](Message::Ptr message) { |
|
|
|
|
bot.getEvents().onCommand("start", [&bot, &data](Message::Ptr message) { |
|
|
|
|
|
|
|
|
|
data.generate_track_list(1); |
|
|
|
|
|
|
|
|
|
bot.getApi().sendMessage(message->chat->id, "Hi!"); |
|
|
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
signal(SIGINT, [](int s) { |
|
|
|
|
printf("SIGINT got\n"); |
|
|
|
|
exit(0); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
std::string * a = new std::string("hello world"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
printf("Bot username: %s\n", bot.getApi().getMe()->username.c_str()); |
|
|
|
|