Skip to content

Commit

Permalink
Release 1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
alvin0319 committed Feb 10, 2021
1 parent d3104e0 commit 4fadefe
Show file tree
Hide file tree
Showing 6 changed files with 345 additions and 0 deletions.
9 changes: 9 additions & 0 deletions plugin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: DiscordVerify
author: alvin0319
main: TeamBixby\DiscordVerify\DiscordVerify
version: 1.0.0
api: 3.0.0

permissions:
discordverify.command.use:
default: true
5 changes: 5 additions & 0 deletions resources/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
address: ~
port: ~
password: ~

bindPort: ~
160 changes: 160 additions & 0 deletions src/TeamBixby/DiscordVerify/DiscordThread.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
<?php

declare(strict_types=1);

namespace TeamBixby\DiscordVerify;

use ClassLoader;
use pocketmine\utils\MainLogger;
use TeamBixby\DiscordVerify\util\DiscordException;
use Thread;
use Volatile;
use function get_class;
use function gettype;
use function is_array;
use function is_object;
use function is_string;
use function json_decode;
use function json_encode;
use function socket_bind;
use function socket_close;
use function socket_create;
use function socket_last_error;
use function socket_recvfrom;
use function socket_sendto;
use function socket_set_nonblock;
use function socket_strerror;
use function trim;
use const PHP_INT_MAX;
use const SOCKET_EWOULDBLOCK;

class DiscordThread extends Thread{

public const KEY_ACTION = "action";

public const KEY_DATA = "data";

protected ClassLoader $classLoader;

protected string $targetHost;

protected int $targetPort;

protected int $bindPort;

protected string $password;

protected Volatile $inboundQueue;

protected Volatile $outboundQueue;

protected bool $shutdown = true;

public function __construct(ClassLoader $loader, string $targetHost, int $targetPort, int $bindPort, string $password, Volatile $inboundQueue, Volatile $outboundQueue){
$this->classLoader = $loader;
$this->targetHost = $targetHost;
$this->targetPort = $targetPort;
$this->bindPort = $bindPort;
$this->password = $password;
$this->inboundQueue = $inboundQueue;
$this->outboundQueue = $outboundQueue;
}

/**
* @throws DiscordException
*/
public function run() : void{
$this->classLoader->register();

$this->shutdown = false;

$sendSock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
$recvSock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);

socket_set_nonblock($recvSock);

if($sendSock === false || $recvSock === false){
throw DiscordException::wrap("Failed to create socket");
}

if(@socket_bind($recvSock, "0.0.0.0", $this->bindPort) === false){
throw DiscordException::wrap("Failed to bind port");
}

socket_set_nonblock($recvSock);

while(!$this->shutdown){
$this->receiveData($recvSock);
$this->sendData($sendSock);
}
socket_close($recvSock);
socket_close($sendSock);
}

/**
* @param resource $recvSock
*
* @throws DiscordException
*/
private function receiveData($recvSock) : void{
$buffer = "";
if(@socket_recvfrom($recvSock, $buffer, 65535, 0, $source, $port) === false){
$errno = socket_last_error($recvSock);
if($errno === SOCKET_EWOULDBLOCK){
return;
}
throw new DiscordException("Failed to recv (errno $errno): " . trim(socket_strerror($errno)), $errno);
//return;
}
if($buffer !== null && $buffer !== ""){
$data = json_decode($buffer, true);
if(!is_array($data)){
throw DiscordException::wrap("Expected array, got " . (is_object($data) ? get_class($data) : gettype($data)));
}
$password = $data["password"] ?? null;
if(!is_string($password)){
MainLogger::getLogger()->notice("Bad socket received from $source:$port");
MainLogger::getLogger()->notice("Password does not exist on response body");
return;
}
if($password !== $this->password){
MainLogger::getLogger()->notice("Bad socket received from $source:$port");
MainLogger::getLogger()->notice("Password does not match");
return;
}
$action = $data["action"];
$actionData = $data["data"];

$this->outboundQueue[] = [
self::KEY_ACTION => $action,
self::KEY_DATA => $actionData
];
}
}

/**
* @param resource $sendSock
*
* @throws DiscordException
*/
private function sendData($sendSock) : void{
while($this->inboundQueue->count() > 0){
$chunk = $this->inboundQueue->shift();
$data = [
"data" => $chunk,
"password" => $this->password
];
if(@socket_sendto($sendSock, json_encode($data), PHP_INT_MAX, 0, $this->targetHost, $this->targetPort) === false){
$errno = socket_last_error($sendSock);
if($errno === SOCKET_EWOULDBLOCK){
return;
}
throw DiscordException::wrap("Failed to send socket: " . trim(socket_strerror($errno)));
}
}
}

public function shutdown() : void{
$this->shutdown = true;
}
}
116 changes: 116 additions & 0 deletions src/TeamBixby/DiscordVerify/DiscordVerify.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

declare(strict_types=1);

namespace TeamBixby\DiscordVerify;

