427 lines
20 KiB
PHP
427 lines
20 KiB
PHP
|
<?php
|
||
|
|
||
|
namespace sockchat\mods\core;
|
||
|
use \sockchat\mods\GenericMod;
|
||
|
|
||
|
use \sockchat\Context;
|
||
|
use \sockchat\User;
|
||
|
use \sockchat\Utils;
|
||
|
use \sockchat\Message;
|
||
|
use \sockchat\Channel;
|
||
|
|
||
|
class Stack {
|
||
|
protected $size;
|
||
|
protected $stack;
|
||
|
|
||
|
public function __construct($size, $copy = null) {
|
||
|
$this->size = $size;
|
||
|
if($copy != null) {
|
||
|
if($copy->Size() <= $this->size) $this->stack = $copy->Raw();
|
||
|
else $this->stack = array_slice($copy->Raw(), $copy->Size()-$this->size);
|
||
|
} else $this->stack = [];
|
||
|
}
|
||
|
|
||
|
public function Push($val, $id = -1) {
|
||
|
if($id == -1)
|
||
|
array_push($this->stack, $val);
|
||
|
else
|
||
|
$this->stack[$id] = $val;
|
||
|
if(count($this->stack) > $this->size)
|
||
|
$this->stack = array_slice($this->stack, 1);
|
||
|
}
|
||
|
|
||
|
public function Pop() {
|
||
|
return array_pop($this->stack);
|
||
|
}
|
||
|
|
||
|
public function Top() {
|
||
|
return ($this->stack == []) ? null : $this->stack[count($this->stack)-1];
|
||
|
}
|
||
|
|
||
|
public function Bottom() {
|
||
|
return ($this->stack == []) ? null : $this->stack[0];
|
||
|
}
|
||
|
|
||
|
public function MaxSize() {
|
||
|
return $this->size;
|
||
|
}
|
||
|
|
||
|
public function Size() {
|
||
|
return count($this->stack);
|
||
|
}
|
||
|
|
||
|
public function Full() {
|
||
|
return $this->size == count($this->stack);
|
||
|
}
|
||
|
|
||
|
public function Raw() {
|
||
|
return $this->stack;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class Main extends GenericMod {
|
||
|
protected static $silencedUsers;
|
||
|
protected static $allowedCmds = ["join", "whois", "afk"];
|
||
|
|
||
|
protected static $maxAfkTagLength = 5;
|
||
|
protected static $floodFilterSize = 30;
|
||
|
protected static $floodFilerDuration = 10;
|
||
|
|
||
|
protected static $folder;
|
||
|
|
||
|
protected static $logLength = 300;
|
||
|
protected static $messageLog;
|
||
|
|
||
|
public static function Silence($user, $expires) {
|
||
|
self::$silencedUsers[Utils::$chat["AUTOID"] ? $user->GetOriginalUsername() : $user->id] = $expires;
|
||
|
$user->SetParameter("silence", $expires);
|
||
|
$fn = Utils::CreateUniqueFile(self::$folder ."/ffdb/silences");
|
||
|
file_put_contents($fn, implode("\f", [$expires, $user->id, $user->GetOriginalUsername()]));
|
||
|
}
|
||
|
|
||
|
public static function RemoveSilence($user) {
|
||
|
$search = Utils::$chat["AUTOID"] ? [$user->GetOriginalUsername(), 2] : [$user->id, 1];
|
||
|
if(array_key_exists($search[0], self::$silencedUsers)) unset(self::$silencedUsers[$search[0]]);
|
||
|
$user->SetParameter("silence", null);
|
||
|
|
||
|
$files = glob(self::$folder ."/ffdb/silences/*");
|
||
|
foreach($files as $file) {
|
||
|
$parts = explode("\f", file_get_contents($file));
|
||
|
if($parts[$search[1]] == $search[0]) {
|
||
|
unlink($file);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static function IsSilencedFromList($user) {
|
||
|
$key = Utils::$chat["AUTOID"] ? $user->GetOriginalUsername() : $user->id;
|
||
|
if(array_key_exists($key, self::$silencedUsers)) {
|
||
|
if(self::$silencedUsers[$key] > gmdate("U") || self::$silencedUsers[$key] == "-1") return self::$silencedUsers[$key];
|
||
|
else unset(self::$silencedUsers[$key]);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
public static function IsSilenced($user) {
|
||
|
$tmp = $user->GetParameter("silence");
|
||
|
if($tmp == null) return false;
|
||
|
else {
|
||
|
if($tmp > gmdate("U") || $tmp == "-1") return true;
|
||
|
else {
|
||
|
self::RemoveSilence($user);
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static function Init() {
|
||
|
$dir = self::$folder = self::GetModFolder(__NAMESPACE__);
|
||
|
|
||
|
if(!file_exists("$dir/ffdb/silences")) mkdir("$dir/ffdb/silences", 0777, true);
|
||
|
self::$silencedUsers = [];
|
||
|
|
||
|
$files = glob("$dir/ffdb/silences/*");
|
||
|
foreach($files as $file) {
|
||
|
$parts = explode("\f", file_get_contents($file));
|
||
|
|
||
|
if(gmdate("U") >= $parts[0])
|
||
|
unlink($file);
|
||
|
else
|
||
|
self::$silencedUsers[Utils::$chat["AUTOID"] ? $parts[2] : $parts[1]] = $parts[0];
|
||
|
}
|
||
|
|
||
|
self::$messageLog = new Stack(self::$logLength);
|
||
|
|
||
|
self::AddCommandHook(["join", "create", "del", "pwd", "password", "priv", "privilege", "rank"], "handleChannelCommands");
|
||
|
self::AddCommandHook(["kick", "ban", "pardon", "unban", "silence", "unsilence", "say", "whois", "ip", "delete", "unbanip", "pardonip", "bans", "banned"], "handleModeratorCommands");
|
||
|
self::AddCommandHook(["whisper", "msg", "nick", "afk", "me", "action", "who", "color"], "handleUserCommands");
|
||
|
}
|
||
|
|
||
|
public static function OnUserJoin($user) {
|
||
|
if(($time = self::IsSilencedFromList($user)) != 0) $user->SetParameter("silence", $time);
|
||
|
}
|
||
|
|
||
|
public static function OnMessageReceive($user, &$msg) {
|
||
|
if(($val = $user->GetParameter("afk")) != null) {
|
||
|
$user->username = mb_substr($user->username, mb_strlen("<$val>_"));
|
||
|
$user->SetParameter("afk", null);
|
||
|
Context::ModifyUser($user);
|
||
|
}
|
||
|
if(self::IsSilenced($user)) return false;
|
||
|
}
|
||
|
|
||
|
public static function OnPacketReceive($conn, &$pid, &$data) {
|
||
|
if(($target = Context::GetUserBySock($conn)) != null) {
|
||
|
if($target->GetParameter("packetlog") == null) $target->SetParameter("packetlog", new Stack(self::$floodFilterSize));
|
||
|
$target->GetParameter("packetlog")->Push(gmdate("U"));
|
||
|
$stack = $target->GetParameter("packetlog");
|
||
|
//var_dump($stack);
|
||
|
$tmp = new Stack(self::$floodFilterSize-5, $stack);
|
||
|
if($tmp->Full()) {
|
||
|
if($stack->Top() - $stack->Bottom() <= self::$floodFilerDuration)
|
||
|
Message::PrivateBotMessage(MSG_NORMAL, "flwarn", [], $target);
|
||
|
}
|
||
|
|
||
|
if($stack->Full()) {
|
||
|
if($stack->Top() - $stack->Bottom() <= self::$floodFilerDuration)
|
||
|
Context::KickUser($target, null, 0, false, "flood");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static function AfterMessageLog($user, $msg, $channel, $flags) {
|
||
|
self::$messageLog->Push($user->id == -1 ? 999999999 : $user->GetRank(), Message::$msgId);
|
||
|
}
|
||
|
|
||
|
public static function handleChannelCommands($cmd, $user, $args) {
|
||
|
switch($cmd) {
|
||
|
case "join":
|
||
|
if(isset($args[0]) && $args[0] != "") {
|
||
|
$pwd = isset($args[1]) ? Utils::Hash(implode(" ", array_slice($args, 1))) : "";
|
||
|
Context::SwitchChannel($user, $args[0], $pwd);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case "create":
|
||
|
if($user->channelCreationPermission() != "0") {
|
||
|
if(isset($args[0]) && $args[0] != "") {
|
||
|
$channel = null;
|
||
|
if(is_numeric($args[0]) && isset($args[1]) && $args[1] != "") {
|
||
|
$args[0] = ($args[0] > $user->getRank()) ? $user->getRank() : $args[0];
|
||
|
$channel = new Channel(implode("_", array_slice($args, 1)), "", $args[0], $user);
|
||
|
} else {
|
||
|
$channel = new Channel(implode("_", $args));
|
||
|
$channel->channelOwner = $user;
|
||
|
}
|
||
|
|
||
|
$channel->channelType = ($user->channelCreationPermission() == 1) ? CHANNEL_TEMP : CHANNEL_PERM;
|
||
|
|
||
|
if(($ret = Context::CreateChannel($channel)) == "OK") {
|
||
|
if($channel->channelType == CHANNEL_TEMP) Context::SwitchChannel($user, $channel->name, $channel->password);
|
||
|
Message::PrivateBotMessage(MSG_NORMAL, "crchan", [$channel->name], $user);
|
||
|
} else
|
||
|
Message::PrivateUserMessage(Message::$bot, $user, $ret, "1001", false);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "cmderr", [], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "cmdna", ["/create"], $user);
|
||
|
break;
|
||
|
case "del":
|
||
|
if(isset($args[0]) && $args[0] != "") {
|
||
|
$name = implode($args, "_");
|
||
|
if(($channel = Context::GetChannel($name)) != null) {
|
||
|
if($user->canModerate() || $channel->GetOwner()->id == $user->id) {
|
||
|
Context::DeleteChannel($channel);
|
||
|
Message::PrivateBotMessage(MSG_NORMAL, "delchan", [$name], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "ndchan", [$name], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "nochan", [$name], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "cmderr", [], $user);
|
||
|
break;
|
||
|
|
||
|
case "password":
|
||
|
case "pwd":
|
||
|
if($user->canModerate() || Context::GetChannel($user->channel)->GetOwner()->id == $user->id) {
|
||
|
Context::ChangeChannelPassword($user->channel, isset($args[0]) ? implode(" ", $args) : "");
|
||
|
Message::PrivateBotMessage(MSG_NORMAL, "cpwdchan", [], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "cmdna", ["/pwd"], $user);
|
||
|
break;
|
||
|
|
||
|
case "privilege":
|
||
|
case "rank":
|
||
|
case "priv":
|
||
|
if($user->canModerate() || Context::GetChannel($user->channel)->GetOwner()->id == $user->id) {
|
||
|
if(!isset($args[0]) || $args[0] <= $user->getRank()) {
|
||
|
Context::ChangeChannelPermission($user->channel, isset($args[0]) ? $args[0] : 0);
|
||
|
Message::PrivateBotMessage(MSG_NORMAL, "cprivchan", [], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "rankerr", [], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "cmdna", ["/priv"], $user);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static function handleModeratorCommands($cmd, $user, $args) {
|
||
|
if($user->canModerate()) {
|
||
|
switch ($cmd) {
|
||
|
case "kick":
|
||
|
if(($target = Context::GetUserByName($args[0])) != null) {
|
||
|
if($target->getRank() < $user->getRank() && strtolower($args[0]) != strtolower($user->username)) {
|
||
|
$length = (!isset($args[1]) || !is_numeric($args[1])) ? 0 : ($args[1] > 0 ? $args[1]*60 : -1);
|
||
|
Context::KickUser($target, $user, $length);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "kickna", [$args[0]], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "usernf", [$args[0]], $user);
|
||
|
break;
|
||
|
|
||
|
case "ban":
|
||
|
if(($target = Context::GetUserByName($args[0])) != null) {
|
||
|
if($target->getRank() < $user->getRank() && strtolower($args[0]) != strtolower($user->username)) {
|
||
|
$length = (!isset($args[1]) || !is_numeric($args[1])) ? -1 : ($args[1] > 0 ? $args[1]*60 : -1);
|
||
|
Context::KickUser($target, $user, $length, true);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "kickna", [$args[0]], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "usernf", [$args[0]], $user);
|
||
|
break;
|
||
|
|
||
|
case "pardon":
|
||
|
case "unban":
|
||
|
if(Context::Unban(null, null, $args[0], $user)) {
|
||
|
Message::PrivateBotMessage(MSG_NORMAL, "unban", [$args[0]], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "notban", [$args[0]], $user);
|
||
|
break;
|
||
|
|
||
|
case "pardonip":
|
||
|
case "unbanip":
|
||
|
if(Context::Unban(null, $args[0], null, $user)) {
|
||
|
Message::PrivateBotMessage(MSG_NORMAL, "unban", [$args[0]], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "notban", [$args[0]], $user);
|
||
|
break;
|
||
|
|
||
|
case "bans":
|
||
|
case "banned":
|
||
|
$list = [];
|
||
|
foreach(Context::$bannedUsers as $bid => $ban) {
|
||
|
if($ban->username == null)
|
||
|
$push = "<a href=\"javascript:void(0);\" onclick=\"Chat.SendMessageWrapper('/unbanip '+ this.innerHTML);\">". $ban->ip ."</a>";
|
||
|
else
|
||
|
$push = "<a href=\"javascript:void(0);\" onclick=\"Chat.SendMessageWrapper('/unban '+ this.innerHTML);\">". $ban->username ."</a>";
|
||
|
array_push($list, $push);
|
||
|
}
|
||
|
Message::PrivateBotMessage(MSG_NORMAL, "banlist", [implode(", ", $list)], $user);
|
||
|
break;
|
||
|
|
||
|
case "silence":
|
||
|
if(($target = Context::GetUserByName($args[0])) != null) {
|
||
|
if($target->id != $user->id) {
|
||
|
if($target->getRank() < $user->getRank()) {
|
||
|
if (!self::IsSilenced($target)) {
|
||
|
$exp = isset($args[1]) ? (int)gmdate("U") + abs($args[1]*60) : -1;
|
||
|
self::Silence($target, $exp);
|
||
|
Message::PrivateBotMessage(MSG_NORMAL, "silence", [], $target);
|
||
|
Message::PrivateBotMessage(MSG_NORMAL, "silok", [$target->username], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "silerr", [], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "silperr", [], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "silself", [], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "usernf", [$args[0]], $user);
|
||
|
break;
|
||
|
case "unsilence":
|
||
|
if(($target = Context::GetUserByName($args[0])) != null) {
|
||
|
if($target->getRank() < $user->getRank()) {
|
||
|
if (self::IsSilenced($target)) {
|
||
|
self::RemoveSilence($target);
|
||
|
Message::PrivateBotMessage(MSG_NORMAL, "unsil", [], $target);
|
||
|
Message::PrivateBotMessage(MSG_NORMAL, "usilok", [$target->username], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "usilerr", [], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "usilperr", [], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "usernf", [$args[0]], $user);
|
||
|
break;
|
||
|
|
||
|
case "say":
|
||
|
Message::BroadcastBotMessage(MSG_NORMAL, "say", [implode(" ", $args)]);
|
||
|
break;
|
||
|
|
||
|
case "ip":
|
||
|
case "whois":
|
||
|
if(($tgt = Context::GetUserByName($args[0])) != null) {
|
||
|
Message::PrivateBotMessage(MSG_NORMAL, "ipaddr", [$args[0], $tgt->sock->remoteAddress], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "usernf", [$args[0]], $user);
|
||
|
break;
|
||
|
|
||
|
case "delete":
|
||
|
if(isset($args[0])) {
|
||
|
if(array_key_exists($args[0], self::$messageLog->Raw())) {
|
||
|
if(self::$messageLog->Raw()[$args[0]] <= $user->GetRank()) {
|
||
|
Message::DeleteMessage($args[0]);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "delerr", [], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "delerr", [], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "cmderr", [], $user);
|
||
|
break;
|
||
|
}
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "cmdna", ["/{$cmd}"], $user);
|
||
|
}
|
||
|
|
||
|
public static function handleUserCommands($cmd, $user, $args) {
|
||
|
switch($cmd) {
|
||
|
case "nick":
|
||
|
if($user->canChangeNick()) {
|
||
|
$name = "~". trim(Utils::SanitizeName(mb_substr(join("_", $args), 0, Utils::$chat["MAX_USERNAME_LEN"]-1)));
|
||
|
if(!isset($args[0])) $name = $user->GetOriginalUsername();
|
||
|
if(Context::GetUserByName($name) == null) {
|
||
|
Message::BroadcastBotMessage(MSG_NORMAL, "nick", [$user->username, $name], $user->channel);
|
||
|
$user->username = $name;
|
||
|
Context::ModifyUser($user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "nameinuse", [$name], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "cmdna", ["/nick"], $user);
|
||
|
break;
|
||
|
case "whisper":
|
||
|
case "msg":
|
||
|
if(isset($args[0]) && isset($args[1])) {
|
||
|
if(($target = Context::GetUserByName($args[0])) != null) {
|
||
|
if($target->id != $user->id) {
|
||
|
$msg = implode(" ", array_slice($args, 1));
|
||
|
Message::PrivateUserMessage($user, $target, $msg);
|
||
|
Message::PrivateUserMessage($user, $user, $target->username . " " . $msg);
|
||
|
}
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "usernf", [$args[0]], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "cmderr", [], $user);
|
||
|
break;
|
||
|
case "afk":
|
||
|
$val = isset($args[0]) ? strtoupper(mb_substr($args[0], 0, self::$maxAfkTagLength)) : "AFK";
|
||
|
if($user->GetParameter("afk") == null && $val != "") {
|
||
|
$user->SetParameter("afk", $val);
|
||
|
$user->username = "<$val>_". $user->username;
|
||
|
Context::ModifyUser($user);
|
||
|
}
|
||
|
break;
|
||
|
case "action":
|
||
|
case "me":
|
||
|
$msg = join(" ", $args);
|
||
|
if(trim($msg) != "")
|
||
|
Message::BroadcastUserMessage($user, "<i>". $msg ."</i>", LOCAL_CHANNEL, "1100");
|
||
|
break;
|
||
|
case "who":
|
||
|
if(isset($args[0])) {
|
||
|
if(($chan = Context::GetChannel($args[0])) != null) {
|
||
|
if($chan->permissionLevel <= $user->GetRank() && ($chan->password != "" || $user->CanModerate())) {
|
||
|
$arr = [];
|
||
|
foreach ($chan->users as $u)
|
||
|
array_push($arr, "<a href=\"javascript:void(0);\" onclick=\"UI.InsertChatText(this.innerHTML);\"". ($u->id == $user->id ? " style='font-weight: bold;'" : "") .">" . $u->username . "</a>");
|
||
|
Message::PrivateBotMessage(MSG_NORMAL, "whochan", [$args[0], implode(", ", $arr)], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "whoerr", [$args[0]], $user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "nochan", [$args[0]], $user);
|
||
|
} else {
|
||
|
$arr = [];
|
||
|
foreach(Context::$onlineUsers as $u)
|
||
|
array_push($arr, "<a href=\"javascript:void(0);\" onclick=\"UI.InsertChatText(this.innerHTML);\"". ($u->id == $user->id ? " style='font-weight: bold;'" : "") .">". $u->username ."</a>");
|
||
|
Message::PrivateBotMessage(MSG_NORMAL, "who", [implode(", ", $arr)], $user);
|
||
|
}
|
||
|
break;
|
||
|
case "color":
|
||
|
if(isset($args[0])) {
|
||
|
$user->color = $args[0];//str_replace(["&",";","\"","'"], ["","","",""], $args[0]);
|
||
|
Context::ModifyUser($user);
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "cmderr", [], $user);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static function OnCommandReceive($user, &$cmd, &$args) {
|
||
|
/*if(!self::IsSilenced($user) || (self::IsSilenced($user) && in_array($cmd, self::$allowedCmds))) {
|
||
|
if($cmd == "afk") {
|
||
|
$val = isset($args[0]) ? strtoupper(mb_substr($args[0], 0, self::$maxAfkTagLength)) : "AFK";
|
||
|
if($user->GetParameter("afk") == null && $val != "") {
|
||
|
$user->SetParameter("afk", $val);
|
||
|
$user->username = "<$val>_". $user->username;
|
||
|
Context::ModifyUser($user);
|
||
|
}
|
||
|
return false;
|
||
|
} else if($cmd == "silence") {
|
||
|
if($user->canModerate()) {
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "cmdna", ["/silence"], $user);
|
||
|
return false;
|
||
|
} else if($cmd == "unsilence") {
|
||
|
if($user->canModerate()) {
|
||
|
} else Message::PrivateBotMessage(MSG_ERROR, "cmdna", ["/unsilence"], $user);
|
||
|
return false;
|
||
|
}
|
||
|
} else return false;*/
|
||
|
}
|
||
|
}
|