Anope IRC Services  Version 2.0
cs_seen.cpp
Go to the documentation of this file.
1 /* cs_seen: provides a seen command by tracking all users
2  *
3  * (C) 2003-2014 Anope Team
4  * Contact us at team@anope.org
5  *
6  * Please read COPYING and README for further details.
7  *
8  * Based on the original code of Epona by Lara.
9  * Based on the original code of Services by Andy Church.
10  */
11 
12 
13 #include "module.h"
14 
16 {
18 };
19 
20 static bool simple;
21 struct SeenInfo;
22 static SeenInfo *FindInfo(const Anope::string &nick);
25 
27 {
31  Anope::string nick2; // for nickchanges and kicks
32  Anope::string channel; // for join/part/kick
33  Anope::string message; // for part/kick/quit
34  time_t last; // the time when the user was last seen
35 
36  SeenInfo() : Serializable("SeenInfo")
37  {
38  }
39 
41  {
42  database_map::iterator iter = database.find(nick);
43  if (iter != database.end() && iter->second == this)
44  database.erase(iter);
45  }
46 
48  {
49  data["nick"] << nick;
50  data["vhost"] << vhost;
51  data["type"] << type;
52  data["nick2"] << nick2;
53  data["channel"] << channel;
54  data["message"] << message;
55  data.SetType("last", Serialize::Data::DT_INT); data["last"] << last;
56  }
57 
59  {
60  Anope::string snick;
61 
62  data["nick"] >> snick;
63 
64  SeenInfo *s;
65  if (obj)
67  else
68  {
69  SeenInfo* &info = database[snick];
70  if (!info)
71  info = new SeenInfo();
72  s = info;
73  }
74 
75  s->nick = snick;
76  data["vhost"] >> s->vhost;
77  unsigned int n;
78  data["type"] >> n;
79  s->type = static_cast<TypeInfo>(n);
80  data["nick2"] >> s->nick2;
81  data["channel"] >> s->channel;
82  data["message"] >> s->message;
83  data["last"] >> s->last;
84 
85  if (!obj)
86  database[s->nick] = s;
87  return s;
88  }
89 };
90 
91 static SeenInfo *FindInfo(const Anope::string &nick)
92 {
93  database_map::iterator iter = database.find(nick);
94  if (iter != database.end())
95  return iter->second;
96  return NULL;
97 }
98 
99 static bool ShouldHide(const Anope::string &channel, User *u)
100 {
101  Channel *targetchan = Channel::Find(channel);
102  const ChannelInfo *targetchan_ci = targetchan ? *targetchan->ci : ChannelInfo::Find(channel);
103 
104  if (targetchan && targetchan->HasMode("SECRET"))
105  return true;
106  else if (targetchan_ci && targetchan_ci->HasExt("CS_PRIVATE"))
107  return true;
108  else if (u && u->HasMode("PRIV"))
109  return true;
110  return false;
111 }
112 
113 class CommandOSSeen : public Command
114 {
115  public:
116  CommandOSSeen(Module *creator) : Command(creator, "operserv/seen", 1, 2)
117  {
118  this->SetDesc(_("Statistics and maintenance for seen data"));
119  this->SetSyntax("STATS");
120  this->SetSyntax(_("CLEAR \037time\037"));
121  }
122 
123  void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
124  {
125  if (params[0].equals_ci("STATS"))
126  {
127  size_t mem_counter;
128  mem_counter = sizeof(database_map);
129  for (database_map::iterator it = database.begin(), it_end = database.end(); it != it_end; ++it)
130  {
131  mem_counter += (5 * sizeof(Anope::string)) + sizeof(TypeInfo) + sizeof(time_t);
132  mem_counter += it->first.capacity();
133  mem_counter += it->second->vhost.capacity();
134  mem_counter += it->second->nick2.capacity();
135  mem_counter += it->second->channel.capacity();
136  mem_counter += it->second->message.capacity();
137  }
138  source.Reply(_("%lu nicks are stored in the database, using %.2Lf kB of memory."), database.size(), static_cast<long double>(mem_counter) / 1024);
139  }
140  else if (params[0].equals_ci("CLEAR"))
141  {
142  time_t time = 0;
143  if ((params.size() < 2) || (0 >= (time = Anope::DoTime(params[1]))))
144  {
145  this->OnSyntaxError(source, params[0]);
146  return;
147  }
148  time = Anope::CurTime - time;
149  database_map::iterator buf;
150  size_t counter = 0;
151  for (database_map::iterator it = database.begin(), it_end = database.end(); it != it_end;)
152  {
153  buf = it;
154  ++it;
155  if (time < buf->second->last)
156  {
157  Log(LOG_DEBUG) << buf->first << " was last seen " << Anope::strftime(buf->second->last) << ", deleting entry";
158  delete buf->second;
159  counter++;
160  }
161  }
162  Log(LOG_ADMIN, source, this) << "CLEAR and removed " << counter << " nicks that were added after " << Anope::strftime(time, NULL, true);
163  source.Reply(_("Database cleared, removed %lu nicks that were added after %s."), counter, Anope::strftime(time, source.nc, true).c_str());
164  }
165  else
166  this->SendSyntax(source);
167  }
168 
169  bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
170  {
171  this->SendSyntax(source);
172  source.Reply(" ");
173  source.Reply(_("The \002STATS\002 command prints out statistics about stored nicks and memory usage."));
174  source.Reply(_("The \002CLEAR\002 command lets you clean the database by removing all entries from the\n"
175  "entries from the database that were added within \037time\037.\n"
176  " \n"
177  "Example:\n"
178  " %s CLEAR 30m\n"
179  " Will remove all entries that were added within the last 30 minutes."), source.command.c_str());
180  return true;
181  }
182 };
183 
184 class CommandSeen : public Command
185 {
186  void SimpleSeen(CommandSource &source, const std::vector<Anope::string> &params)
187  {
188  if (!source.c || !source.c->ci)
189  {
190  if (source.IsOper())
191  source.Reply("Seen in simple mode is designed as a fantasy command only!");
192  return;
193  }
194 
195  BotInfo *bi = BotInfo::Find(params[0], true);
196  if (bi)
197  {
198  if (bi == source.c->ci->bi)
199  source.Reply(_("You found me, %s!"), source.GetNick().c_str());
200  else
201  source.Reply(_("%s is a network service."), bi->nick.c_str());
202  return;
203  }
204 
205  NickAlias *na = NickAlias::Find(params[0]);
206  if (!na)
207  {
208  source.Reply(_("I don't know who %s is."), params[0].c_str());
209  return;
210  }
211 
212  if (source.GetAccount() == na->nc)
213  {
214  source.Reply(_("Looking for yourself, eh %s?"), source.GetNick().c_str());
215  return;
216  }
217 
218  User *target = User::Find(params[0], true);
219 
220  if (target && source.c->FindUser(target))
221  {
222  source.Reply(_("%s is on the channel right now!"), target->nick.c_str());
223  return;
224  }
225 
226  for (Channel::ChanUserList::const_iterator it = source.c->users.begin(), it_end = source.c->users.end(); it != it_end; ++it)
227  {
228  ChanUserContainer *uc = it->second;
229  User *u = uc->user;
230 
231  if (u->Account() == na->nc)
232  {
233  source.Reply(_("%s is on the channel right now (as %s)!"), params[0].c_str(), u->nick.c_str());
234  return;
235  }
236  }
237 
238  AccessGroup ag = source.c->ci->AccessFor(na->nc);
239  time_t last = 0;
240  for (unsigned i = 0; i < ag.size(); ++i)
241  {
242  ChanAccess *a = ag[i];
243 
244  if (a->GetAccount() == na->nc && a->last_seen > last)
245  last = a->last_seen;
246  }
247 
248  if (last > Anope::CurTime || !last)
249  source.Reply(_("I've never seen %s on this channel."), na->nick.c_str());
250  else
251  source.Reply(_("%s was last seen here %s ago."), na->nick.c_str(), Anope::Duration(Anope::CurTime - last, source.GetAccount()).c_str());
252  }
253 
254  public:
255  CommandSeen(Module *creator) : Command(creator, "chanserv/seen", 1, 2)
256  {
257  this->SetDesc(_("Tells you about the last time a user was seen"));
258  this->SetSyntax(_("\037nick\037"));
259  }
260 
261  void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
262  {
263  const Anope::string &target = params[0];
264 
265  if (simple)
266  return this->SimpleSeen(source, params);
267 
268  if (target.length() > Config->GetBlock("networkinfo")->Get<unsigned>("nicklen"))
269  {
270  source.Reply(_("Nick too long, max length is %u characters."), Config->GetBlock("networkinfo")->Get<unsigned>("nicklen"));
271  return;
272  }
273 
274  if (BotInfo::Find(target, true) != NULL)
275  {
276  source.Reply(_("%s is a client on services."), target.c_str());
277  return;
278  }
279 
280  if (target.equals_ci(source.GetNick()))
281  {
282  source.Reply(_("You might see yourself in the mirror, %s."), source.GetNick().c_str());
283  return;
284  }
285 
286  SeenInfo *info = FindInfo(target);
287  if (!info)
288  {
289  source.Reply(_("Sorry, I have not seen %s."), target.c_str());
290  return;
291  }
292 
293  User *u2 = User::Find(target, true);
294  Anope::string onlinestatus;
295  if (u2)
296  onlinestatus = ".";
297  else
298  onlinestatus = Anope::printf(_(" but %s mysteriously dematerialized."), target.c_str());
299 
300  Anope::string timebuf = Anope::Duration(Anope::CurTime - info->last, source.nc);
301  Anope::string timebuf2 = Anope::strftime(info->last, source.nc, true);
302 
303  if (info->type == NEW)
304  {
305  source.Reply(_("%s (%s) was last seen connecting %s ago (%s)%s"),
306  target.c_str(), info->vhost.c_str(), timebuf.c_str(), timebuf2.c_str(), onlinestatus.c_str());
307  }
308  else if (info->type == NICK_TO)
309  {
310  u2 = User::Find(info->nick2, true);
311  if (u2)
312  onlinestatus = Anope::printf( _(". %s is still online."), u2->nick.c_str());
313  else
314  onlinestatus = Anope::printf(_(", but %s mysteriously dematerialized."), info->nick2.c_str());
315 
316  source.Reply(_("%s (%s) was last seen changing nick to %s %s ago%s"),
317  target.c_str(), info->vhost.c_str(), info->nick2.c_str(), timebuf.c_str(), onlinestatus.c_str());
318  }
319  else if (info->type == NICK_FROM)
320  {
321  source.Reply(_("%s (%s) was last seen changing nick from %s to %s %s ago%s"),
322  target.c_str(), info->vhost.c_str(), info->nick2.c_str(), target.c_str(), timebuf.c_str(), onlinestatus.c_str());
323  }
324  else if (info->type == JOIN)
325  {
326  if (ShouldHide(info->channel, u2))
327  source.Reply(_("%s (%s) was last seen joining a secret channel %s ago%s"),
328  target.c_str(), info->vhost.c_str(), timebuf.c_str(), onlinestatus.c_str());
329  else
330  source.Reply(_("%s (%s) was last seen joining %s %s ago%s"),
331  target.c_str(), info->vhost.c_str(), info->channel.c_str(), timebuf.c_str(), onlinestatus.c_str());
332  }
333  else if (info->type == PART)
334  {
335  if (ShouldHide(info->channel, u2))
336  source.Reply(_("%s (%s) was last seen parting a secret channel %s ago%s"),
337  target.c_str(), info->vhost.c_str(), timebuf.c_str(), onlinestatus.c_str());
338  else
339  source.Reply(_("%s (%s) was last seen parting %s %s ago%s"),
340  target.c_str(), info->vhost.c_str(), info->channel.c_str(), timebuf.c_str(), onlinestatus.c_str());
341  }
342  else if (info->type == QUIT)
343  {
344  source.Reply(_("%s (%s) was last seen quitting (%s) %s ago (%s)."),
345  target.c_str(), info->vhost.c_str(), info->message.c_str(), timebuf.c_str(), timebuf2.c_str());
346  }
347  else if (info->type == KICK)
348  {
349  if (ShouldHide(info->channel, u2))
350  source.Reply(_("%s (%s) was kicked from a secret channel %s ago%s"),
351  target.c_str(), info->vhost.c_str(), timebuf.c_str(), onlinestatus.c_str());
352  else
353  source.Reply(_("%s (%s) was kicked from %s (\"%s\") %s ago%s"),
354  target.c_str(), info->vhost.c_str(), info->channel.c_str(), info->message.c_str(), timebuf.c_str(), onlinestatus.c_str());
355  }
356  }
357 
358  bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
359  {
360  this->SendSyntax(source);
361  source.Reply(" ");
362  source.Reply(_("Checks for the last time \037nick\037 was seen joining, leaving,\n"
363  "or changing nick on the network and tells you when and, depending\n"
364  "on channel or user settings, where it was."));
365  return true;
366  }
367 };
368 
369 class CSSeen : public Module
370 {
374  public:
375  CSSeen(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), seeninfo_type("SeenInfo", SeenInfo::Unserialize), commandseen(this), commandosseen(this)
376  {
377  }
378 
380  {
381  simple = conf->GetModule(this)->Get<bool>("simple");
382  }
383 
385  {
386  size_t previous_size = database.size();
387  time_t purgetime = Config->GetModule(this)->Get<time_t>("purgetime");
388  if (!purgetime)
389  purgetime = Anope::DoTime("30d");
390  for (database_map::iterator it = database.begin(), it_end = database.end(); it != it_end;)
391  {
392  database_map::iterator cur = it;
393  ++it;
394 
395  if ((Anope::CurTime - cur->second->last) > purgetime)
396  {
397  Log(LOG_DEBUG) << cur->first << " was last seen " << Anope::strftime(cur->second->last) << ", purging entries";
398  delete cur->second;
399  }
400  }
401  Log(LOG_DEBUG) << "cs_seen: Purged database, checked " << previous_size << " nicks and removed " << (previous_size - database.size()) << " old entries.";
402  }
403 
404  void OnUserConnect(User *u, bool &exempt) anope_override
405  {
406  if (!u->Quitting())
407  UpdateUser(u, NEW, u->nick, "", "", "");
408  }
409 
411  {
412  UpdateUser(u, NICK_TO, oldnick, u->nick, "", "");
413  UpdateUser(u, NICK_FROM, u->nick, oldnick, "", "");
414  }
415 
417  {
418  UpdateUser(u, QUIT, u->nick, "", "", msg);
419  }
420 
422  {
423  UpdateUser(u, JOIN, u->nick, "", c->name, "");
424  }
425 
426  void OnPartChannel(User *u, Channel *c, const Anope::string &channel, const Anope::string &msg) anope_override
427  {
428  UpdateUser(u, PART, u->nick, "", channel, msg);
429  }
430 
432  {
433  UpdateUser(cu->user, KICK, cu->user->nick, source.GetSource(), cu->chan->name, msg);
434  }
435 
436  private:
437  void UpdateUser(const User *u, const TypeInfo Type, const Anope::string &nick, const Anope::string &nick2, const Anope::string &channel, const Anope::string &message)
438  {
439  if (simple || !u->server->IsSynced())
440  return;
441 
442  SeenInfo* &info = database[nick];
443  if (!info)
444  info = new SeenInfo();
445  info->nick = nick;
446  info->vhost = u->GetVIdent() + "@" + u->GetDisplayedHost();
447  info->type = Type;
448  info->last = Anope::CurTime;
449  info->nick2 = nick2;
450  info->channel = channel;
451  info->message = message;
452  }
453 };
454 
Serialize::Reference< NickCore > nc
Definition: account.h:47
Definition: bots.h:24
bool HasMode(const Anope::string &name) const
Definition: users.cpp:513
void OnUserConnect(User *u, bool &exempt) anope_override
Definition: cs_seen.cpp:404
void OnUserQuit(User *u, const Anope::string &msg) anope_override
Definition: cs_seen.cpp:416
static NickAlias * Find(const Anope::string &nick)
Definition: nickalias.cpp:121
bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
Definition: cs_seen.cpp:358
Definition: cs_seen.cpp:17
bool IsSynced() const
Definition: servers.cpp:298
void OnReload(Configuration::Conf *conf) anope_override
Definition: cs_seen.cpp:379
TypeInfo
Definition: cs_seen.cpp:15
Anope::string nick2
Definition: cs_seen.cpp:31
void Execute(CommandSource &source, const std::vector< Anope::string > &params) anope_override
Definition: cs_seen.cpp:123
TypeInfo type
Definition: cs_seen.cpp:30
Anope::string nick
Definition: cs_seen.cpp:28
Anope::string nick
Definition: account.h:37
CSSeen(const Anope::string &modname, const Anope::string &creator)
Definition: cs_seen.cpp:375
void OnJoinChannel(User *u, Channel *c) anope_override
Definition: cs_seen.cpp:421
CoreExport string printf(const char *fmt,...)
Definition: misc.cpp:536
Reference< Channel > c
Definition: commands.h:65
SeenInfo()
Definition: cs_seen.cpp:36
void Execute(CommandSource &source, const std::vector< Anope::string > &params) anope_override
Definition: cs_seen.cpp:261
Definition: users.h:34
static SeenInfo * FindInfo(const Anope::string &nick)
Definition: cs_seen.cpp:91
Definition: cs_seen.cpp:17
void OnPartChannel(User *u, Channel *c, const Anope::string &channel, const Anope::string &msg) anope_override
Definition: cs_seen.cpp:426
void OnPreUserKicked(const MessageSource &source, ChanUserContainer *cu, const Anope::string &msg) anope_override
Definition: cs_seen.cpp:431
Serialize::Type seeninfo_type
Definition: cs_seen.cpp:371
Definition: cs_seen.cpp:17
NickCore * GetAccount() const
Definition: access.cpp:204
void SetDesc(const Anope::string &d)
Definition: command.cpp:130
bool equals_ci(const char *_str) const
Definition: anope.h:78
~SeenInfo()
Definition: cs_seen.cpp:40
Serialize::Reference< ChannelInfo > ci
Definition: channels.h:46
CoreExport time_t CurTime
Definition: main.cpp:41
const Anope::string & GetNick() const
Definition: command.cpp:26
Anope::string vhost
Definition: cs_seen.cpp:29
static ChannelInfo * Find(const Anope::string &name)
Definition: regchannel.cpp:630
time_t last
Definition: cs_seen.cpp:34
CommandOSSeen commandosseen
Definition: cs_seen.cpp:373
void Serialize(Serialize::Data &data) const anope_override
Definition: cs_seen.cpp:47
Anope::string message
Definition: cs_seen.cpp:33
static bool ShouldHide(const Anope::string &channel, User *u)
Definition: cs_seen.cpp:99
size_type length() const
Definition: anope.h:131
CommandSeen(Module *creator)
Definition: cs_seen.cpp:255
Definition: Config.cs:26
time_t last_seen
Definition: access.h:94
virtual void OnSyntaxError(CommandSource &source, const Anope::string &subcommand)
Definition: command.cpp:191
Anope::string channel
Definition: cs_seen.cpp:32
CoreExport time_t DoTime(const Anope::string &s)
Definition: misc.cpp:275
void Reply(const char *message,...)
Definition: command.cpp:96
const Anope::string & GetDisplayedHost() const
Definition: users.cpp:203
CommandOSSeen(Module *creator)
Definition: cs_seen.cpp:116
Definition: cs_seen.cpp:17
void OnExpireTick() anope_override
Definition: cs_seen.cpp:384
database_map database
Definition: cs_seen.cpp:24
CommandSeen commandseen
Definition: cs_seen.cpp:372
const Anope::string & GetVIdent() const
Definition: users.cpp:247
void UpdateUser(const User *u, const TypeInfo Type, const Anope::string &nick, const Anope::string &nick2, const Anope::string &channel, const Anope::string &message)
Definition: cs_seen.cpp:437
#define anope_override
Definition: services.h:56
Definition: cs_seen.cpp:17
std::basic_string< char, ci_char_traits, std::allocator< char > > string
Definition: hashcomp.h:133
#define MODULE_INIT(x)
Definition: modules.h:45
Server * server
Definition: users.h:77
ChanUserList users
Definition: channels.h:56
ChanUserContainer * FindUser(User *u) const
Definition: channels.cpp:173
bool IsOper()
Definition: command.cpp:87
void SetSyntax(const Anope::string &s)
Definition: command.cpp:140
void OnUserNickChange(User *u, const Anope::string &oldnick) anope_override
Definition: cs_seen.cpp:410
size_t HasMode(const Anope::string &name, const Anope::string &param="")
Definition: channels.cpp:201
static bool simple
Definition: cs_seen.cpp:20
Anope::string nick
Definition: users.h:62
static User * Find(const Anope::string &name, bool nick_only=false)
Definition: users.cpp:815
NickCore * Account() const
Definition: users.cpp:422
static Channel * Find(const Anope::string &name)
Definition: channels.cpp:920
T anope_dynamic_static_cast(O ptr)
Definition: anope.h:774
void SendSyntax(CommandSource &)
Definition: command.cpp:145
NickCore * GetAccount()
Definition: command.cpp:36
const char * c_str() const
Definition: anope.h:117
Definition: logger.h:53
CoreExport Anope::string Duration(time_t seconds, const NickCore *nc=NULL)
Definition: misc.cpp:315
static Serializable * Unserialize(Serializable *obj, Serialize::Data &data)
Definition: cs_seen.cpp:58
CoreExport Anope::string strftime(time_t t, const NickCore *nc=NULL, bool short_output=false)
Definition: misc.cpp:356
void SimpleSeen(CommandSource &source, const std::vector< Anope::string > &params)
Definition: cs_seen.cpp:186
Anope::hash_map< SeenInfo * > database_map
Definition: cs_seen.cpp:23
#define _(x)
Definition: services.h:50
static BotInfo * Find(const Anope::string &nick, bool nick_only=false)
Definition: bots.cpp:249
bool HasExt(const Anope::string &name) const
Definition: extensible.cpp:31
Type(const Anope::string &n, unserialize_func f, Module *owner=NULL)
bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
Definition: cs_seen.cpp:169