forked from Wirlaburla/P3NG0
stuff
This commit is contained in:
parent
fb666f1d92
commit
ce204d0f8f
|
@ -1,7 +1,10 @@
|
||||||
cmake_minimum_required(VERSION 3.5)
|
cmake_minimum_required(VERSION 3.5)
|
||||||
project(pengobot)
|
project(pengobot)
|
||||||
|
add_definitions(-DCURL_STATICLIB)
|
||||||
SET(CMAKE_CXX_STANDARD 17)
|
SET(CMAKE_CXX_STANDARD 17)
|
||||||
SET(CMAKE_CXX_STANDARD_REQUIRED True)
|
SET(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||||
SET(CMAKE_CXX_FLAGS "-O3")
|
SET(CMAKE_CXX_FLAGS "-O3")
|
||||||
|
find_package(CURL REQUIRED)
|
||||||
|
include_directories(${CURL_INCLUDE_DIR})
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
install(TARGETS pengobot RUNTIME DESTINATION bin)
|
install(TARGETS pengobot RUNTIME DESTINATION bin)
|
||||||
|
|
81
conf.examples/filth.list
Normal file
81
conf.examples/filth.list
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
masturbater
|
||||||
|
masturbation
|
||||||
|
jerking
|
||||||
|
blowjob
|
||||||
|
asshole
|
||||||
|
cunt
|
||||||
|
b1tch
|
||||||
|
slut
|
||||||
|
pussy
|
||||||
|
nigga
|
||||||
|
fukk
|
||||||
|
lesbian
|
||||||
|
dildo
|
||||||
|
whore
|
||||||
|
dick
|
||||||
|
phuck
|
||||||
|
fuck
|
||||||
|
clit
|
||||||
|
cock
|
||||||
|
penis
|
||||||
|
bitch
|
||||||
|
fucking
|
||||||
|
pussyeaster
|
||||||
|
cuntfucker
|
||||||
|
eatmypussy
|
||||||
|
NIGGER
|
||||||
|
shit
|
||||||
|
fag
|
||||||
|
fuk
|
||||||
|
fuc
|
||||||
|
nigga
|
||||||
|
cocksucker
|
||||||
|
pussy
|
||||||
|
asshole
|
||||||
|
lezbo
|
||||||
|
cunt
|
||||||
|
clit
|
||||||
|
bitch
|
||||||
|
fucking
|
||||||
|
cumeater
|
||||||
|
dick
|
||||||
|
pussyeater
|
||||||
|
cuntfucker
|
||||||
|
eatmypussy
|
||||||
|
nigger
|
||||||
|
fuck
|
||||||
|
Fucking
|
||||||
|
Fucked
|
||||||
|
Fucker
|
||||||
|
Fuckface
|
||||||
|
Motherfucker
|
||||||
|
Cocksucker
|
||||||
|
Shit
|
||||||
|
Shithead
|
||||||
|
Shitter
|
||||||
|
Cunt
|
||||||
|
Bitch
|
||||||
|
Asshole
|
||||||
|
Prick
|
||||||
|
fuck
|
||||||
|
fucking
|
||||||
|
fucked
|
||||||
|
fucker
|
||||||
|
fuckface
|
||||||
|
motherfucker
|
||||||
|
cocksucker
|
||||||
|
shit
|
||||||
|
shithead
|
||||||
|
shitter
|
||||||
|
cunt
|
||||||
|
bitch
|
||||||
|
asshole
|
||||||
|
prick
|
||||||
|
d-i-c-k
|
||||||
|
sextape
|
||||||
|
kike
|
||||||
|
pornography
|
||||||
|
porno
|
||||||
|
porn
|
||||||
|
sex
|
||||||
|
xxx
|
|
@ -1,4 +1,4 @@
|
||||||
add_executable(pengobot
|
add_executable(pengobot
|
||||||
main.cpp
|
main.cpp
|
||||||
)
|
)
|
||||||
target_link_libraries(pengobot -static)
|
target_link_libraries(pengobot ${CURL_LIBRARIES} -static)
|
||||||
|
|
34
src/acfile.h
34
src/acfile.h
|
@ -16,7 +16,7 @@ public:
|
||||||
}
|
}
|
||||||
ACFile(const char* path) {
|
ACFile(const char* path) {
|
||||||
std::ifstream infile(path);
|
std::ifstream infile(path);
|
||||||
|
printf("info: reading file %s\n", path);
|
||||||
std::string line;
|
std::string line;
|
||||||
while (std::getline(infile, line)) {
|
while (std::getline(infile, line)) {
|
||||||
if (line.length() == 0) continue;
|
if (line.length() == 0) continue;
|
||||||
|
@ -30,9 +30,6 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map<std::string, std::string> get() {
|
std::map<std::string, std::string> get() {
|
||||||
for (auto const& c : this->conf) {
|
|
||||||
printf("info: (%s),(%s)\n", c.first.c_str(), c.second.c_str());
|
|
||||||
}
|
|
||||||
return this->conf;
|
return this->conf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +73,7 @@ public:
|
||||||
this->rng = std::mt19937(rd());
|
this->rng = std::mt19937(rd());
|
||||||
|
|
||||||
std::ifstream infile(path);
|
std::ifstream infile(path);
|
||||||
|
printf("info: reading file %s\n", path);
|
||||||
std::string line; std::string group;
|
std::string line; std::string group;
|
||||||
while (std::getline(infile, line)) {
|
while (std::getline(infile, line)) {
|
||||||
if (line.length() == 0) continue;
|
if (line.length() == 0) continue;
|
||||||
|
@ -113,5 +110,32 @@ private:
|
||||||
std::mt19937 rng;
|
std::mt19937 rng;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Another List File
|
||||||
|
class ALFile {
|
||||||
|
public:
|
||||||
|
ALFile() {};
|
||||||
|
ALFile(const ALFile &other) {
|
||||||
|
this->list = other.list;
|
||||||
|
}
|
||||||
|
ALFile(const char* path) {
|
||||||
|
std::ifstream infile(path);
|
||||||
|
printf("info: reading file %s\n", path);
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(infile, line)) {
|
||||||
|
if (line.length() == 0) continue;
|
||||||
|
// Check if line is a group defining line.
|
||||||
|
if (line.rfind("#", 0) != 0) {
|
||||||
|
list.push_back(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> getLines() {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::vector<std::string> list;
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -11,11 +11,13 @@ bool debug = false;
|
||||||
std::random_device rd;
|
std::random_device rd;
|
||||||
std::mt19937 rng(rd());
|
std::mt19937 rng(rd());
|
||||||
|
|
||||||
|
const std::string user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36";
|
||||||
std::string confFile = "conf/bot.conf";
|
std::string confFile = "conf/bot.conf";
|
||||||
ACFile* mainConf;
|
ACFile* mainConf;
|
||||||
ACFile* worldlist;
|
ACFile* worldlist;
|
||||||
ACFile* replylist;
|
ACFile* replylist;
|
||||||
AMFile* messages;
|
AMFile* messages;
|
||||||
|
ALFile* filth;
|
||||||
|
|
||||||
#define BUFFERSIZE 65535
|
#define BUFFERSIZE 65535
|
||||||
|
|
||||||
|
|
12
src/drone.h
12
src/drone.h
|
@ -1,5 +1,6 @@
|
||||||
#ifndef H_DRONE
|
#ifndef H_DRONE
|
||||||
#define H_DRONE
|
#define H_DRONE
|
||||||
|
#include "group.h"
|
||||||
|
|
||||||
class Drone {
|
class Drone {
|
||||||
public:
|
public:
|
||||||
|
@ -10,6 +11,17 @@ public:
|
||||||
Drone(char* &_name) : name{ _name } { };
|
Drone(char* &_name) : name{ _name } { };
|
||||||
bool operator != (Drone &d) { return strcmp(this->name, d.name) != 0; };
|
bool operator != (Drone &d) { return strcmp(this->name, d.name) != 0; };
|
||||||
Drone operator = (Drone *d) { return *d; };
|
Drone operator = (Drone *d) { return *d; };
|
||||||
|
void setGroup(Group* newGroup) {
|
||||||
|
activeGroup = newGroup;
|
||||||
|
}
|
||||||
|
Group* getCurrentGroup() {
|
||||||
|
return activeGroup;
|
||||||
|
}
|
||||||
|
bool inGroup() {
|
||||||
|
return activeGroup != nullptr;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
Group* activeGroup;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
72
src/main.cpp
72
src/main.cpp
|
@ -21,6 +21,9 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#define CURL_STATICLIB
|
||||||
|
#include <curl/curl.h>
|
||||||
|
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
#include "verrors.h"
|
#include "verrors.h"
|
||||||
#include "cmds.h"
|
#include "cmds.h"
|
||||||
|
@ -33,11 +36,11 @@ public:
|
||||||
QueuedPacket() {};
|
QueuedPacket() {};
|
||||||
QueuedPacket(const QueuedPacket &other) {
|
QueuedPacket(const QueuedPacket &other) {
|
||||||
this->sock = other.sock;
|
this->sock = other.sock;
|
||||||
memcpy(this->buf, other.buf, 255);
|
memcpy(this->buf, other.buf, other.buf[0]);
|
||||||
}
|
}
|
||||||
QueuedPacket(int *sock, unsigned char str[]) {
|
QueuedPacket(int *sock, unsigned char str[]) {
|
||||||
this->sock = *sock;
|
this->sock = *sock;
|
||||||
memcpy(this->buf, str, 255);
|
memcpy(this->buf, str, str[0]);
|
||||||
};
|
};
|
||||||
|
|
||||||
int flush(int flags) {
|
int flush(int flags) {
|
||||||
|
@ -82,6 +85,7 @@ void loadConfig() {
|
||||||
worldlist = new ACFile(mainConf->getValue("worldfile", "conf/worldlist.conf").c_str());
|
worldlist = new ACFile(mainConf->getValue("worldfile", "conf/worldlist.conf").c_str());
|
||||||
replylist = new ACFile(mainConf->getValue("replyfile", "conf/replies.conf").c_str());
|
replylist = new ACFile(mainConf->getValue("replyfile", "conf/replies.conf").c_str());
|
||||||
messages = new AMFile(mainConf->getValue("messages", "conf/messages.list").c_str());
|
messages = new AMFile(mainConf->getValue("messages", "conf/messages.list").c_str());
|
||||||
|
filth = new ALFile(mainConf->getValue("filthlist", "conf/filth.list").c_str());
|
||||||
login_username = mainConf->getValue("username", "");
|
login_username = mainConf->getValue("username", "");
|
||||||
login_password = mainConf->getValue("password", "");
|
login_password = mainConf->getValue("password", "");
|
||||||
room = mainConf->getValue("room", "GroundZero#Reception<dimension-1>");
|
room = mainConf->getValue("room", "GroundZero#Reception<dimension-1>");
|
||||||
|
@ -520,14 +524,16 @@ bool handleCommand(char* buffer, std::string from, std::string message) {
|
||||||
if (args.size() > 0) {
|
if (args.size() > 0) {
|
||||||
if (args[0] == "roll" && args.size() > 1) {
|
if (args[0] == "roll" && args.size() > 1) {
|
||||||
int dice = 1; int sides = 6; int roll = 0;
|
int dice = 1; int sides = 6; int roll = 0;
|
||||||
if (args.size() > 1) {
|
int darg = 1;
|
||||||
|
if (args[1] == "a") darg = 2;
|
||||||
|
if (args.size() > darg) {
|
||||||
try {
|
try {
|
||||||
int dinx = args[1].find("d");
|
int dinx = args[darg].find("d");
|
||||||
if (dinx > 0) {
|
if (dinx > 0) {
|
||||||
dice = std::stoi(args[1].substr(0, dinx));
|
dice = std::stoi(args[darg].substr(0, dinx));
|
||||||
}
|
}
|
||||||
sides = std::stoi(args[1].substr(dinx+1, args[1].length()));
|
sides = std::stoi(args[darg].substr(dinx+1, args[darg].length()));
|
||||||
} catch (const std::out_of_range& e) { }
|
} catch (...) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
std::uniform_int_distribution<int> rno(1,sides);
|
std::uniform_int_distribution<int> rno(1,sides);
|
||||||
|
@ -600,14 +606,6 @@ bool handlePhrase(char* buffer, std::string from, std::string message) {
|
||||||
sprintf(buffer, messages->getMessage("greets").c_str(), from.c_str());
|
sprintf(buffer, messages->getMessage("greets").c_str(), from.c_str());
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
std::string response;
|
|
||||||
if ((response = getValueOfIncludedName(replylist->get(), message)).length() > 0) {
|
|
||||||
sprintf(buffer, response.c_str());
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -616,22 +614,53 @@ void processText(int *sock, std::string username, std::string message) {
|
||||||
if (debug) printf("debug: received text from %s: \"%s\"\n", username.c_str(), message.c_str());
|
if (debug) printf("debug: received text from %s: \"%s\"\n", username.c_str(), message.c_str());
|
||||||
char *msgout = new char[BUFFERSIZE];
|
char *msgout = new char[BUFFERSIZE];
|
||||||
if (username.compare(login_username) != 0) {
|
if (username.compare(login_username) != 0) {
|
||||||
message = toLower(message); // Make it a lowercase string so we can work with it.
|
// We'll make a lowercase version so we can work with it without worrying about cases.
|
||||||
|
std::string lowermsg = toLower(message); // Assign message to lowermsg to make a copy.
|
||||||
int alen = 0;
|
int alen = 0;
|
||||||
// Someone has requested P3NG0s attention.
|
// Someone has requested P3NG0s attention.
|
||||||
// We'll accept some variations.
|
// We'll accept some variations.
|
||||||
if ((alen = vstrcontains(message, messages->getMessages("attention"))) > 0) {
|
if ((alen = vstrcontains(lowermsg, messages->getMessages("attention"))) > 0) {
|
||||||
// Strip out the attention. We got it.
|
// Strip out the attention. We got it.
|
||||||
message = message.substr(alen+1, message.length());
|
if (handleCommand(msgout, username, message.substr(alen+1, message.length()))) {
|
||||||
if (handleCommand(msgout, username, message)) {
|
|
||||||
printf("info: processed command\n");
|
printf("info: processed command\n");
|
||||||
} else if (handlePhrase(msgout, username, message)) {
|
} else if (handlePhrase(msgout, username, lowermsg.substr(alen+1, lowermsg.length()))) {
|
||||||
printf("info: processed phrase\n");
|
printf("info: processed phrase\n");
|
||||||
}
|
}
|
||||||
sendChatMessage(sock, std::string(msgout));
|
sendChatMessage(sock, std::string(msgout));
|
||||||
} else if (message == "ping") {
|
} else if (lowermsg == "ping") {
|
||||||
sprintf(msgout, mainConf->getValue("pong_msg", "Pong!").c_str());
|
sprintf(msgout, mainConf->getValue("pong_msg", "Pong!").c_str());
|
||||||
sendChatMessage(sock, std::string(msgout));
|
sendChatMessage(sock, std::string(msgout));
|
||||||
|
} else if (lowermsg.find("http") != std::string::npos) {
|
||||||
|
int pos;
|
||||||
|
if ((pos = lowermsg.find("http://")) != std::string::npos || (pos = lowermsg.find("https://")) != std::string::npos) {
|
||||||
|
std::string url = message.substr(pos, message.substr(pos, message.length()).find(" "));
|
||||||
|
if (debug) printf("debug: getting title for url \"%s\"\n", url.c_str());
|
||||||
|
if(CURL* curl = curl_easy_init()) {
|
||||||
|
std::string httpContents;
|
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||||
|
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent.c_str());
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &httpContents);
|
||||||
|
|
||||||
|
CURLcode res = curl_easy_perform(curl);
|
||||||
|
if(res != CURLE_OK) fprintf(stderr, "Error<cURL>: curl_easy_perform() failed > %s\n", curl_easy_strerror(res));
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
if (httpContents.length() > 0 && httpContents.find("<title>") != std::string::npos) {
|
||||||
|
std::string linktitle = httpContents.substr(httpContents.find("<title>")+7, httpContents.length());
|
||||||
|
linktitle = decodeHTML(filter(filth->getLines(), linktitle.substr(0, linktitle.find("<"))));
|
||||||
|
if (linktitle.length() > 0) {
|
||||||
|
if (linktitle.length() > 180) linktitle = linktitle.substr(0, 180)+"...";
|
||||||
|
if (debug) printf("debug: got title \"%s\"\n", linktitle.c_str());
|
||||||
|
sprintf(msgout, mainConf->getValue("link_msg", "[%s] Link: %s").c_str(), username.c_str(), linktitle.c_str());
|
||||||
|
sendChatMessage(sock, std::string(msgout));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -711,3 +740,4 @@ void qsend(int *sock, unsigned char str[], bool queue) {
|
||||||
if (queue) bufferQueue.push_back(*qp);
|
if (queue) bufferQueue.push_back(*qp);
|
||||||
else qp->flush(0);
|
else qp->flush(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
18
src/urlname.cpp
Normal file
18
src/urlname.cpp
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#include <iostream>
|
||||||
|
#define CURL_STATICLIB
|
||||||
|
#include <curl/curl.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#ifdef __linux__
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
size_t my_array_write(char *ptr, size_t size, size_t nmemb, void *userdata) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void getTitle(std::string url, const char* filename) {
|
||||||
|
|
||||||
|
}
|
53
src/utils.h
53
src/utils.h
|
@ -9,6 +9,11 @@
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
static size_t CurlWriteCallback(void *contents, size_t size, size_t nmemb, void *userp) {
|
||||||
|
((std::string*)userp)->append((char*)contents, size * nmemb);
|
||||||
|
return size * nmemb;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Out>
|
template <typename Out>
|
||||||
void split(const std::string &s, char delim, Out result) {
|
void split(const std::string &s, char delim, Out result) {
|
||||||
std::istringstream iss(s);
|
std::istringstream iss(s);
|
||||||
|
@ -24,6 +29,17 @@ std::vector<std::string> split(const std::string &s, char delim) {
|
||||||
return elems;
|
return elems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool strcontains(std::string needle, std::string haystack);
|
||||||
|
bool vfind(std::vector<std::string> v, std::string i);
|
||||||
|
int vstrcontains(std::string needle, std::vector<std::string> haystack);
|
||||||
|
std::string filter(std::vector<std::string> filterlist, std::string input);
|
||||||
|
std::string getValueOfIncludedName(std::map<std::string, std::string> list, std::string input);
|
||||||
|
std::string strcombine(std::vector<std::string> args, int start, int end, char pad);
|
||||||
|
static char* toLower(char* str);
|
||||||
|
static std::string toLower(std::string str);
|
||||||
|
static char* trim(char *str);
|
||||||
|
|
||||||
|
|
||||||
bool strcontains(std::string needle, std::string haystack) {
|
bool strcontains(std::string needle, std::string haystack) {
|
||||||
bool found = haystack.find(needle) != std::string::npos;
|
bool found = haystack.find(needle) != std::string::npos;
|
||||||
return found;
|
return found;
|
||||||
|
@ -40,6 +56,43 @@ int vstrcontains(std::string needle, std::vector<std::string> haystack) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string replaceAll(std::string input, std::string find, std::string replacement) {
|
||||||
|
int pos;
|
||||||
|
while ((pos = input.find(find)) != std::string::npos) {
|
||||||
|
input = input.replace(pos, find.length(), replacement);
|
||||||
|
}
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string filter(std::vector<std::string> filterlist, std::string input) {
|
||||||
|
for (std::string str : filterlist) {
|
||||||
|
int pos;
|
||||||
|
while ((pos = toLower(input).find(toLower(str))) != std::string::npos) {
|
||||||
|
std::string replacement;
|
||||||
|
for (int i = 0; i < str.length(); i++) replacement+="*";
|
||||||
|
input = input.replace(pos, str.length(), replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string decodeHTML(std::string in) {
|
||||||
|
int pos;
|
||||||
|
while ((pos = in.find("&#")) != std::string::npos) {
|
||||||
|
try {
|
||||||
|
char c = std::stoi(in.substr(pos+2, in.length()));
|
||||||
|
int endpos = in.substr(pos, in.length()).find(";");
|
||||||
|
if (endpos == std::string::npos) continue;
|
||||||
|
in = in.replace(pos, endpos+1, std::string(1, c));
|
||||||
|
} catch (...) { };
|
||||||
|
}
|
||||||
|
// We should be good to try and parse named encodes.
|
||||||
|
in = replaceAll(in, "&", "&");
|
||||||
|
in = replaceAll(in, "<", "<");
|
||||||
|
in = replaceAll(in, ">", ">");
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
|
||||||
std::string getValueOfIncludedName(std::map<std::string, std::string> list, std::string input) {
|
std::string getValueOfIncludedName(std::map<std::string, std::string> list, std::string input) {
|
||||||
for (auto v : list) {
|
for (auto v : list) {
|
||||||
if (strcontains(v.first, input)) return v.second;
|
if (strcontains(v.first, input)) return v.second;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user