use pocketmine\plugin\PluginBase;
use pocketmine\scheduler\ClosureTask;
use pocketmine\utils\SingletonTrait;
use pocketmine\utils\TextFormat;
use TeamBixby\DiscordVerify\command\VerifyCommand;
use TeamBixby\DiscordVerify\util\DiscordException;
use Volatile;
use function file_exists;
use function file_get_contents;
use function file_put_contents;
use function json_decode;
use function json_encode;
use function strtolower;
use const PTHREADS_INHERIT_ALL;

final class DiscordVerify extends PluginBase{
use SingletonTrait;

public const ACTION_VERIFIED = "verified";
public const ACTION_UNVERIFIED = "unverified";

protected Volatile $inboundQueue;

protected Volatile $outboundQueue;

protected DiscordThread $thread;

protected array $data = [];

protected bool $canThreadClose = false;

public function onLoad() : void{
self::setInstance($this);
}

public function onEnable() : void{
$this->saveDefaultConfig();
$config = $this->getConfig();

if($config->get("address", null) === null || $config->get("port", null) === null || $config->get("password", null) === null || $config->get("bindPort", null) === null){
$this->getLogger()->emergency("Please fill the config correctly before use this plugin!");
$this->getServer()->getPluginManager()->disablePlugin($this);
return;
}

$this->inboundQueue = new Volatile();
$this->outboundQueue = new Volatile();

try{
$this->thread = new DiscordThread($this->getServer()->getLoader(), $config->get("address"), $config->get("port"), $config->get("bindPort"), $config->get("password"), $this->inboundQueue, $this->outboundQueue);
$this->thread->start(PTHREADS_INHERIT_ALL);
$this->canThreadClose = true;
}catch(DiscordException $e){
$this->getLogger()->emergency("Failed to run DiscordThread");
$this->getLogger()->logException($e);
$this->canThreadClose = false;
$this->getServer()->getPluginManager()->disablePlugin($this);
return;
}

if(file_exists($file = $this->getDataFolder() . "verified_players.json")){
$this->data = json_decode(file_get_contents($file), true);
}

$this->getScheduler()->scheduleRepeatingTask(new ClosureTask(function(int $unused) : void{
while($this->outboundQueue->count() > 0){
$chunk = $this->outboundQueue->shift();
$data = $chunk["data"];
switch($chunk["action"]){
case self::ACTION_VERIFIED:
$player = $data["player"];
$this->data[strtolower($player)] = $data["discordId"];
if(($player = $this->getServer()->getPlayerExact($player)) !== null){
$player->sendMessage(TextFormat::GREEN . "You've verified!");
}
break;
case self::ACTION_UNVERIFIED:
$player = $data["player"];
if(isset($this->data[strtolower($player)])){
unset($this->data[strtolower($player)]);
if(($player = $this->getServer()->getPlayerExact($player)) !== null){
$player->sendMessage(TextFormat::GREEN . "You've unverified!");
}
}
break;
}
}
}), 5);

$this->getServer()->getCommandMap()->register("discordverify", new VerifyCommand());
}

public function onDisable() : void{
if($this->canThreadClose){
$this->thread->shutdown();
while($this->thread->isRunning()){
// SAFETY THREAD CLOSE
}
}
file_put_contents($this->getDataFolder() . "verified_players.json", json_encode($this->data));
}

public function addQueue(array $data) : void{
$this->inboundQueue[] = $data;
}

public function isVerified(string $player) : bool{
return isset($this->data[strtolower($player)]);
}
}
41 changes: 41 additions & 0 deletions src/TeamBixby/DiscordVerify/command/VerifyCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace TeamBixby\DiscordVerify\command;

use pocketmine\command\CommandSender;
use pocketmine\command\PluginCommand;
use pocketmine\Player;
use pocketmine\utils\TextFormat;
use pocketmine\utils\UUID;
use TeamBixby\DiscordVerify\DiscordVerify;
use function substr;

class VerifyCommand extends PluginCommand{

public function __construct(){
parent::__construct("verify", DiscordVerify::getInstance());
$this->setPermission("discordverify.command.use");
}

public function execute(CommandSender $sender, string $commandLabel, array $args) : bool{
if(!$this->testPermission($sender)){
return false;
}
if(!$sender instanceof Player){
$sender->sendMessage(TextFormat::RED . "You can't use this command on console.");
return false;
}
if(DiscordVerify::getInstance()->isVerified($sender->getName())){
$sender->sendMessage(TextFormat::RED . "You are already verified.");
return false;
}
DiscordVerify::getInstance()->addQueue([
"player" => $sender->getName(),
"random_token" => $token = substr(UUID::fromRandom()->toString(), 0, 6)
]);
$sender->sendMessage(TextFormat::GREEN . "Use the verify command on Discord server using " . $token);
return true;
}
}
14 changes: 14 additions & 0 deletions src/TeamBixby/DiscordVerify/util/DiscordException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace TeamBixby\DiscordVerify\util;

use Exception;

class DiscordException extends Exception{

public static function wrap(string $message) : DiscordException{
return new DiscordException($message);
}
}

0 comments on commit 4fadefe

Please sign in to comment.