/* * Copyright (C) 2004-2013 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include using std::vector; using std::stringstream; class CAlias { private: CModule* parent; CString name; VCString alias_cmds; public: // getters/setters const CString& GetName() const { return name; } // name should be a single, all uppercase word void SetName(const CString& newname) { name = newname.Token(0, false, " "); name.MakeUpper(); } // combined getter/setter for command list VCString& AliasCmds() { return alias_cmds; } // check registry if alias exists static bool AliasExists(CModule* module, CString alias_name) { alias_name = alias_name.Token(0, false, " ").MakeUpper(); return (module->FindNV(alias_name) != module->EndNV()); } // populate alias from stored settings in registry, or return false if none // exists static bool AliasGet(CAlias& alias, CModule* module, CString line) { line = line.Token(0, false, " ").MakeUpper(); MCString::iterator i = module->FindNV(line); if (i == module->EndNV()) return false; alias.parent = module; alias.name = line; i->second.Split("\n", alias.alias_cmds, false); return true; } // constructors CAlias() : parent(nullptr) {} CAlias(CModule* new_parent, const CString& new_name) : parent(new_parent) { SetName(new_name); } // produce a command string from this alias' command list CString GetCommands() const { return CString("\n").Join(alias_cmds.begin(), alias_cmds.end()); } // write this alias to registry void Commit() const { if (!parent) return; parent->SetNV(name, GetCommands()); } // delete this alias from regisrty void Delete() const { if (!parent) return; parent->DelNV(name); } private: // this function helps imprint out. it checks if there is a substitution // token at 'caret' in 'alias_data' // and if it finds one, pulls the appropriate token out of 'line' and // appends it to 'output', and updates 'caret'. // 'skip' is updated based on the logic that we should skip the % at the // caret if we fail to parse the token. static void ParseToken(const CString& alias_data, const CString& line, CString& output, size_t& caret, size_t& skip) { bool optional = false; bool subsequent = false; size_t index = caret + 1; int token = -1; skip = 1; if (alias_data.length() > index && alias_data[index] == '?') { optional = true; ++index; } // try to read optional flag if (alias_data.length() > index && CString(alias_data.substr(index)) .Convert(&token)) // try to read integer { while (alias_data.length() > index && alias_data[index] >= '0' && alias_data[index] <= '9') ++index; // skip any numeric digits in string (supposed to fail if // whitespace precedes integer) } else { // token was malformed. leave caret unchanged, and flag first // character for skipping return; } if (alias_data.length() > index && alias_data[index] == '+') { subsequent = true; ++index; } // try to read subsequent flag if (alias_data.length() > index && alias_data[index] == '%') { ++index; } // try to read end-of-substitution marker else return; // if we get here, we're definitely dealing with a token, so get the // token's value CString stok = line.Token(token, subsequent, " "); if (stok.empty() && !optional) throw std::invalid_argument( CString("missing required parameter: ") + CString(token)); // blow up if token is required and also empty output.append(stok); // write token value to output skip = 0; // since we're moving the cursor after the end of the token, // skip no characters caret = index; // advance the cursor forward by the size of the token } public: // read an IRC line and do token substitution // throws an exception if a required parameter is missing, and might also // throw if you manage to make it bork CString Imprint(CString line) const { CString output; CString alias_data = GetCommands(); alias_data = parent->ExpandString(alias_data); size_t lastfound = 0, skip = 0; // it would be very inefficient to attempt to blindly replace every // possible token // so let's just parse the line and replace when we find them // token syntax: // %[?]n[+]% // adding ? makes the substitution optional (you'll get "" if there are // insufficient tokens, otherwise the alias will fail) // adding + makes the substitution contain all tokens from the nth to // the end of the line while (true) { // if (found >= (int) alias_data.length()) break; // ^ shouldn't be possible. size_t found = alias_data.find("%", lastfound + skip); if (found == CString::npos) break; // if we found nothing, break // capture everything between the last stopping point and here output.append(alias_data.substr(lastfound, found - lastfound)); // attempt to read a token, updates indices based on // success/failure ParseToken(alias_data, line, output, found, skip); lastfound = found; } output += alias_data.substr(lastfound); // append from the final return output; } }; class CAliasMod : public CModule { private: bool sending_lines; public: void CreateCommand(const CString& sLine) { CString name = sLine.Token(1, false, " "); if (!CAlias::AliasExists(this, name)) { CAlias na(this, name); na.Commit(); PutModule("Created alias: " + na.GetName()); } else PutModule("Alias already exists."); } void DeleteCommand(const CString& sLine) { CString name = sLine.Token(1, false, " "); CAlias delete_alias; if (CAlias::AliasGet(delete_alias, this, name)) { PutModule("Deleted alias: " + delete_alias.GetName()); delete_alias.Delete(); } else PutModule("Alias does not exist."); } void AddCmd(const CString& sLine) { CString name = sLine.Token(1, false, " "); CAlias add_alias; if (CAlias::AliasGet(add_alias, this, name)) { add_alias.AliasCmds().push_back(sLine.Token(2, true, " ")); add_alias.Commit(); PutModule("Modified alias."); } else PutModule("Alias does not exist."); } void InsertCommand(const CString& sLine) { CString name = sLine.Token(1, false, " "); CAlias insert_alias; int index; if (CAlias::AliasGet(insert_alias, this, name)) { // if Convert succeeds, then i has been successfully read from user // input if (!sLine.Token(2, false, " ").Convert(&index) || index < 0 || index > (int)insert_alias.AliasCmds().size()) { PutModule("Invalid index."); return; } insert_alias.AliasCmds().insert( insert_alias.AliasCmds().begin() + index, sLine.Token(3, true, " ")); insert_alias.Commit(); PutModule("Modified alias."); } else PutModule("Alias does not exist."); } void RemoveCommand(const CString& sLine) { CString name = sLine.Token(1, false, " "); CAlias remove_alias; int index; if (CAlias::AliasGet(remove_alias, this, name)) { if (!sLine.Token(2, false, " ").Convert(&index) || index < 0 || index > (int)remove_alias.AliasCmds().size() - 1) { PutModule("Invalid index."); return; } remove_alias.AliasCmds().erase(remove_alias.AliasCmds().begin() + index); remove_alias.Commit(); PutModule("Modified alias."); } else PutModule("Alias does not exist."); } void ClearCommand(const CString& sLine) { CString name = sLine.Token(1, false, " "); CAlias clear_alias; if (CAlias::AliasGet(clear_alias, this, name)) { clear_alias.AliasCmds().clear(); clear_alias.Commit(); PutModule("Modified alias."); } else PutModule("Alias does not exist."); } void ListCommand(const CString& sLine) { CString output = "The following aliases exist:"; MCString::iterator i = BeginNV(); if (i == EndNV()) output += " [none]"; for (; i != EndNV(); ++i) { output.append(" "); output.append(i->first); } PutModule(output); } void DumpCommand(const CString& sLine) { MCString::iterator i = BeginNV(); if (i == EndNV()) { PutModule("There are no aliases."); return; } PutModule("-----------------------"); PutModule("/ZNC-CLEAR-ALL-ALIASES!"); for (; i != EndNV(); ++i) { PutModule("/msg " + GetModNick() + " Create " + i->first); if (!i->second.empty()) { VCString it; uint idx; i->second.Split("\n", it); for (idx = 0; idx < it.size(); ++idx) { PutModule("/msg " + GetModNick() + " Add " + i->first + " " + it[idx]); } } } PutModule("-----------------------"); } void InfoCommand(const CString& sLine) { CString name = sLine.Token(1, false, " "); CAlias info_alias; if (CAlias::AliasGet(info_alias, this, name)) { PutModule("Actions for alias " + info_alias.GetName() + ":"); for (size_t i = 0; i < info_alias.AliasCmds().size(); ++i) { CString num(i); CString padding(4 - (num.length() > 3 ? 3 : num.length()), ' '); PutModule(num + padding + info_alias.AliasCmds()[i]); } PutModule("End of actions for alias " + info_alias.GetName() + "."); } else PutModule("Alias does not exist."); } MODCONSTRUCTOR(CAliasMod), sending_lines(false) { AddHelpCommand(); AddCommand("Create", static_cast( &CAliasMod::CreateCommand), "", "Creates a new, blank alias called name."); AddCommand("Delete", static_cast( &CAliasMod::DeleteCommand), "", "Deletes an existing alias."); AddCommand("Add", static_cast(&CAliasMod::AddCmd), " ", "Adds a line to an existing alias."); AddCommand("Insert", static_cast( &CAliasMod::InsertCommand), " ", "Inserts a line into an existing alias."); AddCommand("Remove", static_cast( &CAliasMod::RemoveCommand), " ", "Removes a line from an existing alias."); AddCommand("Clear", static_cast( &CAliasMod::ClearCommand), "", "Removes all line from an existing alias."); AddCommand("List", static_cast( &CAliasMod::ListCommand), "", "Lists all aliases by name."); AddCommand("Info", static_cast( &CAliasMod::InfoCommand), "", "Reports the actions performed by an alias."); AddCommand( "Dump", static_cast(&CAliasMod::DumpCommand), "", "Generate a list of commands to copy your alias config."); } EModRet OnUserRaw(CString& sLine) override { CAlias current_alias; if (sending_lines) return CONTINUE; try { if (sLine.Equals("ZNC-CLEAR-ALL-ALIASES!")) { ListCommand(""); PutModule("Clearing all of them!"); ClearNV(); return HALT; } else if (CAlias::AliasGet(current_alias, this, sLine)) { VCString rawLines; current_alias.Imprint(sLine).Split("\n", rawLines, false); sending_lines = true; for (size_t i = 0; i < rawLines.size(); ++i) { m_pClient->ReadLine(rawLines[i]); } sending_lines = false; return HALT; } } catch (std::exception& e) { CString my_nick = (GetNetwork() == nullptr ? "" : GetNetwork()->GetCurNick()); if (my_nick.empty()) my_nick = "*"; PutUser(CString(":znc.in 461 " + my_nick + " " + current_alias.GetName() + " :ZNC alias error: ") + e.what()); return HALTCORE; } return CONTINUE; } }; template <> void TModInfo(CModInfo& Info) { Info.SetWikiPage("alias"); Info.AddType(CModInfo::NetworkModule); } USERMODULEDEFS(CAliasMod, "Provides bouncer-side command alias support.")