anope

- supernets anope source code & configuration
git clone git://git.acid.vegas/anope.git
Log | Files | Refs | Archive | README

cs_topic.cpp (8511B)

      1 /* ChanServ core functions
      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 #include "modules/cs_mode.h"
     14 
     15 class CommandCSSetKeepTopic : public Command
     16 {
     17  public:
     18 	CommandCSSetKeepTopic(Module *creator, const Anope::string &cname = "chanserv/set/keeptopic") : Command(creator, cname, 2, 2)
     19 	{
     20 		this->SetDesc(_("Retain topic when channel is not in use"));
     21 		this->SetSyntax(_("\037channel\037 {ON | OFF}"));
     22 	}
     23 
     24 	void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
     25 	{
     26 		if (Anope::ReadOnly)
     27 		{
     28 			source.Reply(READ_ONLY_MODE);
     29 			return;
     30 		}
     31 
     32 		ChannelInfo *ci = ChannelInfo::Find(params[0]);
     33 		if (ci == NULL)
     34 		{
     35 			source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
     36 			return;
     37 		}
     38 
     39 		EventReturn MOD_RESULT;
     40 		FOREACH_RESULT(OnSetChannelOption, MOD_RESULT, (source, this, ci, params[1]));
     41 		if (MOD_RESULT == EVENT_STOP)
     42 			return;
     43 
     44 		if (MOD_RESULT != EVENT_ALLOW && !source.AccessFor(ci).HasPriv("SET") && source.permission.empty() && !source.HasPriv("chanserv/administration"))
     45 		{
     46 			source.Reply(ACCESS_DENIED);
     47 			return;
     48 		}
     49 
     50 		if (params[1].equals_ci("ON"))
     51 		{
     52 			Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to enable keeptopic";
     53 			ci->Extend<bool>("KEEPTOPIC");
     54 			source.Reply(_("Topic retention option for %s is now \002on\002."), ci->name.c_str());
     55 		}
     56 		else if (params[1].equals_ci("OFF"))
     57 		{
     58 			Log(source.AccessFor(ci).HasPriv("SET") ? LOG_COMMAND : LOG_OVERRIDE, source, this, ci) << "to disable keeptopic";
     59 			ci->Shrink<bool>("KEEPTOPIC");
     60 			source.Reply(_("Topic retention option for %s is now \002off\002."), ci->name.c_str());
     61 		}
     62 		else
     63 			this->OnSyntaxError(source, "KEEPTOPIC");
     64 	}
     65 
     66 	bool OnHelp(CommandSource &source, const Anope::string &) anope_override
     67 	{
     68 		this->SendSyntax(source);
     69 		source.Reply(" ");
     70 		source.Reply(_("Enables or disables the \002topic retention\002 option for a\n"
     71 				"channel. When \002%s\002 is set, the topic for the\n"
     72 				"channel will be remembered by %s even after the\n"
     73 				"last user leaves the channel, and will be restored the\n"
     74 				"next time the channel is created."), source.command.c_str(), source.service->nick.c_str());
     75 		return true;
     76 	}
     77 };
     78 
     79 class CommandCSTopic : public Command
     80 {
     81 	ExtensibleRef<bool> topiclock;
     82 
     83 	void Lock(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params)
     84 	{
     85 		if (Anope::ReadOnly)
     86 		{
     87 			source.Reply(READ_ONLY_MODE);
     88 			return;
     89 		}
     90 
     91 		EventReturn MOD_RESULT;
     92 		FOREACH_RESULT(OnSetChannelOption, MOD_RESULT, (source, this, ci, "topiclock on"));
     93 		if (MOD_RESULT == EVENT_STOP)
     94 			return;
     95 
     96 		topiclock->Set(ci, true);
     97 		source.Reply(_("Topic lock option for %s is now \002on\002."), ci->name.c_str());
     98 	}
     99 
    100 	void Unlock(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params)
    101 	{
    102 		if (Anope::ReadOnly)
    103 		{
    104 			source.Reply(READ_ONLY_MODE);
    105 			return;
    106 		}
    107 
    108 		EventReturn MOD_RESULT;
    109 		FOREACH_RESULT(OnSetChannelOption, MOD_RESULT, (source, this, ci, "topiclock off"));
    110 		if (MOD_RESULT == EVENT_STOP)
    111 			return;
    112 
    113 		topiclock->Unset(ci);
    114 		source.Reply(_("Topic lock option for %s is now \002off\002."), ci->name.c_str());
    115 	}
    116 
    117 	void Set(CommandSource &source, ChannelInfo *ci, const Anope::string &topic)
    118 	{
    119 		bool has_topiclock = topiclock->HasExt(ci);
    120 		topiclock->Unset(ci);
    121 		ci->c->ChangeTopic(source.GetNick(), topic, Anope::CurTime);
    122 		if (has_topiclock)
    123 			topiclock->Set(ci);
    124 
    125 		bool override = !source.AccessFor(ci).HasPriv("TOPIC");
    126 		Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << (!topic.empty() ? "to change the topic to: " : "to unset the topic") << (!topic.empty() ? topic : "");
    127 	}
    128 
    129 	void Append(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params)
    130 	{
    131 		const Anope::string &topic = params[2];
    132 
    133 		Anope::string new_topic;
    134 		if (!ci->c->topic.empty())
    135 		{
    136 			new_topic = ci->c->topic + " " + topic;
    137 			ci->last_topic.clear();
    138 		}
    139 		else
    140 			new_topic = topic;
    141 
    142 		this->Set(source, ci, new_topic);
    143 	}
    144 
    145  public:
    146 	CommandCSTopic(Module *creator) : Command(creator, "chanserv/topic", 2, 3),
    147 		topiclock("TOPICLOCK")
    148 	{
    149 		this->SetDesc(_("Manipulate the topic of the specified channel"));
    150 		this->SetSyntax(_("\037channel\037 [SET] [\037topic\037]"));
    151 		this->SetSyntax(_("\037channel\037 APPEND \037topic\037"));
    152 		this->SetSyntax(_("\037channel\037 [UNLOCK|LOCK]"));
    153 	}
    154 
    155 	void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
    156 	{
    157 		const Anope::string &subcmd = params[1];
    158 
    159 		ChannelInfo *ci = ChannelInfo::Find(params[0]);
    160 		if (ci == NULL)
    161 			source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
    162 		else if (!source.AccessFor(ci).HasPriv("TOPIC") && !source.HasCommand("chanserv/topic"))
    163 			source.Reply(ACCESS_DENIED);
    164 		else if (subcmd.equals_ci("LOCK"))
    165 			this->Lock(source, ci, params);
    166 		else if (subcmd.equals_ci("UNLOCK"))
    167 			this->Unlock(source, ci, params);
    168 		else if (!ci->c)
    169 			source.Reply(CHAN_X_NOT_IN_USE, ci->name.c_str());
    170 		else if (subcmd.equals_ci("APPEND") && params.size() > 2)
    171 			this->Append(source, ci, params);
    172 		else
    173 		{
    174 			Anope::string topic;
    175 			if (subcmd.equals_ci("SET"))
    176 			{
    177 				topic = params.size() > 2 ? params[2] : "";
    178 			}
    179 			else
    180 			{
    181 				topic = subcmd;
    182 				if (params.size() > 2)
    183 					topic += " " + params[2];
    184 			}
    185 			this->Set(source, ci, topic);
    186 		}
    187 	}
    188 
    189 	bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
    190 	{
    191 		this->SendSyntax(source);
    192 		source.Reply(" ");
    193 		source.Reply(_("Allows manipulating the topic of the specified channel.\n"
    194 				"The \002SET\002 command changes the topic of the channel to the given topic\n"
    195 				"or unsets the topic if no topic is given. The \002APPEND\002 command appends\n"
    196 				"the given topic to the existing topic.\n"
    197 				" \n"
    198 				"\002LOCK\002 and \002UNLOCK\002 may be used to enable and disable topic lock. When\n"
    199 				"topic lock is set, the channel topic will be unchangeable by users who do not have\n"
    200 				"the \002TOPIC\002 privilege."));
    201 		return true;
    202 	}
    203 };
    204 
    205 class CSTopic : public Module
    206 {
    207 	CommandCSTopic commandcstopic;
    208 	CommandCSSetKeepTopic commandcssetkeeptopic;
    209 
    210 	SerializableExtensibleItem<bool> topiclock, keeptopic;
    211 
    212  public:
    213 	CSTopic(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
    214 		commandcstopic(this), commandcssetkeeptopic(this), topiclock(this, "TOPICLOCK"), keeptopic(this, "KEEPTOPIC")
    215 	{
    216 
    217 	}
    218 
    219 	void OnChannelSync(Channel *c) anope_override
    220 	{
    221 		if (c->ci)
    222 		{
    223 			/* Update channel topic */
    224 			if ((topiclock.HasExt(c->ci) || keeptopic.HasExt(c->ci)) && c->ci->last_topic != c->topic)
    225 			{
    226 				c->ChangeTopic(!c->ci->last_topic_setter.empty() ? c->ci->last_topic_setter : c->ci->WhoSends()->nick, c->ci->last_topic, c->ci->last_topic_time ? c->ci->last_topic_time : Anope::CurTime);
    227 			}
    228 		}
    229 	}
    230 
    231 	void OnTopicUpdated(User *source, Channel *c, const Anope::string &user, const Anope::string &topic) anope_override
    232 	{
    233 		if (!c->ci)
    234 			return;
    235 
    236 		/* We only compare the topics here, not the time or setter. This is because some (old) IRCds do not
    237 		 * allow us to set the topic as someone else, meaning we have to bump the TS and change the setter to us.
    238 		 * This desyncs what is really set with what we have stored, and we end up resetting the topic often when
    239 		 * it is not required
    240 		 */
    241 		if (topiclock.HasExt(c->ci) && c->ci->last_topic != c->topic && (!source || !c->ci->AccessFor(source).HasPriv("TOPIC")))
    242 		{
    243 			c->ChangeTopic(c->ci->last_topic_setter, c->ci->last_topic, c->ci->last_topic_time);
    244 		}
    245 		else
    246 		{
    247 			c->ci->last_topic = c->topic;
    248 			c->ci->last_topic_setter = c->topic_setter;
    249 			c->ci->last_topic_time = c->topic_ts;
    250 		}
    251 	}
    252 
    253 	void OnChanInfo(CommandSource &source, ChannelInfo *ci, InfoFormatter &info, bool show_all) anope_override
    254 	{
    255 		if (keeptopic.HasExt(ci))
    256 			info.AddOption(_("Topic retention"));
    257 		if (topiclock.HasExt(ci))
    258 			info.AddOption(_("Topic lock"));
    259 
    260 		ModeLocks *ml = ci->GetExt<ModeLocks>("modelocks");
    261 		const ModeLock *secret = ml ? ml->GetMLock("SECRET") : NULL;
    262 		if (!ci->last_topic.empty() && (show_all || ((!secret || secret->set == false) && (!ci->c || !ci->c->HasMode("SECRET")))))
    263 		{
    264 			info[_("Last topic")] = ci->last_topic;
    265 			info[_("Topic set by")] = ci->last_topic_setter;
    266 		}
    267 	}
    268 };
    269 
    270 MODULE_INIT(CSTopic)