You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

291 lines
9.6 KiB

  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")