mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-12 16:00:27 +00:00
style: lint
This commit is contained in:
@@ -20,11 +20,11 @@ class BaseAction<PayloadType, ReturnDataType> {
|
||||
return {
|
||||
valid: false,
|
||||
message: errorMessages.join('\n') as string || '未知错误'
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
valid: true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public async handle(payload: PayloadType): Promise<OB11Return<ReturnDataType | null>> {
|
||||
|
||||
@@ -5,7 +5,7 @@ import BaseAction from '../BaseAction';
|
||||
import { ActionName, BaseCheckResult } from '../types';
|
||||
import { NTQQUserApi } from '@/core/apis';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import Ajv from "ajv"
|
||||
import Ajv from 'ajv';
|
||||
// 设置在线状态
|
||||
|
||||
const SchemaData = {
|
||||
|
||||
@@ -13,7 +13,7 @@ export default class SetAvatar extends BaseAction<Payload, null> {
|
||||
actionName = ActionName.SetQQAvatar;
|
||||
// 用不着复杂检测
|
||||
protected async check(payload: Payload): Promise<BaseCheckResult> {
|
||||
if (!payload.file || typeof payload.file != "string") {
|
||||
if (!payload.file || typeof payload.file != 'string') {
|
||||
return {
|
||||
valid: false,
|
||||
message: 'file字段不能为空或者类型错误',
|
||||
|
||||
@@ -17,9 +17,9 @@ const SchemaData = {
|
||||
base64: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
headers: {
|
||||
type: ["string", "array"],
|
||||
type: ['string', 'array'],
|
||||
items: {
|
||||
type: "string"
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { log } from '@/common/utils/log';
|
||||
import BaseAction from '../BaseAction'
|
||||
import { ActionName } from '../types'
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { QuickAction, QuickActionEvent, handleQuickOperation } from '@/onebot11/server/postOB11Event';
|
||||
|
||||
interface Payload{
|
||||
@@ -9,9 +9,9 @@ interface Payload{
|
||||
}
|
||||
|
||||
export class GoCQHTTHandleQuickAction extends BaseAction<Payload, null>{
|
||||
actionName = ActionName.GoCQHTTP_HandleQuickAction
|
||||
actionName = ActionName.GoCQHTTP_HandleQuickAction;
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
handleQuickOperation(payload.context, payload.operation).then().catch(log);
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -5,61 +5,57 @@ import { NTQQGroupApi, WebApi } from '@/core/apis';
|
||||
import { unlink } from 'node:fs';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
group_id: { type: 'number' },
|
||||
content: { type: 'string' },
|
||||
image: { type: 'string' },
|
||||
pinned: { type: 'number' },
|
||||
confirmRequired: { type: 'number' }
|
||||
},
|
||||
required: ['group_id', 'content']
|
||||
type: 'object',
|
||||
properties: {
|
||||
group_id: { type: 'number' },
|
||||
content: { type: 'string' },
|
||||
image: { type: 'string' },
|
||||
pinned: { type: 'number' },
|
||||
confirmRequired: { type: 'number' }
|
||||
},
|
||||
required: ['group_id', 'content']
|
||||
} as const satisfies JSONSchema;
|
||||
|
||||
type Payload = FromSchema<typeof SchemaData>;
|
||||
|
||||
export class SendGroupNotice extends BaseAction<Payload, null> {
|
||||
actionName = ActionName.GoCQHTTP_SendGroupNotice;
|
||||
protected async _handle(payload: Payload) {
|
||||
let UploadImage: { id: string, width: number, height: number } | undefined = undefined;
|
||||
if (payload.image) {
|
||||
//公告图逻辑
|
||||
let Image_path, Image_errMsg, Image_IsLocal = false;
|
||||
let Uri2LocalRet = (await uri2local(payload.image));
|
||||
Image_errMsg = Uri2LocalRet.errMsg;
|
||||
Image_path = Uri2LocalRet.path;
|
||||
Image_IsLocal = Uri2LocalRet.isLocal;
|
||||
if (Image_errMsg) {
|
||||
throw `群公告${payload.image}设置失败,image字段可能格式不正确`;
|
||||
}
|
||||
if (!Image_path) {
|
||||
throw `群公告${payload.image}设置失败,获取资源失败`;
|
||||
}
|
||||
await checkFileReceived(Image_path, 5000); // 文件不存在QQ会崩溃,需要提前判断
|
||||
let ImageUploadResult = await NTQQGroupApi.uploadGroupBulletinPic(payload.group_id.toString(), Image_path);
|
||||
if (ImageUploadResult.errCode != 0) {
|
||||
throw `群公告${payload.image}设置失败,图片上传失败`;
|
||||
}
|
||||
if (!Image_IsLocal) {
|
||||
unlink(Image_path, () => { });
|
||||
}
|
||||
UploadImage = ImageUploadResult.picInfo;
|
||||
}
|
||||
let Notice_Pinned = 0;
|
||||
let Notice_confirmRequired = 0;
|
||||
if (!payload.pinned) {
|
||||
Notice_Pinned = 0;
|
||||
}
|
||||
if (!payload.confirmRequired) {
|
||||
Notice_confirmRequired = 0;
|
||||
}
|
||||
let PublishGroupBulletinResult = await NTQQGroupApi.publishGroupBulletin(payload.group_id.toString(), payload.content, UploadImage, Notice_Pinned, Notice_confirmRequired);
|
||||
|
||||
if (PublishGroupBulletinResult.result! = 0) {
|
||||
throw `设置群公告失败,错误信息:${PublishGroupBulletinResult.errMsg}`;
|
||||
}
|
||||
// 下面实现扬了
|
||||
//await WebApi.setGroupNotice(payload.group_id, payload.content) ;
|
||||
return null;
|
||||
actionName = ActionName.GoCQHTTP_SendGroupNotice;
|
||||
protected async _handle(payload: Payload) {
|
||||
let UploadImage: { id: string, width: number, height: number } | undefined = undefined;
|
||||
if (payload.image) {
|
||||
//公告图逻辑
|
||||
const { errMsg, path, isLocal } = (await uri2local(payload.image));
|
||||
if (errMsg) {
|
||||
throw `群公告${payload.image}设置失败,image字段可能格式不正确`;
|
||||
}
|
||||
if (!path) {
|
||||
throw `群公告${payload.image}设置失败,获取资源失败`;
|
||||
}
|
||||
await checkFileReceived(path, 5000); // 文件不存在QQ会崩溃,需要提前判断
|
||||
const ImageUploadResult = await NTQQGroupApi.uploadGroupBulletinPic(payload.group_id.toString(), path);
|
||||
if (ImageUploadResult.errCode != 0) {
|
||||
throw `群公告${payload.image}设置失败,图片上传失败`;
|
||||
}
|
||||
if (!isLocal) {
|
||||
unlink(path, () => { });
|
||||
}
|
||||
UploadImage = ImageUploadResult.picInfo;
|
||||
}
|
||||
let Notice_Pinned = 0;
|
||||
let Notice_confirmRequired = 0;
|
||||
if (!payload.pinned) {
|
||||
Notice_Pinned = 0;
|
||||
}
|
||||
if (!payload.confirmRequired) {
|
||||
Notice_confirmRequired = 0;
|
||||
}
|
||||
const PublishGroupBulletinResult = await NTQQGroupApi.publishGroupBulletin(payload.group_id.toString(), payload.content, UploadImage, Notice_Pinned, Notice_confirmRequired);
|
||||
|
||||
if (PublishGroupBulletinResult.result != 0) {
|
||||
throw `设置群公告失败,错误信息:${PublishGroupBulletinResult.errMsg}`;
|
||||
}
|
||||
// 下面实现扬了
|
||||
//await WebApi.setGroupNotice(payload.group_id, payload.content) ;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ export class GetGroupSystemMsg extends BaseAction<void, any> {
|
||||
actionName = ActionName.GetGroupSystemMsg;
|
||||
protected async _handle(payload: void) {
|
||||
// 默认10条 该api未完整实现 包括响应数据规范化 类型规范化
|
||||
let SingleScreenNotifies = await NTQQGroupApi.getSingleScreenNotifies(10);
|
||||
let retData: any = { InvitedRequest: [], join_requests: [] };
|
||||
const SingleScreenNotifies = await NTQQGroupApi.getSingleScreenNotifies(10);
|
||||
const retData: any = { InvitedRequest: [], join_requests: [] };
|
||||
for (const SSNotify of SingleScreenNotifies) {
|
||||
if (SSNotify.type == 1) {
|
||||
retData.InvitedRequest.push({
|
||||
|
||||
@@ -104,7 +104,7 @@ const _handlers: {
|
||||
// File service
|
||||
|
||||
[OB11MessageDataType.image]: async (sendMsg, context) => {
|
||||
let PicEle = await SendMsgElementConstructor.pic(
|
||||
const PicEle = await SendMsgElementConstructor.pic(
|
||||
(await handleOb11FileLikeMessage(sendMsg, context)).path,
|
||||
sendMsg.data.summary || '',
|
||||
sendMsg.data.subType || 0
|
||||
|
||||
@@ -51,16 +51,16 @@ export async function sendMsg(peer: Peer, sendElements: SendMessageElement[], de
|
||||
totalSize += fs.statSync(fileElement.videoElement.filePath).size;
|
||||
}
|
||||
if (fileElement.elementType === ElementType.PIC) {
|
||||
totalSize += fs.statSync(fileElement.picElement.sourcePath).size
|
||||
totalSize += fs.statSync(fileElement.picElement.sourcePath).size;
|
||||
}
|
||||
}
|
||||
//且 PredictTime ((totalSize / 1024 / 512) * 1000)不等于Nan
|
||||
let PredictTime = totalSize / 1024 / 512 * 1000;
|
||||
const PredictTime = totalSize / 1024 / 512 * 1000;
|
||||
if (!Number.isNaN(PredictTime)) {
|
||||
timeout += PredictTime// 5S Basic Timeout + PredictTime( For File 512kb/s )
|
||||
timeout += PredictTime;// 5S Basic Timeout + PredictTime( For File 512kb/s )
|
||||
}
|
||||
} catch (e) {
|
||||
logError("发送消息计算预计时间异常", e);
|
||||
logError('发送消息计算预计时间异常', e);
|
||||
}
|
||||
const returnMsg = await NTQQMsgApi.sendMsg(peer, sendElements, waitComplete, timeout);
|
||||
try {
|
||||
|
||||
@@ -5,40 +5,40 @@ import { selfInfo } from '@/core/data';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
delay: { type: 'number' }
|
||||
},
|
||||
required: ['delay']
|
||||
type: 'object',
|
||||
properties: {
|
||||
delay: { type: 'number' }
|
||||
},
|
||||
required: ['delay']
|
||||
} as const satisfies JSONSchema;
|
||||
|
||||
type Payload = FromSchema<typeof SchemaData>;
|
||||
|
||||
export class Reboot extends BaseAction<Payload, null> {
|
||||
actionName = ActionName.Reboot;
|
||||
actionName = ActionName.Reboot;
|
||||
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
if (payload.delay) {
|
||||
setTimeout(() => {
|
||||
rebootWithQuickLogin(selfInfo.uin);
|
||||
}, payload.delay);
|
||||
} else {
|
||||
rebootWithQuickLogin(selfInfo.uin);
|
||||
}
|
||||
return null;
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
if (payload.delay) {
|
||||
setTimeout(() => {
|
||||
rebootWithQuickLogin(selfInfo.uin);
|
||||
}, payload.delay);
|
||||
} else {
|
||||
rebootWithQuickLogin(selfInfo.uin);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
export class RebootNormol extends BaseAction<Payload, null> {
|
||||
actionName = ActionName.RebootNormol;
|
||||
actionName = ActionName.RebootNormol;
|
||||
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
if (payload.delay) {
|
||||
setTimeout(() => {
|
||||
rebootWithNormolLogin();
|
||||
}, payload.delay);
|
||||
} else {
|
||||
rebootWithNormolLogin();
|
||||
}
|
||||
return null;
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
if (payload.delay) {
|
||||
setTimeout(() => {
|
||||
rebootWithNormolLogin();
|
||||
}, payload.delay);
|
||||
} else {
|
||||
rebootWithNormolLogin();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ export enum ActionName {
|
||||
CleanCache = 'clean_cache',
|
||||
GetCookies = 'get_cookies',
|
||||
// 以下为go-cqhttp api
|
||||
GoCQHTTP_HandleQuickAction = ".handle_quick_operation",
|
||||
GoCQHTTP_HandleQuickAction = '.handle_quick_operation',
|
||||
GetGroupHonorInfo = 'get_group_honor_info',
|
||||
GoCQHTTP_GetEssenceMsg = 'get_essence_msg_list',
|
||||
GoCQHTTP_SendGroupNotice = '_send_group_notice',
|
||||
@@ -78,5 +78,5 @@ export enum ActionName {
|
||||
GoCQHTTP_GetGroupMsgHistory = 'get_group_msg_history',
|
||||
GoCQHTTP_GetForwardMsg = 'get_forward_msg',
|
||||
GetFriendMsgHistory = 'get_friend_msg_history',
|
||||
GetGroupSystemMsg = "get_group_system_msg"
|
||||
GetGroupSystemMsg = 'get_group_system_msg'
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { selfInfo } from "@/core/data";
|
||||
import { logDebug, logError } from "@/common/utils/log";
|
||||
import { ConfigBase } from "@/common/utils/ConfigBase";
|
||||
import { json } from "stream/consumers";
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { selfInfo } from '@/core/data';
|
||||
import { logDebug, logError } from '@/common/utils/log';
|
||||
import { ConfigBase } from '@/common/utils/ConfigBase';
|
||||
import { json } from 'stream/consumers';
|
||||
|
||||
export interface OB11Config {
|
||||
http: {
|
||||
@@ -27,7 +27,7 @@ export interface OB11Config {
|
||||
|
||||
debug: boolean;
|
||||
heartInterval: number;
|
||||
messagePostFormat: "array" | "string";
|
||||
messagePostFormat: 'array' | 'string';
|
||||
enableLocalFile2Url: boolean;
|
||||
musicSignUrl: string;
|
||||
reportSelfMessage: boolean;
|
||||
@@ -41,16 +41,16 @@ export interface OB11Config {
|
||||
class Config extends ConfigBase<OB11Config> implements OB11Config {
|
||||
http = {
|
||||
enable: false,
|
||||
host: "",
|
||||
host: '',
|
||||
port: 3000,
|
||||
secret: "",
|
||||
secret: '',
|
||||
enableHeart: false,
|
||||
enablePost: false,
|
||||
postUrls: [],
|
||||
};
|
||||
ws = {
|
||||
enable: false,
|
||||
host: "",
|
||||
host: '',
|
||||
port: 3001,
|
||||
};
|
||||
reverseWs = {
|
||||
@@ -59,11 +59,11 @@ class Config extends ConfigBase<OB11Config> implements OB11Config {
|
||||
};
|
||||
debug = false;
|
||||
heartInterval = 30000;
|
||||
messagePostFormat: "array" | "string" = "array";
|
||||
messagePostFormat: 'array' | 'string' = 'array';
|
||||
enableLocalFile2Url = true;
|
||||
musicSignUrl = "";
|
||||
musicSignUrl = '';
|
||||
reportSelfMessage = false;
|
||||
token = "";
|
||||
token = '';
|
||||
|
||||
getConfigPath() {
|
||||
return path.join(this.getConfigDir(), `onebot11_${selfInfo.uin}.json`);
|
||||
|
||||
@@ -31,10 +31,11 @@ import { OB11GroupRecallNoticeEvent } from '@/onebot11/event/notice/OB11GroupRec
|
||||
import { logMessage, logNotice, logRequest } from '@/onebot11/log';
|
||||
import { OB11Message } from '@/onebot11/types';
|
||||
import { OB11LifeCycleEvent } from './event/meta/OB11LifeCycleEvent';
|
||||
import { Data as SysData } from '@/proto/SysMessage'
|
||||
import { Data as SysData } from '@/proto/SysMessage';
|
||||
import { OB11FriendPokeEvent, OB11GroupPokeEvent } from './event/notice/OB11PokeEvent';
|
||||
import { isEqual } from '@/common/utils/helper';
|
||||
//peer->cached(boolen)
|
||||
let PokeCache = new Map<string, boolean>();
|
||||
const PokeCache = new Map<string, boolean>();
|
||||
export class NapCatOnebot11 {
|
||||
private bootTime: number = Date.now() / 1000; // 秒
|
||||
|
||||
@@ -92,12 +93,12 @@ export class NapCatOnebot11 {
|
||||
// }
|
||||
// };
|
||||
try {
|
||||
let sysMsg = SysData.fromBinary(Buffer.from(protobufData));
|
||||
let peeruin = sysMsg.header[0].groupNumber;
|
||||
let peeruid = sysMsg.header[0].groupString;
|
||||
let MsgType = sysMsg.body[0].msgType;
|
||||
let subType0 = sysMsg.body[0].subType0;
|
||||
let subType1 = sysMsg.body[0].subType1;
|
||||
const sysMsg = SysData.fromBinary(Buffer.from(protobufData));
|
||||
const peeruin = sysMsg.header[0].groupNumber;
|
||||
const peeruid = sysMsg.header[0].groupString;
|
||||
const MsgType = sysMsg.body[0].msgType;
|
||||
const subType0 = sysMsg.body[0].subType0;
|
||||
const subType1 = sysMsg.body[0].subType1;
|
||||
let pokeEvent: OB11FriendPokeEvent | OB11GroupPokeEvent;
|
||||
//console.log(peeruid);
|
||||
if (MsgType == 528 && subType0 == 290) {
|
||||
@@ -106,7 +107,7 @@ export class NapCatOnebot11 {
|
||||
PokeCache.delete(peeruid);
|
||||
} else {
|
||||
PokeCache.set(peeruid, false);
|
||||
log("[私聊] 用户 ", peeruin, " 对你戳一戳");
|
||||
log('[私聊] 用户 ', peeruin, ' 对你戳一戳');
|
||||
pokeEvent = new OB11FriendPokeEvent(peeruin);
|
||||
postOB11Event(pokeEvent);
|
||||
}
|
||||
@@ -117,13 +118,13 @@ export class NapCatOnebot11 {
|
||||
PokeCache.delete(peeruid);
|
||||
} else {
|
||||
PokeCache.set(peeruid, false);
|
||||
log("[群聊] 群组 ", peeruin, " 戳一戳");
|
||||
log('[群聊] 群组 ', peeruin, ' 戳一戳');
|
||||
pokeEvent = new OB11GroupPokeEvent(peeruin);
|
||||
postOB11Event(pokeEvent);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
log("解析SysMsg异常", e);
|
||||
log('解析SysMsg异常', e);
|
||||
// console.log(e);
|
||||
//
|
||||
}
|
||||
@@ -184,15 +185,15 @@ export class NapCatOnebot11 {
|
||||
};
|
||||
groupListener.onMemberInfoChange = async (groupCode: string, changeType: number, members: Map<string, GroupMember>) => {
|
||||
// 如果自身是非管理员也许要从这里获取Delete 成员变动 待测试与验证
|
||||
let role = (await getGroupMember(groupCode, selfInfo.uin))?.role;
|
||||
let isPrivilege = role === 3 || role === 4;
|
||||
const role = (await getGroupMember(groupCode, selfInfo.uin))?.role;
|
||||
const isPrivilege = role === 3 || role === 4;
|
||||
for (const member of members.values()) {
|
||||
if (member?.isDelete && !isPrivilege) {
|
||||
const groupDecreaseEvent = new OB11GroupDecreaseEvent(parseInt(groupCode), parseInt(member.uin), 0, 'leave');// 不知道怎么出去的
|
||||
postOB11Event(groupDecreaseEvent, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
groupListener.onJoinGroupNotify = (...notify) => {
|
||||
// console.log('ob11 onJoinGroupNotify', notify);
|
||||
};
|
||||
@@ -253,21 +254,6 @@ export class NapCatOnebot11 {
|
||||
}
|
||||
async SetConfig(NewOb11: OB11Config) {
|
||||
try {
|
||||
function isEqual(obj1: any, obj2: any) {
|
||||
if (obj1 === obj2) return true;
|
||||
if (obj1 == null || obj2 == null) return false;
|
||||
if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return obj1 === obj2;
|
||||
|
||||
const keys1 = Object.keys(obj1);
|
||||
const keys2 = Object.keys(obj2);
|
||||
|
||||
if (keys1.length !== keys2.length) return false;
|
||||
|
||||
for (let key of keys1) {
|
||||
if (!isEqual(obj1[key], obj2[key])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// if (!NewOb11 || typeof NewOb11 !== 'object') {
|
||||
// throw new Error('Invalid configuration object');
|
||||
// }
|
||||
|
||||
@@ -170,26 +170,26 @@ async function handleGroupRequest(request: OB11GroupRequestEvent, quickAction: Q
|
||||
groupNotifies[request.flag],
|
||||
quickAction.approve ? GroupRequestOperateTypes.approve : GroupRequestOperateTypes.reject,
|
||||
quickAction.reason,
|
||||
).then().catch(logError)
|
||||
).then().catch(logError);
|
||||
}
|
||||
}
|
||||
async function handleFriendRequest(request: OB11FriendRequestEvent, quickAction: QuickActionFriendRequest) {
|
||||
if (!isNull(quickAction.approve)) {
|
||||
NTQQFriendApi.handleFriendRequest(friendRequests[request.flag], !!quickAction.approve).then().catch(logError)
|
||||
NTQQFriendApi.handleFriendRequest(friendRequests[request.flag], !!quickAction.approve).then().catch(logError);
|
||||
}
|
||||
}
|
||||
export async function handleQuickOperation(context: QuickActionEvent, quickAction: QuickAction) {
|
||||
if (context.post_type === 'message') {
|
||||
handleMsg(context as OB11Message, quickAction).then().catch(logError)
|
||||
handleMsg(context as OB11Message, quickAction).then().catch(logError);
|
||||
}
|
||||
if (context.post_type === 'request') {
|
||||
const friendRequest = context as OB11FriendRequestEvent;
|
||||
const groupRequest = context as OB11GroupRequestEvent;
|
||||
if ((friendRequest).request_type === 'friend') {
|
||||
handleFriendRequest(friendRequest, quickAction).then().catch(logError)
|
||||
handleFriendRequest(friendRequest, quickAction).then().catch(logError);
|
||||
}
|
||||
else if (groupRequest.request_type === 'group') {
|
||||
handleGroupRequest(groupRequest, quickAction).then().catch(logError)
|
||||
handleGroupRequest(groupRequest, quickAction).then().catch(logError);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user