mirror of
https://github.com/kuaifan/dootask.git
synced 2026-03-07 09:57:37 +00:00
perf: 新增doo模块
This commit is contained in:
parent
1ccf6e49b7
commit
13ca9031a8
@ -22,6 +22,7 @@ use App\Models\WebSocketDialog;
|
||||
use App\Module\Base;
|
||||
use App\Module\BillExport;
|
||||
use App\Module\BillMultipleExport;
|
||||
use App\Module\Doo;
|
||||
use App\Module\TimeRange;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Arr;
|
||||
@ -2082,12 +2083,12 @@ class ProjectController extends AbstractController
|
||||
if ($task_id === 0) {
|
||||
$log->projectTask?->cancelAppend();
|
||||
}
|
||||
$log->detail = Base::Lang($log->detail);
|
||||
$log->detail = Doo::translate($log->detail);
|
||||
$log->time = [
|
||||
'ymd' => date(date("Y", $timestamp) == date("Y", Base::time()) ? "m-d" : "Y-m-d", $timestamp),
|
||||
'hi' => date("h:i", $timestamp) ,
|
||||
'week' => Base::Lang("周" . Base::getTimeWeek($timestamp)),
|
||||
'segment' => Base::Lang(Base::getTimeDayeSegment($timestamp)),
|
||||
'week' => Doo::translate("周" . Base::getTimeWeek($timestamp)),
|
||||
'segment' => Doo::translate(Base::getTimeDayeSegment($timestamp)),
|
||||
];
|
||||
return $log;
|
||||
});
|
||||
|
||||
@ -9,6 +9,7 @@ use App\Models\Report;
|
||||
use App\Models\ReportReceive;
|
||||
use App\Models\User;
|
||||
use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
use App\Tasks\PushTask;
|
||||
use Carbon\Carbon;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
@ -306,7 +307,7 @@ class ReportController extends AbstractController
|
||||
if ($complete_task->isNotEmpty()) {
|
||||
foreach ($complete_task as $task) {
|
||||
$complete_at = Carbon::parse($task->complete_at);
|
||||
$pre = $type == Report::WEEKLY ? ('<span>[' . Base::Lang('周' . ['日', '一', '二', '三', '四', '五', '六'][$complete_at->dayOfWeek]) . ']</span> ') : '';
|
||||
$pre = $type == Report::WEEKLY ? ('<span>[' . Doo::translate('周' . ['日', '一', '二', '三', '四', '五', '六'][$complete_at->dayOfWeek]) . ']</span> ') : '';
|
||||
$completeContent .= "<li>{$pre}[{$task->project->name}] {$task->name}</li>";
|
||||
}
|
||||
} else {
|
||||
@ -327,7 +328,7 @@ class ReportController extends AbstractController
|
||||
if ($unfinished_task->isNotEmpty()) {
|
||||
foreach ($unfinished_task as $task) {
|
||||
empty($task->end_at) || $end_at = Carbon::parse($task->end_at);
|
||||
$pre = (!empty($end_at) && $end_at->lt($now_dt)) ? '<span style="color:#ff0000;">[' . Base::Lang('超期') . ']</span> ' : '';
|
||||
$pre = (!empty($end_at) && $end_at->lt($now_dt)) ? '<span style="color:#ff0000;">[' . Doo::translate('超期') . ']</span> ' : '';
|
||||
$unfinishedContent .= "<li>{$pre}[{$task->project->name}] {$task->name}</li>";
|
||||
}
|
||||
} else {
|
||||
@ -341,12 +342,12 @@ class ReportController extends AbstractController
|
||||
$title = $user->nickname . "的日报[" . $start_time->format("Y/m/d") . "]";
|
||||
}
|
||||
// 生成内容
|
||||
$content = '<h2>' . Base::Lang('已完成工作') . '</h2><ol>' .
|
||||
$content = '<h2>' . Doo::translate('已完成工作') . '</h2><ol>' .
|
||||
$completeContent . '</ol><h2>' .
|
||||
Base::Lang('未完成的工作') . '</h2><ol>' .
|
||||
Doo::translate('未完成的工作') . '</h2><ol>' .
|
||||
$unfinishedContent . '</ol>';
|
||||
if ($type === Report::WEEKLY) {
|
||||
$content .= "<h2>" . Base::Lang("下周拟定计划") . "[" . $start_time->addWeek()->format("m/d") . "-" . $end_time->addWeek()->format("m/d") . "]</h2><ol><li> </li></ol>";
|
||||
$content .= "<h2>" . Doo::translate("下周拟定计划") . "[" . $start_time->addWeek()->format("m/d") . "-" . $end_time->addWeek()->format("m/d") . "]</h2><ol><li> </li></ol>";
|
||||
}
|
||||
$data = [
|
||||
"time" => $start_time->toDateTimeString(),
|
||||
|
||||
@ -19,6 +19,7 @@ use App\Models\WebSocketDialog;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use App\Module\AgoraIO\AgoraTokenGenerator;
|
||||
use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
use Arr;
|
||||
use Cache;
|
||||
use Captcha;
|
||||
@ -135,8 +136,8 @@ class UsersController extends AbstractController
|
||||
//
|
||||
if (!Project::withTrashed()->whereUserid($user->userid)->wherePersonal(1)->exists()) {
|
||||
Project::createProject([
|
||||
'name' => Base::Lang('个人项目'),
|
||||
'desc' => Base::Lang('注册时系统自动创建项目,你可以自由删除。'),
|
||||
'name' => Doo::translate('个人项目'),
|
||||
'desc' => Doo::translate('注册时系统自动创建项目,你可以自由删除。'),
|
||||
'personal' => 1,
|
||||
], $user->userid);
|
||||
}
|
||||
@ -530,13 +531,13 @@ class UsersController extends AbstractController
|
||||
return preg_match("/\(M\)$/", $item);
|
||||
});
|
||||
if ($dep) {
|
||||
$tags[] = preg_replace("/\(M\)$/", "", trim($dep[0])) . Base::Lang("负责人");
|
||||
$tags[] = preg_replace("/\(M\)$/", "", trim($dep[0])) . Doo::translate("负责人");
|
||||
}
|
||||
if ($userInfo->isAdmin()) {
|
||||
$tags[] = Base::Lang("系统管理员");
|
||||
$tags[] = Doo::translate("系统管理员");
|
||||
}
|
||||
if ($userInfo->isTemp()) {
|
||||
$tags[] = Base::Lang("临时帐号");
|
||||
$tags[] = Doo::translate("临时帐号");
|
||||
}
|
||||
$userInfo->tags = $tags;
|
||||
//
|
||||
|
||||
@ -4,6 +4,7 @@ namespace App\Http\Middleware;
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
use App\Module\Doo;
|
||||
use Closure;
|
||||
use Request;
|
||||
|
||||
@ -31,6 +32,7 @@ class WebApi
|
||||
if (in_array(strtolower($APP_SCHEME), ['https', 'on', 'ssl', '1', 'true', 'yes'], true)) {
|
||||
$request->setTrustedProxies([$request->getClientIp()], $request::HEADER_X_FORWARDED_PROTO);
|
||||
}
|
||||
Doo::init();
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
use App\Module\Extranet;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
@ -47,43 +47,43 @@ class UserBot extends AbstractModel
|
||||
'check-in@bot.system' => [
|
||||
[
|
||||
'key' => 'checkin',
|
||||
'label' => Base::Lang('我要签到')
|
||||
'label' => Doo::translate('我要签到')
|
||||
], [
|
||||
'key' => 'it',
|
||||
'label' => Base::Lang('IT资讯')
|
||||
'label' => Doo::translate('IT资讯')
|
||||
], [
|
||||
'key' => '36ke',
|
||||
'label' => Base::Lang('36氪')
|
||||
'label' => Doo::translate('36氪')
|
||||
], [
|
||||
'key' => '60s',
|
||||
'label' => Base::Lang('60s读世界')
|
||||
'label' => Doo::translate('60s读世界')
|
||||
], [
|
||||
'key' => 'joke',
|
||||
'label' => Base::Lang('开心笑话')
|
||||
'label' => Doo::translate('开心笑话')
|
||||
], [
|
||||
'key' => 'soup',
|
||||
'label' => Base::Lang('心灵鸡汤')
|
||||
'label' => Doo::translate('心灵鸡汤')
|
||||
]
|
||||
],
|
||||
'anon-msg@bot.system' => [
|
||||
[
|
||||
'key' => 'help',
|
||||
'label' => Base::Lang('使用说明')
|
||||
'label' => Doo::translate('使用说明')
|
||||
], [
|
||||
'key' => 'privacy',
|
||||
'label' => Base::Lang('隐私说明')
|
||||
'label' => Doo::translate('隐私说明')
|
||||
],
|
||||
],
|
||||
'bot-manager@bot.system' => [
|
||||
[
|
||||
'key' => '/help',
|
||||
'label' => Base::Lang('帮助指令')
|
||||
'label' => Doo::translate('帮助指令')
|
||||
], [
|
||||
'key' => '/api',
|
||||
'label' => Base::Lang('Api接口文档')
|
||||
'label' => Doo::translate('Api接口文档')
|
||||
], [
|
||||
'key' => '/list',
|
||||
'label' => Base::Lang('我的机器人')
|
||||
'label' => Doo::translate('我的机器人')
|
||||
],
|
||||
],
|
||||
default => [],
|
||||
|
||||
@ -4,6 +4,7 @@ namespace App\Models;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
use App\Tasks\PushTask;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
@ -147,7 +148,7 @@ class WebSocketDialog extends AbstractModel
|
||||
}
|
||||
break;
|
||||
case 'all':
|
||||
$this->name = Base::Lang('全体成员');
|
||||
$this->name = Doo::translate('全体成员');
|
||||
$this->all_group_mute = Base::settingFind('system', 'all_group_mute');
|
||||
break;
|
||||
}
|
||||
@ -181,7 +182,7 @@ class WebSocketDialog extends AbstractModel
|
||||
&& $mention_id = intval($builder->clone()->whereMention(1)->orderByDesc('msg_id')->value('msg_id'))) {
|
||||
$array[] = [
|
||||
'msg_id' => $mention_id,
|
||||
'label' => Base::Lang('@我的消息'),
|
||||
'label' => Doo::translate('@我的消息'),
|
||||
];
|
||||
}
|
||||
// 最早一条未读消息
|
||||
@ -374,7 +375,7 @@ class WebSocketDialog extends AbstractModel
|
||||
$name = \DB::table('project_tasks')->where('dialog_id', $this->id)->value('name');
|
||||
break;
|
||||
case 'all':
|
||||
$name = Base::Lang('全体成员');
|
||||
$name = Doo::translate('全体成员');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1458,73 +1458,6 @@ class Base
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 国际化(替换国际语言)
|
||||
* @param $val
|
||||
* @return mixed
|
||||
*/
|
||||
public static function Lang($val)
|
||||
{
|
||||
$data = self::langData();
|
||||
if (isset($data[$val])) {
|
||||
return $data[$val] ?: $val;
|
||||
}
|
||||
foreach ($data as $key => $item) {
|
||||
if (str_contains($key, "(*)")) {
|
||||
$regex = str_replace("(*)", "~%~", $key);
|
||||
$regex = preg_quote($regex);
|
||||
$regex = str_replace("~%~", "(.*?)", $regex);
|
||||
$regex = "/^" . $regex . "$/";
|
||||
if (preg_match($regex, $val)) {
|
||||
$r = $item ?: $key;
|
||||
return preg_replace_callback($regex, function($m) use ($r) {
|
||||
$i = 0;
|
||||
foreach ($m as $v) {
|
||||
if ($i > 0) {
|
||||
$r = preg_replace("/\(\*\)/", $v, $r, 1);
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
return $r;
|
||||
}, $val);
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
$undefinedPath = base_path('language/undefined-api.txt');
|
||||
if (self::$undefinedLang === null) {
|
||||
self::$undefinedLang = [];
|
||||
if (file_exists($undefinedPath)) {
|
||||
self::$undefinedLang = explode("\n", file_get_contents($undefinedPath));
|
||||
self::$undefinedLang = array_values(array_filter(array_unique(self::$undefinedLang)));
|
||||
}
|
||||
}
|
||||
if (!in_array($val, self::$undefinedLang)) {
|
||||
self::$undefinedLang[] = $val;
|
||||
@file_put_contents(base_path('language/undefined-api.txt'), "$val\n", FILE_APPEND);
|
||||
}
|
||||
return $val;
|
||||
}
|
||||
private static $undefinedLang = null;
|
||||
|
||||
/**
|
||||
* 加载语言数据
|
||||
* @return array
|
||||
*/
|
||||
public static function langData()
|
||||
{
|
||||
global $_A;
|
||||
$language = trim(self::headerOrInput('language'));
|
||||
if (!isset($_A["__static_langdata_" . $language])) {
|
||||
$_A["__static_langdata_" . $language] = [];
|
||||
$langpath = public_path('language/api/' . $language . '.json');
|
||||
if (file_exists($langpath) && $data = Base::json2array(file_get_contents($langpath))) {
|
||||
$_A["__static_langdata_" . $language] = $data;
|
||||
}
|
||||
}
|
||||
return $_A["__static_langdata_" . $language];
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON返回
|
||||
* @param $param
|
||||
@ -1552,7 +1485,7 @@ class Base
|
||||
{
|
||||
return [
|
||||
'ret' => $ret,
|
||||
'msg' => self::Lang($msg),
|
||||
'msg' => Doo::translate($msg),
|
||||
'data' => $data
|
||||
];
|
||||
}
|
||||
@ -1568,7 +1501,7 @@ class Base
|
||||
{
|
||||
return [
|
||||
'ret' => $ret,
|
||||
'msg' => self::Lang($msg),
|
||||
'msg' => Doo::translate($msg),
|
||||
'data' => $data
|
||||
];
|
||||
}
|
||||
|
||||
170
app/Module/Doo.php
Normal file
170
app/Module/Doo.php
Normal file
@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
namespace App\Module;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FFI;
|
||||
|
||||
class Doo
|
||||
{
|
||||
public static $doo = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
return self::init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
* @return mixed
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
if (self::$doo === null) {
|
||||
$doo = FFI::cdef(<<<EOF
|
||||
void initialize();
|
||||
void setWorkDir(char* val);
|
||||
void setDefaultLanguage(char* val);
|
||||
void setUserToken(char* val);
|
||||
char* license();
|
||||
int userId();
|
||||
char* userExpiredAt();
|
||||
char* userEmail();
|
||||
char* userToken();
|
||||
char* tokenEncode(int userid, char* email, char* encrypt, int days);
|
||||
char* tokenDecode(char* val);
|
||||
char* translate(char* val);
|
||||
char* translateSpecified(char* val, char* val);
|
||||
EOF, app_path("Module/Lib/doo.so"));
|
||||
$doo->initialize();
|
||||
$doo->setWorkDir("/var/www");
|
||||
$doo->setDefaultLanguage(Base::headerOrInput('language'));
|
||||
$doo->setUserToken(Base::getToken());
|
||||
self::$doo = $doo;
|
||||
}
|
||||
return self::$doo;
|
||||
}
|
||||
|
||||
/**
|
||||
* char转为字符串
|
||||
* @param $text
|
||||
* @return string
|
||||
*/
|
||||
public static function string($text)
|
||||
{
|
||||
return FFI::string($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* License
|
||||
* @return array
|
||||
*/
|
||||
public static function license()
|
||||
{
|
||||
$array = Base::json2array(self::string(self::init()->license()));
|
||||
|
||||
$ips = explode(",", $array['ip']);
|
||||
$array['ip'] = [];
|
||||
foreach ($ips as $ip) {
|
||||
if (Base::is_ipv4($ip)) {
|
||||
$array['ip'][] = $ip;
|
||||
}
|
||||
}
|
||||
|
||||
$domains = explode(",", $array['domain']);
|
||||
$array['domain'] = [];
|
||||
foreach ($domains as $domain) {
|
||||
if (Base::is_domain($domain)) {
|
||||
$array['domain'][] = $domain;
|
||||
}
|
||||
}
|
||||
|
||||
$emails = explode(",", $array['email']);
|
||||
$array['email'] = [];
|
||||
foreach ($emails as $email) {
|
||||
if (Base::isEmail($email)) {
|
||||
$array['email'][] = $email;
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前会员ID(来自请求的token)
|
||||
* @return int
|
||||
*/
|
||||
public static function userId()
|
||||
{
|
||||
return self::init()->userId();
|
||||
}
|
||||
|
||||
/**
|
||||
* token是否过期(来自请求的token)
|
||||
* @return bool
|
||||
*/
|
||||
public static function userExpired()
|
||||
{
|
||||
return Carbon::parse(self::string(self::init()->userExpiredAt()))->isBefore(Carbon::now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前会员邮箱地址(来自请求的token)
|
||||
* @return string
|
||||
*/
|
||||
public static function userEmail()
|
||||
{
|
||||
return self::string(self::init()->userEmail());
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前会员token(来自请求的token)
|
||||
* @return string
|
||||
*/
|
||||
public static function userToken()
|
||||
{
|
||||
return self::string(self::init()->userToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成token(编码token)
|
||||
* @param $userid
|
||||
* @param $email
|
||||
* @param $encrypt
|
||||
* @param int $days 有效时间(天)
|
||||
* @return string
|
||||
*/
|
||||
public static function tokenEncode($userid, $email, $encrypt, $days = 7)
|
||||
{
|
||||
return self::string(self::init()->tokenEncode($userid, $email, $encrypt, $days));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解码token
|
||||
* @param $token
|
||||
* @return array
|
||||
*/
|
||||
public static function tokenDecode($token)
|
||||
{
|
||||
$array = Base::json2array(self::string(self::init()->tokenDecode($token)));
|
||||
$array['is_expired'] = Carbon::parse($array['expired_at'])->isBefore(Carbon::now());
|
||||
return $array;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 翻译
|
||||
* @param string $text
|
||||
* @param string|null $type
|
||||
* @return string
|
||||
*/
|
||||
public static function translate($text, $type = null)
|
||||
{
|
||||
if ($type) {
|
||||
$test = self::init()->translateSpecified($text, $type);
|
||||
} else {
|
||||
$test = self::init()->translate($text);
|
||||
}
|
||||
return self::string($test);
|
||||
}
|
||||
}
|
||||
93
app/Module/Lib/doo.h
Normal file
93
app/Module/Lib/doo.h
Normal file
@ -0,0 +1,93 @@
|
||||
/* Code generated by cmd/cgo; DO NOT EDIT. */
|
||||
|
||||
/* package command-line-arguments */
|
||||
|
||||
|
||||
#line 1 "cgo-builtin-export-prolog"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifndef GO_CGO_EXPORT_PROLOGUE_H
|
||||
#define GO_CGO_EXPORT_PROLOGUE_H
|
||||
|
||||
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
||||
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
/* Start of preamble from import "C" comments. */
|
||||
|
||||
|
||||
|
||||
|
||||
/* End of preamble from import "C" comments. */
|
||||
|
||||
|
||||
/* Start of boilerplate cgo prologue. */
|
||||
#line 1 "cgo-gcc-export-header-prolog"
|
||||
|
||||
#ifndef GO_CGO_PROLOGUE_H
|
||||
#define GO_CGO_PROLOGUE_H
|
||||
|
||||
typedef signed char GoInt8;
|
||||
typedef unsigned char GoUint8;
|
||||
typedef short GoInt16;
|
||||
typedef unsigned short GoUint16;
|
||||
typedef int GoInt32;
|
||||
typedef unsigned int GoUint32;
|
||||
typedef long long GoInt64;
|
||||
typedef unsigned long long GoUint64;
|
||||
typedef GoInt64 GoInt;
|
||||
typedef GoUint64 GoUint;
|
||||
typedef size_t GoUintptr;
|
||||
typedef float GoFloat32;
|
||||
typedef double GoFloat64;
|
||||
#ifdef _MSC_VER
|
||||
#include <complex.h>
|
||||
typedef _Fcomplex GoComplex64;
|
||||
typedef _Dcomplex GoComplex128;
|
||||
#else
|
||||
typedef float _Complex GoComplex64;
|
||||
typedef double _Complex GoComplex128;
|
||||
#endif
|
||||
|
||||
/*
|
||||
static assertion to make sure the file is being used on architecture
|
||||
at least with matching size of GoInt.
|
||||
*/
|
||||
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
|
||||
|
||||
#ifndef GO_CGO_GOSTRING_TYPEDEF
|
||||
typedef _GoString_ GoString;
|
||||
#endif
|
||||
typedef void *GoMap;
|
||||
typedef void *GoChan;
|
||||
typedef struct { void *t; void *v; } GoInterface;
|
||||
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
|
||||
|
||||
#endif
|
||||
|
||||
/* End of boilerplate cgo prologue. */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern void initialize();
|
||||
extern void setWorkDir(char* val);
|
||||
extern void setDefaultLanguage(char* val);
|
||||
extern void setUserToken(char* token);
|
||||
extern char* license();
|
||||
extern int userId();
|
||||
extern char* userExpiredAt();
|
||||
extern char* userEmail();
|
||||
extern char* userToken();
|
||||
extern char* tokenEncode(int userid, char* email, char* encrypt, int days);
|
||||
extern char* tokenDecode(char* token);
|
||||
extern char* translate(char* text);
|
||||
extern char* translateSpecified(char* text, char* langType);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
BIN
app/Module/Lib/doo.so
Normal file
BIN
app/Module/Lib/doo.so
Normal file
Binary file not shown.
@ -3,7 +3,7 @@ version: '3'
|
||||
services:
|
||||
php:
|
||||
container_name: "dootask-php-${APP_ID}"
|
||||
image: "kuaifan/php:swoole-8.0.rc2"
|
||||
image: "kuaifan/php:swoole-8.0.rc3"
|
||||
shm_size: "1024m"
|
||||
volumes:
|
||||
- ./docker/crontab/crontab.conf:/etc/supervisor/conf.d/crontab.conf
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user