mirror of
https://gitee.com/dromara/sms4j.git
synced 2025-12-06 17:08:40 +08:00
feat: netease
This commit is contained in:
parent
f4b1ecb01a
commit
6028b004b3
@ -9,6 +9,7 @@ import org.dromara.sms4j.ctyun.config.CtyunConfig;
|
||||
import org.dromara.sms4j.emay.config.EmayConfig;
|
||||
import org.dromara.sms4j.huawei.config.HuaweiConfig;
|
||||
import org.dromara.sms4j.jdcloud.config.JdCloudConfig;
|
||||
import org.dromara.sms4j.netease.config.NeteaseConfig;
|
||||
import org.dromara.sms4j.provider.enumerate.SupplierType;
|
||||
import org.dromara.sms4j.tencent.config.TencentConfig;
|
||||
import org.dromara.sms4j.unisms.config.UniConfig;
|
||||
@ -29,7 +30,7 @@ public class SupplierSqlConfig {
|
||||
* readSqlConfig
|
||||
* <p>读取数据库配置信息
|
||||
* @author :Wind
|
||||
*/
|
||||
*/
|
||||
public static void readSqlConfig(){
|
||||
select = JDBCTool.selectConfig();
|
||||
}
|
||||
@ -38,7 +39,7 @@ public class SupplierSqlConfig {
|
||||
* refreshSqlConfig
|
||||
* <p>读取并刷新数据库配置
|
||||
* @author :Wind
|
||||
*/
|
||||
*/
|
||||
public static void refreshSqlConfig(){
|
||||
readSqlConfig();
|
||||
alibaba();
|
||||
@ -50,6 +51,7 @@ public class SupplierSqlConfig {
|
||||
cloopen();
|
||||
emay();
|
||||
ctyun();
|
||||
netease();
|
||||
}
|
||||
|
||||
public SupplierSqlConfig() {
|
||||
@ -64,17 +66,17 @@ public class SupplierSqlConfig {
|
||||
* alibaba
|
||||
* <p>数据库读取并设置阿里云短信
|
||||
* @author :Wind
|
||||
*/
|
||||
*/
|
||||
public static void alibaba(){
|
||||
AlibabaConfig alibabaConfig = SmsUtil.jsonForObject(select.get(SupplierType.ALIBABA.getName()), AlibabaConfig.class);
|
||||
SupplierFactory.setAlibabaConfig(alibabaConfig);
|
||||
SupplierFactory.setAlibabaConfig(alibabaConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* huawei
|
||||
* <p>数据库读取并设置华为短信
|
||||
* @author :Wind
|
||||
*/
|
||||
*/
|
||||
public static void huawei(){
|
||||
HuaweiConfig huaweiConfig = SmsUtil.jsonForObject(select.get(SupplierType.HUAWEI.getName()), HuaweiConfig.class);
|
||||
SupplierFactory.setHuaweiConfig(huaweiConfig);
|
||||
@ -84,7 +86,7 @@ public class SupplierSqlConfig {
|
||||
* jingdong
|
||||
* <p>数据库读取并设置京东短信
|
||||
* @author :Wind
|
||||
*/
|
||||
*/
|
||||
public static void jingdong(){
|
||||
JdCloudConfig jdCloudConfig = SmsUtil.jsonForObject(select.get(SupplierType.JD_CLOUD.getName()), JdCloudConfig.class);
|
||||
SupplierFactory.setJdCloudConfig(jdCloudConfig);
|
||||
@ -94,7 +96,7 @@ public class SupplierSqlConfig {
|
||||
* tencent
|
||||
* <p>数据库读取并设置腾讯短信
|
||||
* @author :Wind
|
||||
*/
|
||||
*/
|
||||
public static void tencent(){
|
||||
TencentConfig tencentConfig = SmsUtil.jsonForObject(select.get(SupplierType.TENCENT.getName()), TencentConfig.class);
|
||||
SupplierFactory.setTencentConfig(tencentConfig);
|
||||
@ -104,7 +106,7 @@ public class SupplierSqlConfig {
|
||||
* uniSms
|
||||
* <p>数据库读取并设置合一短信配置
|
||||
* @author :Wind
|
||||
*/
|
||||
*/
|
||||
public static void uniSms(){
|
||||
UniConfig uniConfig = SmsUtil.jsonForObject(select.get(SupplierType.UNI_SMS.getName()), UniConfig.class);
|
||||
SupplierFactory.setUniConfig(uniConfig);
|
||||
@ -114,7 +116,7 @@ public class SupplierSqlConfig {
|
||||
* yunPian
|
||||
* <p>数据库读取并设置云片短信
|
||||
* @author :Wind
|
||||
*/
|
||||
*/
|
||||
public static void yunPian(){
|
||||
YunpianConfig yunpianConfig = SmsUtil.jsonForObject(select.get(SupplierType.YUNPIAN.getName()), YunpianConfig.class);
|
||||
SupplierFactory.setYunpianConfig(yunpianConfig);
|
||||
@ -124,7 +126,7 @@ public class SupplierSqlConfig {
|
||||
* cloopen
|
||||
* <p>数据库读取并设置容联云短信
|
||||
* @author :Wind
|
||||
*/
|
||||
*/
|
||||
public static void cloopen(){
|
||||
CloopenConfig cloopenConfig = SmsUtil.jsonForObject(select.get(SupplierType.CLOOPEN.getName()), CloopenConfig.class);
|
||||
SupplierFactory.setCloopenConfig(cloopenConfig);
|
||||
@ -143,9 +145,19 @@ public class SupplierSqlConfig {
|
||||
* ctyun
|
||||
* <p>数据库读取并设置天翼云短信
|
||||
* @author :Wind
|
||||
*/
|
||||
*/
|
||||
public static void ctyun(){
|
||||
CtyunConfig ctyunConfig = SmsUtil.jsonForObject(select.get(SupplierType.CTYUN.getName()), CtyunConfig.class);
|
||||
SupplierFactory.setCtyunConfig(ctyunConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* netease
|
||||
* <p>数据库读取并设置网易云短信
|
||||
* @author : Adam
|
||||
*/
|
||||
public static void netease(){
|
||||
NeteaseConfig neteaseConfig = SmsUtil.jsonForObject(select.get(SupplierType.NETEASE.getName()), NeteaseConfig.class);
|
||||
SupplierFactory.setNeteaseConfig(neteaseConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,8 @@ import org.dromara.sms4j.huawei.config.HuaweiConfig;
|
||||
import org.dromara.sms4j.huawei.config.HuaweiFactory;
|
||||
import org.dromara.sms4j.jdcloud.config.JdCloudConfig;
|
||||
import org.dromara.sms4j.jdcloud.config.JdCloudFactory;
|
||||
import org.dromara.sms4j.netease.config.NeteaseConfig;
|
||||
import org.dromara.sms4j.netease.config.NeteaseFactory;
|
||||
import org.dromara.sms4j.provider.enumerate.SupplierType;
|
||||
import org.dromara.sms4j.tencent.config.TencentConfig;
|
||||
import org.dromara.sms4j.tencent.config.TencentFactory;
|
||||
@ -96,12 +98,19 @@ public class SupplierFactory {
|
||||
return CtyunFactory.instance().getConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* 网易云信配置获取
|
||||
*/
|
||||
public static NeteaseConfig getNeteaseConfig() {
|
||||
return NeteaseFactory.instance().getConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* setSupplierConfig
|
||||
* <p>通用化set,用于设置
|
||||
* @param t 配置对象
|
||||
* @author :Wind
|
||||
*/
|
||||
*/
|
||||
public static <T extends SupplierConfig> void setSupplierConfig(T t) {
|
||||
if (t instanceof AlibabaConfig) {
|
||||
setAlibabaConfig((AlibabaConfig) t);
|
||||
@ -121,7 +130,9 @@ public class SupplierFactory {
|
||||
setEmayConfig((EmayConfig) t);
|
||||
} else if (t instanceof CtyunConfig) {
|
||||
setCtyunConfig((CtyunConfig) t);
|
||||
}else {
|
||||
} else if (t instanceof NeteaseConfig) {
|
||||
setNeteaseConfig((NeteaseConfig) t);
|
||||
} else {
|
||||
throw new SmsBlendException("Loading failure! Please check the configuration type.");
|
||||
}
|
||||
}
|
||||
@ -197,4 +208,12 @@ public class SupplierFactory {
|
||||
CtyunFactory.instance().setConfig(ctyunConfig);
|
||||
SmsFactory.refresh(SupplierType.CTYUN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 neteaseConfig
|
||||
*/
|
||||
public static void setNeteaseConfig(NeteaseConfig neteaseConfig) {
|
||||
NeteaseFactory.instance().setConfig(neteaseConfig);
|
||||
SmsFactory.refresh(SupplierType.NETEASE);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
package org.dromara.sms4j.netease.config;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import org.dromara.sms4j.api.universal.SupplierConfig;
|
||||
import org.dromara.sms4j.comm.config.BaseConfig;
|
||||
|
||||
/**
|
||||
* @author adam
|
||||
*/
|
||||
@Data
|
||||
@SuperBuilder
|
||||
public class NeteaseConfig extends BaseConfig implements SupplierConfig {
|
||||
|
||||
/**
|
||||
* 模板变量名称
|
||||
*/
|
||||
private String templateId;
|
||||
|
||||
/**
|
||||
* appKey
|
||||
*/
|
||||
private String appKey;
|
||||
|
||||
/**
|
||||
* secret
|
||||
*/
|
||||
private String secret;
|
||||
|
||||
/**
|
||||
* 模板短信请求地址
|
||||
*/
|
||||
@Builder.Default
|
||||
private String templateUrl = "https://api.netease.im/sms/sendtemplate.action";
|
||||
|
||||
|
||||
/**
|
||||
* 验证码短信请求地址
|
||||
*/
|
||||
@Builder.Default
|
||||
private String codeUrl = "https://api.netease.im/sms/sendcode.action";
|
||||
|
||||
/**
|
||||
* 验证码验证请求地址
|
||||
*/
|
||||
@Builder.Default
|
||||
private String verifyUrl = "https://api.netease.im/sms/verifycode.action";
|
||||
|
||||
/**
|
||||
* 是否需要支持短信上行。true:需要,false:不需要
|
||||
* 说明:如果开通了短信上行抄送功能,该参数需要设置为true,其它情况设置无效
|
||||
*/
|
||||
private final Boolean needUp;
|
||||
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
package org.dromara.sms4j.netease.config;
|
||||
|
||||
import org.dromara.sms4j.comm.factory.BeanFactory;
|
||||
import org.dromara.sms4j.netease.service.NeteaseSmsImpl;
|
||||
import org.dromara.sms4j.provider.base.BaseProviderFactory;
|
||||
|
||||
/**
|
||||
* NeteaseSmsConfig
|
||||
* <p> 网易云信短信
|
||||
*
|
||||
* @author :adam
|
||||
* 2023-05-30
|
||||
**/
|
||||
public class NeteaseFactory implements BaseProviderFactory<NeteaseSmsImpl, NeteaseConfig> {
|
||||
|
||||
private static NeteaseSmsImpl neteaseSms;
|
||||
|
||||
private static final NeteaseFactory INSTANCE = new NeteaseFactory();
|
||||
|
||||
private static final class ConfigHolder {
|
||||
private static NeteaseConfig config = NeteaseConfig.builder().build();
|
||||
}
|
||||
|
||||
private NeteaseFactory() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取建造者实例
|
||||
* @return 建造者实例
|
||||
*/
|
||||
public static NeteaseFactory instance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 建造一个网易云的短信实现
|
||||
*/
|
||||
@Override
|
||||
public NeteaseSmsImpl createSms(NeteaseConfig neteaseConfig) {
|
||||
if (neteaseSms == null) {
|
||||
neteaseSms = new NeteaseSmsImpl(
|
||||
neteaseConfig,
|
||||
BeanFactory.getExecutor(),
|
||||
BeanFactory.getDelayedTime()
|
||||
);
|
||||
}
|
||||
return neteaseSms;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新对象
|
||||
*/
|
||||
@Override
|
||||
public NeteaseSmsImpl refresh(NeteaseConfig neteaseConfig) {
|
||||
neteaseSms = new NeteaseSmsImpl(
|
||||
neteaseConfig,
|
||||
BeanFactory.getExecutor(),
|
||||
BeanFactory.getDelayedTime()
|
||||
);
|
||||
return neteaseSms;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置
|
||||
* @return 配置对象
|
||||
*/
|
||||
@Override
|
||||
public NeteaseConfig getConfig() {
|
||||
return ConfigHolder.config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置配置
|
||||
* @param config 配置对象
|
||||
*/
|
||||
@Override
|
||||
public void setConfig(NeteaseConfig config) {
|
||||
ConfigHolder.config = config;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,134 @@
|
||||
package org.dromara.sms4j.netease.service;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.AbstractSmsBlend;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.comm.annotation.Restricted;
|
||||
import org.dromara.sms4j.comm.delayedTime.DelayedTime;
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
import org.dromara.sms4j.netease.config.NeteaseConfig;
|
||||
import org.dromara.sms4j.netease.utils.NeteaseUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* Created with IntelliJ IDEA.
|
||||
*
|
||||
* @author adam
|
||||
* @create 2023/5/29 17:34
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
public class NeteaseSmsImpl extends AbstractSmsBlend {
|
||||
|
||||
private NeteaseConfig config;
|
||||
|
||||
public NeteaseSmsImpl(NeteaseConfig config, Executor pool, DelayedTime delayed) {
|
||||
super(pool, delayed);
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param phone 接收短信的手机号
|
||||
* @param message 短信变量 ["xxx","yyy"]
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@Restricted
|
||||
public SmsResponse sendMessage(String phone, String message) {
|
||||
Optional.ofNullable(phone).orElseThrow(() -> new SmsBlendException("手机号不能为空"));
|
||||
Optional.ofNullable(config.getTemplateId()).orElseThrow(() -> new SmsBlendException("模板ID不能为空"));
|
||||
return getSmsResponse(config.getTemplateUrl(), Collections.singletonList(phone), message, config.getTemplateId());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param phone
|
||||
* @param templateId 模板id
|
||||
* @param messages 短信变量 key为默认 params value为模板变量值 ["xxx","yyy"]
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@Restricted
|
||||
public SmsResponse sendMessage(String phone, String templateId, LinkedHashMap<String, String> messages) {
|
||||
Optional.ofNullable(phone).orElseThrow(() -> new SmsBlendException("手机号不能为空"));
|
||||
Optional.ofNullable(config.getTemplateId()).orElseThrow(() -> new SmsBlendException("模板ID不能为空"));
|
||||
String messageStr = messages.get("params");
|
||||
return getSmsResponse(config.getTemplateUrl(), Collections.singletonList(phone), messageStr, templateId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Restricted
|
||||
public SmsResponse massTexting(List<String> phones, String message) {
|
||||
if (phones.size() < 1) {
|
||||
throw new SmsBlendException("手机号不能为空");
|
||||
}
|
||||
if (phones.size() > 100) {
|
||||
throw new SmsBlendException("单次发送超过最大发送上限,建议每次群发短信人数低于100");
|
||||
}
|
||||
Optional.ofNullable(config.getTemplateId()).orElseThrow(() -> new SmsBlendException("模板ID不能为空"));
|
||||
return getSmsResponse(config.getTemplateUrl(), phones, config.getTemplateId(), message);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Restricted
|
||||
public SmsResponse massTexting(List<String> phones, String templateId, LinkedHashMap<String, String> messages) {
|
||||
if (phones.size() > 100) {
|
||||
throw new SmsBlendException("单次发送超过最大发送上限,建议每次群发短信人数低于100");
|
||||
}
|
||||
Optional.ofNullable(config.getTemplateId()).orElseThrow(() -> new SmsBlendException("模板ID不能为空"));
|
||||
String messageStr = messages.get("message");
|
||||
return getSmsResponse(config.getTemplateUrl(), phones, messageStr, templateId);
|
||||
}
|
||||
|
||||
private SmsResponse getSmsResponse(String requestUrl, List<String> phones, String message, String templateId) {
|
||||
AtomicReference<SmsResponse> reference = new AtomicReference<>();
|
||||
String nonce = IdUtil.fastSimpleUUID();
|
||||
String curTime = String.valueOf(DateUtil.currentSeconds());
|
||||
String checkSum = NeteaseUtils.getCheckSum(config.getSecret(), nonce, curTime);
|
||||
Map<String, Object> body = new LinkedHashMap<>(4);
|
||||
body.put("templateid", templateId);
|
||||
body.put("mobiles", JSONArray.parseArray(JSON.toJSONString(phones)).toJSONString());
|
||||
body.put("params", message);
|
||||
body.put("needUp", config.getNeedUp());
|
||||
http.post(requestUrl)
|
||||
.addHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||
.addHeader("AppKey", config.getAppKey())
|
||||
.addHeader("Nonce", nonce)
|
||||
.addHeader("CurTime", curTime)
|
||||
.addHeader("CheckSum", checkSum)
|
||||
.addBody(body)
|
||||
.onSuccess(((data, req, res) -> {
|
||||
reference.set(this.getResponse(res.get(JSONObject.class)));
|
||||
}))
|
||||
.onError((ex, req, res) -> {
|
||||
reference.set(this.getResponse(res.get(JSONObject.class)));
|
||||
})
|
||||
.execute();
|
||||
return reference.get();
|
||||
}
|
||||
|
||||
private SmsResponse getResponse(JSONObject jsonObject) {
|
||||
SmsResponse response = new SmsResponse();
|
||||
Integer code = jsonObject.getInteger("code");
|
||||
if (code > 200) {
|
||||
response.setErrorCode(String.valueOf(code));
|
||||
response.setErrMessage(jsonObject.getString("msg"));
|
||||
} else {
|
||||
response.setCode(String.valueOf(code));
|
||||
response.setMessage(jsonObject.getString("msg"));
|
||||
response.setData(jsonObject.get("obj"));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
package org.dromara.sms4j.netease.utils;
|
||||
|
||||
import org.dromara.sms4j.comm.exception.SmsBlendException;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
/**
|
||||
* Created with IntelliJ IDEA.
|
||||
*
|
||||
* @author adam
|
||||
* @create 2023/5/29 17:49
|
||||
*/
|
||||
|
||||
public class NeteaseUtils {
|
||||
|
||||
/**
|
||||
* 计算并获取CheckSum
|
||||
* @param appSecret
|
||||
* @param nonce
|
||||
* @param curTime
|
||||
* @return
|
||||
*/
|
||||
public static String getCheckSum(String appSecret, String nonce, String curTime) {
|
||||
return encode("sha1", appSecret + nonce + curTime);
|
||||
}
|
||||
|
||||
private static String encode(String algorithm, String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
MessageDigest messageDigest
|
||||
= MessageDigest.getInstance(algorithm);
|
||||
messageDigest.update(value.getBytes());
|
||||
return getFormattedText(messageDigest.digest());
|
||||
} catch (Exception e) {
|
||||
throw new SmsBlendException(e.getMessage());
|
||||
}
|
||||
}
|
||||
private static String getFormattedText(byte[] bytes) {
|
||||
int len = bytes.length;
|
||||
StringBuilder buf = new StringBuilder(len * 2);
|
||||
for (int j = 0; j < len; j++) {
|
||||
buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
|
||||
buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5',
|
||||
'6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
||||
}
|
||||
@ -8,6 +8,7 @@ import org.dromara.sms4j.ctyun.config.CtyunFactory;
|
||||
import org.dromara.sms4j.emay.config.EmayFactory;
|
||||
import org.dromara.sms4j.huawei.config.HuaweiFactory;
|
||||
import org.dromara.sms4j.jdcloud.config.JdCloudFactory;
|
||||
import org.dromara.sms4j.netease.config.NeteaseFactory;
|
||||
import org.dromara.sms4j.provider.base.BaseProviderFactory;
|
||||
import org.dromara.sms4j.tencent.config.TencentFactory;
|
||||
import org.dromara.sms4j.unisms.config.UniFactory;
|
||||
@ -38,6 +39,8 @@ public enum SupplierType {
|
||||
EMAY("亿美软通", EmayFactory.instance()),
|
||||
/** 天翼云 */
|
||||
CTYUN("天翼云短信", CtyunFactory.instance()),
|
||||
/** 网易云信 */
|
||||
NETEASE("网易云短信", NeteaseFactory.instance())
|
||||
;
|
||||
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import org.dromara.sms4j.ctyun.config.CtyunConfig;
|
||||
import org.dromara.sms4j.emay.config.EmayConfig;
|
||||
import org.dromara.sms4j.huawei.config.HuaweiConfig;
|
||||
import org.dromara.sms4j.jdcloud.config.JdCloudConfig;
|
||||
import org.dromara.sms4j.netease.config.NeteaseConfig;
|
||||
import org.dromara.sms4j.tencent.config.TencentConfig;
|
||||
import org.dromara.sms4j.unisms.config.UniConfig;
|
||||
import org.dromara.sms4j.yunpian.config.YunpianConfig;
|
||||
@ -82,4 +83,14 @@ public class SupplierConfig {
|
||||
protected CtyunConfig ctyunConfig(){
|
||||
return SupplierFactory.getCtyunConfig();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 网易云信差异化配置
|
||||
*/
|
||||
@Bean
|
||||
@ConfigurationProperties(prefix = "sms.netease")
|
||||
protected NeteaseConfig neteaseConfig(){
|
||||
return SupplierFactory.getNeteaseConfig();
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user