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