mirror of
https://github.com/znc/znc.git
synced 2026-07-05 01:11:53 +02:00
Add clang-format configuration.
For now, it uses tabs like before, to make the diff easier to read/check. One of following commits will switch it to spaces.
This commit is contained in:
+180
-159
@@ -25,43 +25,34 @@
|
||||
using std::vector;
|
||||
using std::stringstream;
|
||||
|
||||
class CAlias
|
||||
{
|
||||
private:
|
||||
CModule *parent;
|
||||
class CAlias {
|
||||
private:
|
||||
CModule* parent;
|
||||
CString name;
|
||||
VCString alias_cmds;
|
||||
|
||||
public:
|
||||
public:
|
||||
// getters/setters
|
||||
const CString &GetName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
const CString& GetName() const { return name; }
|
||||
|
||||
// name should be a single, all uppercase word
|
||||
void SetName(const CString &newname)
|
||||
{
|
||||
void SetName(const CString& newname) {
|
||||
name = newname.Token(0, false, " ");
|
||||
name.MakeUpper();
|
||||
}
|
||||
|
||||
// combined getter/setter for command list
|
||||
VCString &AliasCmds()
|
||||
{
|
||||
return alias_cmds;
|
||||
}
|
||||
VCString& AliasCmds() { return alias_cmds; }
|
||||
|
||||
// check registry if alias exists
|
||||
static bool AliasExists(CModule *module, CString alias_name)
|
||||
{
|
||||
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)
|
||||
{
|
||||
// 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;
|
||||
@@ -73,34 +64,36 @@ public:
|
||||
|
||||
// constructors
|
||||
CAlias() : parent(nullptr) {}
|
||||
CAlias(CModule *new_parent, const CString &new_name) : parent(new_parent) { SetName(new_name); }
|
||||
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
|
||||
{
|
||||
CString GetCommands() const {
|
||||
return CString("\n").Join(alias_cmds.begin(), alias_cmds.end());
|
||||
}
|
||||
|
||||
// write this alias to registry
|
||||
void Commit() const
|
||||
{
|
||||
void Commit() const {
|
||||
if (!parent) return;
|
||||
parent->SetNV(name, GetCommands());
|
||||
}
|
||||
|
||||
// delete this alias from regisrty
|
||||
void Delete() const
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
@@ -108,244 +101,271 @@ private:
|
||||
|
||||
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
|
||||
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;
|
||||
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
|
||||
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
|
||||
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
|
||||
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:
|
||||
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
|
||||
{
|
||||
// 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
|
||||
// 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.
|
||||
// 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
|
||||
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
|
||||
output += alias_data.substr(lastfound); // append from the final
|
||||
return output;
|
||||
}
|
||||
};
|
||||
|
||||
class CAliasMod : public CModule {
|
||||
private:
|
||||
private:
|
||||
bool sending_lines;
|
||||
|
||||
public:
|
||||
void CreateCommand(const CString& sLine)
|
||||
{
|
||||
public:
|
||||
void CreateCommand(const CString& sLine) {
|
||||
CString name = sLine.Token(1, false, " ");
|
||||
if (!CAlias::AliasExists(this, name))
|
||||
{
|
||||
if (!CAlias::AliasExists(this, name)) {
|
||||
CAlias na(this, name);
|
||||
na.Commit();
|
||||
PutModule("Created alias: " + na.GetName());
|
||||
}
|
||||
else PutModule("Alias already exists.");
|
||||
} else
|
||||
PutModule("Alias already exists.");
|
||||
}
|
||||
|
||||
void DeleteCommand(const CString& sLine)
|
||||
{
|
||||
void DeleteCommand(const CString& sLine) {
|
||||
CString name = sLine.Token(1, false, " ");
|
||||
CAlias delete_alias;
|
||||
if (CAlias::AliasGet(delete_alias, this, name))
|
||||
{
|
||||
if (CAlias::AliasGet(delete_alias, this, name)) {
|
||||
PutModule("Deleted alias: " + delete_alias.GetName());
|
||||
delete_alias.Delete();
|
||||
}
|
||||
else PutModule("Alias does not exist.");
|
||||
} else
|
||||
PutModule("Alias does not exist.");
|
||||
}
|
||||
|
||||
void AddCmd(const CString& sLine)
|
||||
{
|
||||
void AddCmd(const CString& sLine) {
|
||||
CString name = sLine.Token(1, false, " ");
|
||||
CAlias add_alias;
|
||||
if (CAlias::AliasGet(add_alias, this, name))
|
||||
{
|
||||
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.");
|
||||
} else
|
||||
PutModule("Alias does not exist.");
|
||||
}
|
||||
|
||||
void InsertCommand(const CString& sLine)
|
||||
{
|
||||
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())
|
||||
{
|
||||
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.AliasCmds().insert(
|
||||
insert_alias.AliasCmds().begin() + index,
|
||||
sLine.Token(3, true, " "));
|
||||
insert_alias.Commit();
|
||||
PutModule("Modified alias.");
|
||||
}
|
||||
else PutModule("Alias does not exist.");
|
||||
} else
|
||||
PutModule("Alias does not exist.");
|
||||
}
|
||||
|
||||
void RemoveCommand(const CString& sLine)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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.AliasCmds().erase(remove_alias.AliasCmds().begin() +
|
||||
index);
|
||||
remove_alias.Commit();
|
||||
PutModule("Modified alias.");
|
||||
}
|
||||
else PutModule("Alias does not exist.");
|
||||
} else
|
||||
PutModule("Alias does not exist.");
|
||||
}
|
||||
|
||||
void ClearCommand(const CString& sLine)
|
||||
{
|
||||
void ClearCommand(const CString& sLine) {
|
||||
CString name = sLine.Token(1, false, " ");
|
||||
CAlias clear_alias;
|
||||
if (CAlias::AliasGet(clear_alias, this, name))
|
||||
{
|
||||
if (CAlias::AliasGet(clear_alias, this, name)) {
|
||||
clear_alias.AliasCmds().clear();
|
||||
clear_alias.Commit();
|
||||
PutModule("Modified alias.");
|
||||
}
|
||||
else PutModule("Alias does not exist.");
|
||||
} else
|
||||
PutModule("Alias does not exist.");
|
||||
}
|
||||
|
||||
void ListCommand(const CString& sLine)
|
||||
{
|
||||
void ListCommand(const CString& sLine) {
|
||||
CString output = "The following aliases exist:";
|
||||
MCString::iterator i = BeginNV();
|
||||
if (i == EndNV()) output += " [none]";
|
||||
for (; i != EndNV(); ++i)
|
||||
{
|
||||
for (; i != EndNV(); ++i) {
|
||||
output.append(" ");
|
||||
output.append(i->first);
|
||||
}
|
||||
PutModule(output);
|
||||
}
|
||||
|
||||
void DumpCommand(const CString& sLine)
|
||||
{
|
||||
void DumpCommand(const CString& sLine) {
|
||||
MCString::iterator i = BeginNV();
|
||||
|
||||
if (i == EndNV()) {
|
||||
|
||||
if (i == EndNV()) {
|
||||
PutModule("There are no aliases.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
PutModule("-----------------------");
|
||||
PutModule("/ZNC-CLEAR-ALL-ALIASES!");
|
||||
for (; i != EndNV(); ++i)
|
||||
{
|
||||
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]);
|
||||
}
|
||||
|
||||
for (idx = 0; idx < it.size(); ++idx) {
|
||||
PutModule("/msg " + GetModNick() + " Add " + i->first +
|
||||
" " + it[idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
PutModule("-----------------------");
|
||||
}
|
||||
|
||||
void InfoCommand(const CString& sLine)
|
||||
{
|
||||
void InfoCommand(const CString& sLine) {
|
||||
CString name = sLine.Token(1, false, " ");
|
||||
CAlias info_alias;
|
||||
if (CAlias::AliasGet(info_alias, this, name))
|
||||
{
|
||||
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)
|
||||
{
|
||||
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.");
|
||||
} else
|
||||
PutModule("Alias does not exist.");
|
||||
}
|
||||
|
||||
MODCONSTRUCTOR(CAliasMod),
|
||||
sending_lines(false)
|
||||
{
|
||||
MODCONSTRUCTOR(CAliasMod), sending_lines(false) {
|
||||
AddHelpCommand();
|
||||
AddCommand("Create", static_cast<CModCommand::ModCmdFunc>(&CAliasMod::CreateCommand), "<name>", "Creates a new, blank alias called name.");
|
||||
AddCommand("Delete", static_cast<CModCommand::ModCmdFunc>(&CAliasMod::DeleteCommand), "<name>", "Deletes an existing alias.");
|
||||
AddCommand("Add", static_cast<CModCommand::ModCmdFunc>(&CAliasMod::AddCmd), "<name> <action ...>", "Adds a line to an existing alias.");
|
||||
AddCommand("Insert", static_cast<CModCommand::ModCmdFunc>(&CAliasMod::InsertCommand), "<name> <pos> <action ...>", "Inserts a line into an existing alias.");
|
||||
AddCommand("Remove", static_cast<CModCommand::ModCmdFunc>(&CAliasMod::RemoveCommand), "<name> <pos>", "Removes a line from an existing alias.");
|
||||
AddCommand("Clear", static_cast<CModCommand::ModCmdFunc>(&CAliasMod::ClearCommand), "<name>", "Removes all line from an existing alias.");
|
||||
AddCommand("List", static_cast<CModCommand::ModCmdFunc>(&CAliasMod::ListCommand), "", "Lists all aliases by name.");
|
||||
AddCommand("Info", static_cast<CModCommand::ModCmdFunc>(&CAliasMod::InfoCommand), "<name>", "Reports the actions performed by an alias.");
|
||||
AddCommand("Dump", static_cast<CModCommand::ModCmdFunc>(&CAliasMod::DumpCommand), "", "Generate a list of commands to copy your alias config.");
|
||||
|
||||
AddCommand("Create", static_cast<CModCommand::ModCmdFunc>(
|
||||
&CAliasMod::CreateCommand),
|
||||
"<name>", "Creates a new, blank alias called name.");
|
||||
AddCommand("Delete", static_cast<CModCommand::ModCmdFunc>(
|
||||
&CAliasMod::DeleteCommand),
|
||||
"<name>", "Deletes an existing alias.");
|
||||
AddCommand("Add",
|
||||
static_cast<CModCommand::ModCmdFunc>(&CAliasMod::AddCmd),
|
||||
"<name> <action ...>", "Adds a line to an existing alias.");
|
||||
AddCommand("Insert", static_cast<CModCommand::ModCmdFunc>(
|
||||
&CAliasMod::InsertCommand),
|
||||
"<name> <pos> <action ...>",
|
||||
"Inserts a line into an existing alias.");
|
||||
AddCommand("Remove", static_cast<CModCommand::ModCmdFunc>(
|
||||
&CAliasMod::RemoveCommand),
|
||||
"<name> <pos>", "Removes a line from an existing alias.");
|
||||
AddCommand("Clear", static_cast<CModCommand::ModCmdFunc>(
|
||||
&CAliasMod::ClearCommand),
|
||||
"<name>", "Removes all line from an existing alias.");
|
||||
AddCommand("List", static_cast<CModCommand::ModCmdFunc>(
|
||||
&CAliasMod::ListCommand),
|
||||
"", "Lists all aliases by name.");
|
||||
AddCommand("Info", static_cast<CModCommand::ModCmdFunc>(
|
||||
&CAliasMod::InfoCommand),
|
||||
"<name>", "Reports the actions performed by an alias.");
|
||||
AddCommand(
|
||||
"Dump",
|
||||
static_cast<CModCommand::ModCmdFunc>(&CAliasMod::DumpCommand), "",
|
||||
"Generate a list of commands to copy your alias config.");
|
||||
}
|
||||
|
||||
EModRet OnUserRaw(CString& sLine) override
|
||||
{
|
||||
EModRet OnUserRaw(CString& sLine) override {
|
||||
CAlias current_alias;
|
||||
|
||||
if (sending_lines) return CONTINUE;
|
||||
|
||||
try
|
||||
{
|
||||
if (sLine.Equals("ZNC-CLEAR-ALL-ALIASES!"))
|
||||
{
|
||||
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))
|
||||
{
|
||||
} else if (CAlias::AliasGet(current_alias, this, sLine)) {
|
||||
VCString rawLines;
|
||||
current_alias.Imprint(sLine).Split("\n", rawLines, false);
|
||||
sending_lines = true;
|
||||
@@ -357,12 +377,13 @@ public:
|
||||
sending_lines = false;
|
||||
return HALT;
|
||||
}
|
||||
}
|
||||
catch (std::exception &e)
|
||||
{
|
||||
CString my_nick = (GetNetwork() == nullptr ? "" : GetNetwork()->GetCurNick());
|
||||
} 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());
|
||||
PutUser(CString(":znc.in 461 " + my_nick + " " +
|
||||
current_alias.GetName() + " :ZNC alias error: ") +
|
||||
e.what());
|
||||
return HALTCORE;
|
||||
}
|
||||
|
||||
@@ -370,10 +391,10 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
template<> void TModInfo<CAliasMod>(CModInfo& Info) {
|
||||
template <>
|
||||
void TModInfo<CAliasMod>(CModInfo& Info) {
|
||||
Info.SetWikiPage("alias");
|
||||
Info.AddType(CModInfo::NetworkModule);
|
||||
}
|
||||
|
||||
USERMODULEDEFS(CAliasMod, "Provides bouncer-side command alias support.")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user