|
|
|
@ -11,9 +11,11 @@
@@ -11,9 +11,11 @@
|
|
|
|
|
#include <exception> |
|
|
|
|
#include <istream> |
|
|
|
|
#include <sstream> |
|
|
|
|
#include <stdexcept> |
|
|
|
|
#include <string> |
|
|
|
|
#include <nlohmann/json.hpp> |
|
|
|
|
#include <memory> |
|
|
|
|
#include <cmath> |
|
|
|
|
|
|
|
|
|
#include "Base64.hpp" |
|
|
|
|
#include <tuple> |
|
|
|
@ -325,7 +327,7 @@ class songdb {
@@ -325,7 +327,7 @@ class songdb {
|
|
|
|
|
* As is, song lists are unique to telegram groups |
|
|
|
|
*/ |
|
|
|
|
int64_t get_song_list_id(int64_t group_id) { |
|
|
|
|
spdlog::debug("get_song_list_id {}", group_id); |
|
|
|
|
spdlog::debug("{} {}", __PRETTY_FUNCTION__, __LINE__); |
|
|
|
|
|
|
|
|
|
std::string list_query = "SELECT * FROM songlists WHERE groupid = ?;"; |
|
|
|
|
/* A constraint of this implementation is that every group can only have
|
|
|
|
@ -413,6 +415,7 @@ class songdb {
@@ -413,6 +415,7 @@ class songdb {
|
|
|
|
|
sqlite3_finalize(statement); |
|
|
|
|
return e; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
track_entry e {id, name, artist}; |
|
|
|
|
sqlite3_finalize(statement); |
|
|
|
|
return e; |
|
|
|
@ -614,7 +617,7 @@ class songdb {
@@ -614,7 +617,7 @@ class songdb {
|
|
|
|
|
|
|
|
|
|
struct base_weight_vector { |
|
|
|
|
std::vector<int64_t> person_order; |
|
|
|
|
std::vector<size_t> song_order; |
|
|
|
|
std::vector<int64_t> song_order; |
|
|
|
|
std::vector<std::vector<double>> weights;
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
@ -622,6 +625,7 @@ class songdb {
@@ -622,6 +625,7 @@ class songdb {
|
|
|
|
|
base_weight_vector |
|
|
|
|
get_base_weights (int64_t song_list)
|
|
|
|
|
{ |
|
|
|
|
spdlog::debug("{} {}", __PRETTY_FUNCTION__, __LINE__); |
|
|
|
|
std::vector<vote> list = get_votes_list(song_list); |
|
|
|
|
std::set<int64_t> chat_members; |
|
|
|
|
|
|
|
|
@ -687,6 +691,139 @@ class songdb {
@@ -687,6 +691,139 @@ class songdb {
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
double
|
|
|
|
|
dot_product(const std::vector<double> &a, const std::vector<double> &b)
|
|
|
|
|
{ |
|
|
|
|
assert(a.size() == b.size()); |
|
|
|
|
|
|
|
|
|
double dot = 0; |
|
|
|
|
|
|
|
|
|
for (int i = 0; i < a.size(); i++) { |
|
|
|
|
dot += a.at(i) * b.at(i); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
double dot2 = sqrt(dot); |
|
|
|
|
return dot2; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
double
|
|
|
|
|
weight_badness_inner_product(const std::vector<double> ¤t_badness, const std::vector<double> &song_goodness)
|
|
|
|
|
{ |
|
|
|
|
return dot_product(current_badness,song_goodness); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
std::vector<double>
|
|
|
|
|
update_badness(std::vector<double> old_badness, std::vector<double> song_goodness)
|
|
|
|
|
{ |
|
|
|
|
auto new_badness = old_badness; |
|
|
|
|
for (int i = 0; i < old_badness.size(); i++) { |
|
|
|
|
new_badness[i] = new_badness[i] - song_goodness[i]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return new_badness; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The returned base weight vector has the songs in the sorted order, |
|
|
|
|
* the weights field is the badness vector used for the next song in the |
|
|
|
|
* list. |
|
|
|
|
* |
|
|
|
|
* /param num: the number songs to return |
|
|
|
|
*/ |
|
|
|
|
base_weight_vector |
|
|
|
|
get_top_songs(base_weight_vector input, std::vector<double> starting_badness, int num)
|
|
|
|
|
{ |
|
|
|
|
spdlog::debug("{} {}", __PRETTY_FUNCTION__, __LINE__); |
|
|
|
|
|
|
|
|
|
if (num > input.song_order.size()) { |
|
|
|
|
num = input.song_order.size(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
base_weight_vector result {}; |
|
|
|
|
auto current_badness = starting_badness; |
|
|
|
|
result.person_order = input.person_order; |
|
|
|
|
|
|
|
|
|
struct score { |
|
|
|
|
int64_t song; |
|
|
|
|
double score; |
|
|
|
|
std::vector<double> base_weight; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// create scores vector
|
|
|
|
|
std::vector<score> scores {};
|
|
|
|
|
for (int i = 0; i < input.song_order.size(); i++) { |
|
|
|
|
|
|
|
|
|
scores.push_back({input.song_order.at(i),
|
|
|
|
|
0, input.weights.at(i)}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for (int i = 0; i < num; i++) { |
|
|
|
|
// Compute scores based on badness
|
|
|
|
|
for (int j = 0; j < scores.size(); j++) { |
|
|
|
|
scores[j].score = weight_badness_inner_product(current_badness, scores[j].base_weight); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// sort scores
|
|
|
|
|
std::sort(scores.rbegin(), scores.rend(),
|
|
|
|
|
[](const score &a, const score &b) |
|
|
|
|
{ |
|
|
|
|
return a.score > b.score; |
|
|
|
|
} |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
// chose the song with the best score
|
|
|
|
|
auto chosen = scores.at(scores.size() - 1); |
|
|
|
|
result.song_order.push_back(chosen.song); |
|
|
|
|
|
|
|
|
|
// update badness vector
|
|
|
|
|
current_badness = update_badness(current_badness, chosen.base_weight); |
|
|
|
|
result.weights.push_back(current_badness); |
|
|
|
|
|
|
|
|
|
// run algorithm again on the subset not containing the chosen
|
|
|
|
|
// score, with the updated badness vector
|
|
|
|
|
scores.pop_back(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
std::string get_top_5_songs(int64_t telegram_group) { |
|
|
|
|
spdlog::debug("{} {}", __PRETTY_FUNCTION__, __LINE__); |
|
|
|
|
int64_t song_list = get_song_list_id(telegram_group); |
|
|
|
|
auto songs = get_base_weights(song_list); |
|
|
|
|
|
|
|
|
|
if (songs.weights.size() == 0) { |
|
|
|
|
return {}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
std::vector<double> starting_badness {}; |
|
|
|
|
for (int i = 0; i < songs.person_order.size(); i++) { |
|
|
|
|
starting_badness.push_back(1); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
auto chosen = get_top_songs(songs, starting_badness, 5); |
|
|
|
|
|
|
|
|
|
std::string slist = "Top 5 Songs:\n\n"; |
|
|
|
|
for (int i = 0; i < chosen.song_order.size(); i++) { |
|
|
|
|
int64_t songid = chosen.song_order.at(i); |
|
|
|
|
auto song = get_song(songid); |
|
|
|
|
if (!song) { |
|
|
|
|
throw std::runtime_error("Invalid state achieved."); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
slist += song->name; |
|
|
|
|
slist += " "; |
|
|
|
|
slist += song->artist; |
|
|
|
|
slist += "\n"; |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
return slist; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<track_entry> |
|
|
|
|
generate_track_list(int64_t song_list) |
|
|
|
|
{ |
|
|
|
@ -696,7 +833,7 @@ class songdb {
@@ -696,7 +833,7 @@ class songdb {
|
|
|
|
|
spdlog::info("song {} nppl {}", base_weights.song_order[i], base_weights.weights.size()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
std::vector<track_entry> retlist; |
|
|
|
|
std::vector<track_entry> retlist {}; |
|
|
|
|
return retlist; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -758,6 +895,14 @@ int main() {
@@ -758,6 +895,14 @@ int main() {
|
|
|
|
|
s = new kare::spotify(spotid, spotsecret); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
signal(SIGINT, [](int s) { |
|
|
|
|
spdlog::info("Shutting down..."); |
|
|
|
|
exit(0); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Bot bot(teletoken); |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -884,12 +1029,24 @@ int main() {
@@ -884,12 +1029,24 @@ int main() {
|
|
|
|
|
|
|
|
|
|
bot.getEvents().onCommand("start", [&bot, &data](Message::Ptr message) { |
|
|
|
|
|
|
|
|
|
data.generate_track_list(1); |
|
|
|
|
|
|
|
|
|
bot.getApi().sendMessage(message->chat->id, "Hi!"); |
|
|
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
bot.getEvents().onCommand("list", [&bot, &data](Message::Ptr message) { |
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
|
|
std::string response = data.get_top_5_songs(message->chat->id); |
|
|
|
|
bot.getApi().sendMessage(message->chat->id, response); |
|
|
|
|
} catch (std::exception const &e) { |
|
|
|
|
spdlog::error("exp: {}", e.what()); |
|
|
|
|
spdlog::dump_backtrace(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::string * a = new std::string("hello world"); |
|
|
|
|