110-add-playback-module.patch (9869B)
1 --- /dev/null 2 +++ b/modules/playback.cpp 3 @@ -0,0 +1,288 @@ 4 +/* 5 + * Copyright (C) 2015 J-P Nurmi 6 + * 7 + * Licensed under the Apache License, Version 2.0 (the "License"); 8 + * you may not use this file except in compliance with the License. 9 + * You may obtain a copy of the License at 10 + * 11 + * http://www.apache.org/licenses/LICENSE-2.0 12 + * 13 + * Unless required by applicable law or agreed to in writing, software 14 + * distributed under the License is distributed on an "AS IS" BASIS, 15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 + * See the License for the specific language governing permissions and 17 + * limitations under the License. 18 + */ 19 + 20 +#include <znc/Modules.h> 21 +#include <znc/IRCNetwork.h> 22 +#include <znc/Client.h> 23 +#include <znc/Buffer.h> 24 +#include <znc/Utils.h> 25 +#include <znc/Query.h> 26 +#include <znc/Chan.h> 27 +#include <znc/znc.h> 28 +#include <znc/version.h> 29 +#include <sys/time.h> 30 +#include <cfloat> 31 + 32 +#if (VERSION_MAJOR < 1) || (VERSION_MAJOR == 1 && VERSION_MINOR < 6) 33 +#error The playback module requires ZNC version 1.6.0 or later. 34 +#endif 35 + 36 +static const char* PlaybackCap = "znc.in/playback"; 37 + 38 +class CPlaybackMod : public CModule 39 +{ 40 +public: 41 + MODCONSTRUCTOR(CPlaybackMod) 42 + { 43 + m_play = false; 44 + AddHelpCommand(); 45 + AddCommand("Clear", static_cast<CModCommand::ModCmdFunc>(&CPlaybackMod::ClearCommand), "<buffer(s)>", "Clears playback for given buffers."); 46 + AddCommand("Play", static_cast<CModCommand::ModCmdFunc>(&CPlaybackMod::PlayCommand), "<buffer(s)> [from] [to]", "Sends playback for given buffers."); 47 + AddCommand("List", static_cast<CModCommand::ModCmdFunc>(&CPlaybackMod::ListCommand), "[buffer(s)]", "Lists available/matching buffers."); 48 + AddCommand("Limit", static_cast<CModCommand::ModCmdFunc>(&CPlaybackMod::LimitCommand), "<client> [limit]", "Get/set the buffer limit (<= 0 to clear) for the given client."); 49 + } 50 + 51 + void OnClientCapLs(CClient* client, SCString& caps) override 52 + { 53 + caps.insert(PlaybackCap); 54 + } 55 + 56 + bool IsClientCapSupported(CClient* client, const CString& cap, bool state) override 57 + { 58 + return cap.Equals(PlaybackCap); 59 + } 60 + 61 + EModRet OnChanBufferStarting(CChan& chan, CClient& client) override 62 + { 63 + if (!m_play && client.IsCapEnabled(PlaybackCap)) 64 + return HALTCORE; 65 + return CONTINUE; 66 + } 67 + 68 + EModRet OnChanBufferPlayLine(CChan& chan, CClient& client, CString& line) override 69 + { 70 + if (!m_play && client.IsCapEnabled(PlaybackCap)) 71 + return HALTCORE; 72 + return CONTINUE; 73 + } 74 + 75 + EModRet OnChanBufferEnding(CChan& chan, CClient& client) override 76 + { 77 + if (!m_play && client.IsCapEnabled(PlaybackCap)) 78 + return HALTCORE; 79 + return CONTINUE; 80 + } 81 + 82 + EModRet OnPrivBufferPlayLine(CClient& client, CString& line) override 83 + { 84 + if (!m_play && client.IsCapEnabled(PlaybackCap)) 85 + return HALTCORE; 86 + return CONTINUE; 87 + } 88 + 89 + void ClearCommand(const CString& line) 90 + { 91 + // CLEAR <buffer(s)> 92 + const CString arg = line.Token(1); 93 + if (arg.empty() || !line.Token(2).empty()) 94 + return; 95 + std::vector<CChan*> chans = FindChans(arg); 96 + for (CChan* chan : chans) 97 + chan->ClearBuffer(); 98 + std::vector<CQuery*> queries = FindQueries(arg); 99 + for (CQuery* query : queries) 100 + query->ClearBuffer(); 101 + } 102 + 103 + void PlayCommand(const CString& line) 104 + { 105 + // PLAY <buffer(s)> [from] [to] 106 + const CString arg = line.Token(1); 107 + if (arg.empty() || !line.Token(4).empty()) 108 + return; 109 + double from = line.Token(2).ToDouble(); 110 + double to = DBL_MAX; 111 + if (!line.Token(3).empty()) 112 + to = line.Token(3).ToDouble(); 113 + int limit = -1; 114 + if (CClient* client = GetClient()) 115 + limit = GetLimit(client->GetIdentifier()); 116 + std::vector<CChan*> chans = FindChans(arg); 117 + for (CChan* chan : chans) { 118 + if (chan->IsOn() && !chan->IsDetached()) { 119 + CBuffer lines = GetLines(chan->GetBuffer(), from, to, limit); 120 + m_play = true; 121 + chan->SendBuffer(GetClient(), lines); 122 + m_play = false; 123 + } 124 + } 125 + std::vector<CQuery*> queries = FindQueries(arg); 126 + for (CQuery* query : queries) { 127 + CBuffer lines = GetLines(query->GetBuffer(), from, to, limit); 128 + m_play = true; 129 + query->SendBuffer(GetClient(), lines); 130 + m_play = false; 131 + } 132 + } 133 + 134 + void ListCommand(const CString& line) 135 + { 136 + // LIST [buffer(s)] 137 + CString arg = line.Token(1); 138 + if (arg.empty()) 139 + arg = "*"; 140 + std::vector<CChan*> chans = FindChans(arg); 141 + for (CChan* chan : chans) { 142 + if (chan->IsOn() && !chan->IsDetached()) { 143 + CBuffer buffer = chan->GetBuffer(); 144 + if (!buffer.IsEmpty()) { 145 + timeval from = UniversalTime(buffer.GetBufLine(0).GetTime()); 146 + timeval to = UniversalTime(buffer.GetBufLine(buffer.Size() - 1).GetTime()); 147 + PutModule(chan->GetName() + " " + CString(Timestamp(from)) + " " + CString(Timestamp(to))); 148 + } 149 + } 150 + } 151 + std::vector<CQuery*> queries = FindQueries(arg); 152 + for (CQuery* query : queries) { 153 + CBuffer buffer = query->GetBuffer(); 154 + if (!buffer.IsEmpty()) { 155 + timeval from = UniversalTime(buffer.GetBufLine(0).GetTime()); 156 + timeval to = UniversalTime(buffer.GetBufLine(buffer.Size() - 1).GetTime()); 157 + PutModule(query->GetName() + " " + CString(Timestamp(from)) + " " + CString(Timestamp(to))); 158 + } 159 + } 160 + } 161 + 162 + void LimitCommand(const CString& line) 163 + { 164 + // LIMIT <client> [limit] 165 + const CString client = line.Token(1); 166 + if (client.empty()) { 167 + PutModule("Usage: LIMIT <client> [limit]"); 168 + return; 169 + } 170 + const CString arg = line.Token(2); 171 + int limit = GetLimit(client); 172 + if (!arg.empty()) { 173 + limit = arg.ToInt(); 174 + SetLimit(client, limit); 175 + } 176 + if (limit <= 0) 177 + PutModule(client + " buffer limit: -"); 178 + else 179 + PutModule(client + " buffer limit: " + CString(limit)); 180 + } 181 + 182 + EModRet OnSendToClient(CString& line, CClient& client) override 183 + { 184 + if (client.IsAttached() && client.IsCapEnabled(PlaybackCap) && !line.Token(0).Equals("CAP")) { 185 + MCString tags = CUtils::GetMessageTags(line); 186 + if (tags.find("time") == tags.end()) { 187 + // CUtils::FormatServerTime() converts to UTC 188 + tags["time"] = CUtils::FormatServerTime(LocalTime()); 189 + CUtils::SetMessageTags(line, tags); 190 + } 191 + } 192 + return CONTINUE; 193 + } 194 + 195 +private: 196 + static double Timestamp(timeval tv) 197 + { 198 + return tv.tv_sec + tv.tv_usec / 1000000.0; 199 + } 200 + 201 + static timeval LocalTime() 202 + { 203 + timeval tv; 204 + if (gettimeofday(&tv, NULL) == -1) { 205 + tv.tv_sec = time(NULL); 206 + tv.tv_usec = 0; 207 + } 208 + return tv; 209 + } 210 + 211 + static timeval UniversalTime(timeval tv = LocalTime()) 212 + { 213 + tm stm; 214 + memset(&stm, 0, sizeof(stm)); 215 + const time_t secs = tv.tv_sec; // OpenBSD has tv_sec as int, so explicitly convert it to time_t to make gmtime_r() happy 216 + gmtime_r(&secs, &stm); 217 + const char* tz = getenv("TZ"); 218 + setenv("TZ", "UTC", 1); 219 + tzset(); 220 + tv.tv_sec = mktime(&stm); 221 + if (tz) 222 + setenv("TZ", tz, 1); 223 + else 224 + unsetenv("TZ"); 225 + tzset(); 226 + return tv; 227 + } 228 + 229 + std::vector<CChan*> FindChans(const CString& arg) const 230 + { 231 + std::vector<CChan*> chans; 232 + CIRCNetwork* network = GetNetwork(); 233 + if (network) { 234 + VCString vargs; 235 + arg.Split(",", vargs, false); 236 + 237 + for (const CString& name : vargs) { 238 + std::vector<CChan*> found = network->FindChans(name); 239 + chans.insert(chans.end(), found.begin(), found.end()); 240 + } 241 + } 242 + return chans; 243 + } 244 + 245 + std::vector<CQuery*> FindQueries(const CString& arg) const 246 + { 247 + std::vector<CQuery*> queries; 248 + CIRCNetwork* network = GetNetwork(); 249 + if (network) { 250 + VCString vargs; 251 + arg.Split(",", vargs, false); 252 + 253 + for (const CString& name : vargs) { 254 + std::vector<CQuery*> found = network->FindQueries(name); 255 + queries.insert(queries.end(), found.begin(), found.end()); 256 + } 257 + } 258 + return queries; 259 + } 260 + 261 + int GetLimit(const CString& client) const 262 + { 263 + return GetNV(client).ToInt(); 264 + } 265 + 266 + void SetLimit(const CString& client, int limit) 267 + { 268 + if (limit > 0) 269 + SetNV(client, CString(limit)); 270 + else 271 + DelNV(client); 272 + } 273 + 274 + static CBuffer GetLines(const CBuffer& buffer, double from, double to, int limit) 275 + { 276 + CBuffer lines(buffer.Size()); 277 + for (size_t i = 0; i < buffer.Size(); ++i) { 278 + const CBufLine& line = buffer.GetBufLine(i); 279 + timeval tv = UniversalTime(line.GetTime()); 280 + if (from < Timestamp(tv) && to >= Timestamp(tv)) 281 + lines.AddLine(line.GetFormat(), line.GetText(), &tv); 282 + } 283 + if (limit > 0) 284 + lines.SetLineCount(limit); 285 + return lines; 286 + } 287 + 288 + bool m_play; 289 +}; 290 + 291 +GLOBALMODULEDEFS(CPlaybackMod, "An advanced playback module for ZNC")