diff --git a/modules/alias.cpp b/modules/alias.cpp new file mode 100644 index 00000000..05250662 --- /dev/null +++ b/modules/alias.cpp @@ -0,0 +1,335 @@ +/* + * 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(NULL) {} + 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 return; // token was malformed. leave caret unchanged, and flag first character for skipping + 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; + + CString stok = line.Token(token, subsequent, " "); // if we get here, we're definitely dealing with a token, so get the token's value + 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 + output.append(alias_data.substr(lastfound, found - lastfound)); // capture everything between the last stopping point and here + ParseToken(alias_data, line, output, found, skip); // attempt to read a token, updates indices based on success/failure + lastfound = found; + } + + output += alias_data.substr(lastfound); // append from the final + return output; + } +}; + +class CAliasMod : public CModule { +public: + using CModule::AddCommand; + + 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 AddCommand(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 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); + PutModule(CString(i) + (" " + ((num.length() > 3) ? 3 : num.length())) + info_alias.AliasCmds()[i]); + } + PutModule("End of actions for alias " + info_alias.GetName() + "."); + } + else PutModule("Alias does not exist."); + } + + MODCONSTRUCTOR(CAliasMod) + { + 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::AddCommand), " ", "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."); + } + + virtual EModRet OnUserRaw(CString& sLine) + { + CAlias current_alias; + 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); + for (size_t i = 0; i < rawLines.size(); ++i) PutIRC(rawLines[i]); + return HALT; + } + } + catch (std::exception &e) + { + PutUser(CString(":znc.in 461 " + GetNetwork()->GetCurNick() + " " + 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.") +