diff --git a/chat.cpp b/chat.cpp index cd56aa4..5513c79 100644 --- a/chat.cpp +++ b/chat.cpp @@ -14,7 +14,7 @@ #include #include -Chat::Chat(const QuesoQueue &qq, const Timer &timer) : _qq(qq), _timer(timer) { +Chat::Chat(const QuesoQueue &qq, const Twitch &twitch, const Timer &timer) : _qq(qq), _twitch(twitch), _timer(timer) { namespace fs = std::experimental::filesystem; static const std::regex chipModuleRegex(".*\\.so", std::regex_constants::egrep); @@ -109,6 +109,7 @@ void Chat::Listen() { // Get message body std::string messageBody = m[2]; + _twitch.markAsOnline(username); HandleMessage(std::stringstream(messageBody), username); continue; } @@ -135,8 +136,10 @@ std::string Chat::LevelListMessage(std::optional current, PriorityQueso l ss << online.size() + (current ? 1 : 0) << " online level(s) in the queue: "; + std::string starter = current ? std::string(current->submitter + " (current)") : ""; + ss << std::accumulate(online.begin(), online.end(), - std::string(current->submitter + " (current)"), + starter, [](std::string acc, Level x){ return acc + ", " + x.submitter; }); @@ -151,7 +154,7 @@ std::string Chat::NextLevelMessage(std::optional l) { } std::stringstream ss; - ss << "Next up in queue is " << l->levelCode << ", submitted by " + ss << "Next is " << l->levelCode << ", submitted by " << l->submitter; return ss.str(); } @@ -177,7 +180,7 @@ std::string Chat::PositionMessage(int position) { msg << "Your level is being played right now!"; break; default: - msg << "You are currently in position " << position+1; + msg << "You are currently in position " << position; break; } return msg.str(); @@ -235,6 +238,14 @@ void Chat::HandleMessage(std::stringstream message, std::string sender) { WriteMessage(NextLevelMessage(l)); } else if (command == "current") { WriteMessage(CurrentLevelMessage(_qq.Current())); + } else if (command == "random" && sender == Auth::channel) { + _timer.Reset(); + std::optional l = _qq.Random(); + if (l) { + WriteMessage(std::string("/marker " + l->levelCode + ", submitted by " + + l->submitter)); + } + WriteMessage(NextLevelMessage(l)); } else if (command == "list") { WriteMessage(LevelListMessage(_qq.Current(), _qq.List())); } else if (command == "position") { diff --git a/chat.h b/chat.h index 5d1d141..5bf332a 100644 --- a/chat.h +++ b/chat.h @@ -14,7 +14,7 @@ class Chat { public: - Chat(const QuesoQueue &qq, const Timer &timer); + Chat(const QuesoQueue &qq, const Twitch &twitch, const Timer &timer); void HandleMessage(std::stringstream message, std::string sender); void WriteMessage(std::string message); void Write(std::string command); @@ -30,6 +30,7 @@ class Chat { bool _canAddToQueue = true; QuesoQueue _qq; + Twitch _twitch; Timer _timer; std::string _priorityText; diff --git a/main.cpp b/main.cpp index 58f4a8f..460f9de 100644 --- a/main.cpp +++ b/main.cpp @@ -8,7 +8,7 @@ int main(int, char **) { QuesoQueue qq(t); qq.LoadLastState(); Timer ti; - Chat c(qq, ti); + Chat c(qq, t, ti); c.Connect(); c.Listen(); } diff --git a/quesoqueue.cpp b/quesoqueue.cpp index 2ff63f1..aa51b7f 100644 --- a/quesoqueue.cpp +++ b/quesoqueue.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -58,19 +59,15 @@ std::string QuesoQueue::Add(Level level) { if (result == _levels.end() || level.submitter == Auth::channel) { auto online_levels = std::get<0>(List()).size(); // push to the end of the queue - if (_levels.empty() && !Current().has_value()) { - _current = std::make_optional(level); - } else { - _levels.push_back(level); - online_levels++; - } + _levels.push_back(level); + online_levels++; std::stringstream ss; // Since they JUST added it, we can pretty safely assume they're online. ss << level.submitter; ss << ", "; ss << level.levelCode; ss << " has been added to the queue. Currently in position #"; - ss << online_levels + 1; + ss << online_levels + (_current ? 1 : 0); ss << "."; SaveState(); return ss.str(); @@ -169,7 +166,7 @@ int QuesoQueue::Position(std::string username) { for (Level l : both) { position++; if (l.submitter == username) { - return position; + return position + (_current ? 1 : 0); } } // not in queue @@ -225,6 +222,42 @@ std::optional QuesoQueue::Current() { return _current; } +std::optional QuesoQueue::Random() { + auto list = List(); + std::deque levels(std::get<0>(list)); // Use online levels first. + + if (levels.empty()) { + levels = std::get<1>(list); // See if there are offline levels. + if (levels.empty()) { + _current = std::nullopt; + return _current; + } + } + std::vector new_current = {}; + std::sample(levels.begin(), levels.end(), std::back_inserter(new_current), + 1, std::mt19937{std::random_device{}()}); + + if (new_current.empty()) { + _current = std::nullopt; + } else { + _current = std::make_optional(new_current[0]); + } + + // Remove current (it's in a special current place now) + _levels.erase( + std::find_if( + _levels.begin(), + _levels.end(), + [&](auto l){ + return l.submitter == _current->submitter && + l.levelCode == _current->levelCode; + } + ) + ); + SaveState(); + return _current; +} + PriorityQueso QuesoQueue::List() { std::deque online, offline; std::set online_users = _twitch.getOnlineUsers(Auth::channel); diff --git a/quesoqueue.h b/quesoqueue.h index 1912bc2..962c14c 100644 --- a/quesoqueue.h +++ b/quesoqueue.h @@ -46,6 +46,11 @@ class QuesoQueue { */ std::optional Punt(); + /** + * Selects a random level from the queue and returns the new top (subject to priority queue split) + */ + std::optional Random(); + /** * Split the stored level queue into online and offline for printing */ @@ -65,5 +70,5 @@ class QuesoQueue { std::deque _levels; std::optional _current; Twitch _twitch; // query online state - const size_t maxSize = 15; + const size_t maxSize = 30; }; diff --git a/twitch.cpp b/twitch.cpp index faf312e..dbdd9ba 100644 --- a/twitch.cpp +++ b/twitch.cpp @@ -1,7 +1,11 @@ #include "twitch.h" +#include +#include #include #include +#include +#include #include #include "SimpleJSON/JSON.h" @@ -67,9 +71,25 @@ std::set Twitch::getOnlineUsers(const std::string& channel) { } } + auto current_time = std::chrono::system_clock::now(); + auto user_snapshot = _recent_chatters; + _recent_chatters = {}; + for(auto const& [user, last_heard_from] : user_snapshot) { + //TODO: This time should be runtime configurable. + if (current_time - last_heard_from < std::chrono::minutes(5)) { + online_users.emplace(user); + _recent_chatters.emplace(user, last_heard_from); + } + } + return online_users; } +void Twitch::markAsOnline(std::string username) { + auto current = std::chrono::system_clock::now(); + _recent_chatters.emplace(username, current); +} + void placeStreamMarker() { // stub } diff --git a/twitch.h b/twitch.h index 8a075ab..decf2be 100644 --- a/twitch.h +++ b/twitch.h @@ -1,5 +1,8 @@ #pragma once +#include +#include +#include #include #include @@ -9,6 +12,9 @@ class Twitch { std::set getOnlineUsers(const std::string& channel); void placeStreamMarker(); + void markAsOnline(std::string username); + private: + std::map _recent_chatters; // IDK twitch API keys?????? };