OK
This commit is contained in:
@@ -1,5 +1,15 @@
|
||||
## 更新记录
|
||||
|
||||
### version: 2.7:
|
||||
|
||||
重构 **交易行 物品名称 [服务器]** 指令,改为免令牌图片查询。
|
||||
|
||||
新增 JX3BOX 交易行物品库缓存,用户输入物品名后先在本地模糊匹配物品 ID,再批量请求交易行价格接口。
|
||||
|
||||
交易行结果图改为列表展示,包含物品图标与名称、砖金银铜图标化价格、数量和数据时间。
|
||||
|
||||
价格接口未返回的物品不再展示,全部无价格数据时返回明确提示。
|
||||
|
||||
### version: 2.6:
|
||||
|
||||
新增 **资历 角色名称 [服务器]** 指令,支持两轮对话选择资历总览或分类总览,并以图片形式展示角色资历点数完成进度。
|
||||
|
||||
@@ -46,7 +46,7 @@ pip install -r requirements.txt
|
||||
- `apscheduler`:后台推送任务调度。
|
||||
- `matplotlib`:部分图像/数据展示依赖。
|
||||
|
||||
插件元信息在 `metadata.yaml` 中维护,当前版本为 `v2.6`,要求 AstrBot 版本 `>=4.11.0`。
|
||||
插件元信息在 `metadata.yaml` 中维护,当前版本为 `v2.7`,要求 AstrBot 版本 `>=4.11.0`。
|
||||
|
||||
## 插件配置
|
||||
|
||||
@@ -171,6 +171,7 @@ pip install -r requirements.txt
|
||||
日常
|
||||
诛恶
|
||||
资历 飞翔大野猪
|
||||
交易行 五行石 梦江南
|
||||
奇遇 飞翔大野猪
|
||||
未做奇遇 飞翔大野猪 梦江南
|
||||
战绩 飞翔大野猪 梦江南 33
|
||||
@@ -340,6 +341,7 @@ html_renderer.render_custom_template()
|
||||
- 避雷记录。
|
||||
- 推送任务状态缓存。
|
||||
- 资历菜单与资历点数基础数据缓存,默认缓存 30 天,接口失败时可使用旧缓存兜底。
|
||||
- 交易行物品库基础数据缓存,默认缓存 30 天,接口失败时可使用旧缓存兜底。
|
||||
|
||||
`core/sqlite.py` 封装了异步增删改查;`core/bilei_data.py` 基于该封装实现避雷数据管理。
|
||||
|
||||
@@ -360,7 +362,22 @@ html_renderer.render_custom_template()
|
||||
4. 展开菜单中单个 ID 和数组 ID,按资历点数计算 `已完成点数 / 总点数` 与百分比。
|
||||
5. 使用 `templates/zili.html` 渲染进度条图片。
|
||||
|
||||
### 9. 后台推送
|
||||
### 9. 交易行查询
|
||||
|
||||
`交易行 物品名称 [服务器]` 在 2.7 中完成重构,属于交易功能,免令牌,输出为图片。
|
||||
|
||||
实现流程:
|
||||
|
||||
1. 从 JX3BOX 交易行物品库接口读取所有可查询物品,并缓存到本地 SQLite。
|
||||
2. 用户输入物品名后,在本地物品库中按名称进行模糊匹配。
|
||||
3. 匹配结果按“完全匹配、前缀匹配、包含匹配”排序,默认最多取前 50 个物品 ID。
|
||||
4. 调用 `https://next2.jx3box.com/api/auction/` 批量查询指定服务器的交易行价格。
|
||||
5. 价格接口未返回的物品不展示;全部无价格数据时直接返回文本提示。
|
||||
6. 使用 `templates/jiaoyihang.html` 渲染列表图片,展示物品图标、物品名称、价格、数量和数据时间。
|
||||
|
||||
价格单位按铜钱换算为砖、金、银、铜,并使用 `templates/img` 下的 `zhuang.png`、`jin.png`、`yin.png`、`tong.png` 图标展示;0 砖不会显示砖图标。
|
||||
|
||||
### 10. 后台推送
|
||||
|
||||
`core/async_task.py` 使用 APScheduler 实现轮询推送。
|
||||
|
||||
|
||||
@@ -50,6 +50,37 @@ def gold_to_string(gold_amount):
|
||||
return "".join(parts)
|
||||
|
||||
|
||||
def gold_to_parts(gold_amount):
|
||||
"""将铜钱数拆成模板可渲染的货币片段"""
|
||||
try:
|
||||
amount = int(gold_amount or 0)
|
||||
except (TypeError, ValueError):
|
||||
amount = 0
|
||||
|
||||
if amount <= 0:
|
||||
return []
|
||||
|
||||
bricks = amount // 100000000
|
||||
gold = (amount % 100000000) // 10000
|
||||
silver = (amount % 10000) // 100
|
||||
copper = amount % 100
|
||||
|
||||
parts = []
|
||||
started = False
|
||||
for key, name, value in [
|
||||
("zhuang", "砖", bricks),
|
||||
("jin", "金", gold),
|
||||
("yin", "银", silver),
|
||||
("tong", "铜", copper),
|
||||
]:
|
||||
if value != 0:
|
||||
started = True
|
||||
if started:
|
||||
parts.append({"key": key, "name": name, "value": value})
|
||||
|
||||
return parts
|
||||
|
||||
|
||||
def week_to_num(week :str):
|
||||
week_map = {
|
||||
"一": 0,
|
||||
|
||||
+133
-28
@@ -9,7 +9,7 @@ import astrbot.api.message_components as Comp
|
||||
|
||||
from .request import APIClient
|
||||
from .sqlite import AsyncSQLiteDB
|
||||
from .fun_basic import load_template,gold_to_string,week_to_num,compare_date_str
|
||||
from .fun_basic import load_template,gold_to_string,gold_to_parts,week_to_num,compare_date_str
|
||||
|
||||
ROLE_RANK_NAMES = {
|
||||
"名士五十强",
|
||||
@@ -2411,7 +2411,7 @@ class JX3Service:
|
||||
return return_data
|
||||
|
||||
|
||||
async def _load_achievement_cache(self, key: str) -> tuple[Optional[Dict[str, Any]], bool]:
|
||||
async def _load_achievement_cache(self, key: str) -> tuple[Optional[Any], bool]:
|
||||
"""读取资历基础数据缓存,返回数据和是否已过期"""
|
||||
try:
|
||||
row = await self._cache_db.select_one("achievement_cache", "key=?", (key,))
|
||||
@@ -2432,7 +2432,7 @@ class JX3Service:
|
||||
return None, True
|
||||
|
||||
|
||||
async def _save_achievement_cache(self, key: str, payload: Dict[str, Any]):
|
||||
async def _save_achievement_cache(self, key: str, payload: Any):
|
||||
"""写入资历基础数据缓存"""
|
||||
try:
|
||||
await self._cache_db.execute(
|
||||
@@ -2471,6 +2471,74 @@ class JX3Service:
|
||||
return None
|
||||
|
||||
|
||||
async def _get_trade_item_groups(self) -> Optional[List[Dict[str, Any]]]:
|
||||
"""获取交易行物品库,优先使用未过期缓存"""
|
||||
cache_key = "trade_item_groups"
|
||||
cached, expired = await self._load_achievement_cache(cache_key)
|
||||
if isinstance(cached, list) and not expired:
|
||||
return cached
|
||||
|
||||
data = await self._base_request("jx3box_trade_items", "GET")
|
||||
if isinstance(data, list) and data:
|
||||
await self._save_achievement_cache(cache_key, data)
|
||||
return data
|
||||
|
||||
if isinstance(cached, list) and cached:
|
||||
logger.warning("交易行物品库接口失败,使用旧缓存")
|
||||
return cached
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _flatten_trade_items(self, groups: List[Dict[str, Any]]) -> list[Dict[str, Any]]:
|
||||
"""从交易行物品分组中提取可查询物品"""
|
||||
items = []
|
||||
for group in groups:
|
||||
if not isinstance(group, dict):
|
||||
continue
|
||||
for item in group.get("items", []) or []:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
item_id = item.get("item_id")
|
||||
label = item.get("label")
|
||||
if not item_id or not label:
|
||||
continue
|
||||
items.append(
|
||||
{
|
||||
"item_id": str(item_id),
|
||||
"label": str(label),
|
||||
"icon": str(item.get("icon") or ""),
|
||||
}
|
||||
)
|
||||
return items
|
||||
|
||||
|
||||
def _match_trade_items(self, items: list[Dict[str, Any]], keyword: str, limit: int = 50) -> list[Dict[str, Any]]:
|
||||
"""按物品名模糊匹配交易行物品"""
|
||||
keyword = (keyword or "").strip()
|
||||
if not keyword:
|
||||
return []
|
||||
|
||||
matched = []
|
||||
seen = set()
|
||||
for item in items:
|
||||
label = item.get("label", "")
|
||||
item_id = item.get("item_id", "")
|
||||
if keyword not in label or item_id in seen:
|
||||
continue
|
||||
seen.add(item_id)
|
||||
if label == keyword:
|
||||
rank = 0
|
||||
elif label.startswith(keyword):
|
||||
rank = 1
|
||||
else:
|
||||
rank = 2
|
||||
matched.append((rank, len(label), label, item))
|
||||
|
||||
matched.sort(key=lambda row: (row[0], row[1], row[2]))
|
||||
return [row[3] for row in matched[:limit]]
|
||||
|
||||
|
||||
def _flatten_achievement_ids(self, values: Any) -> list[int]:
|
||||
"""展开菜单中的单个资历 ID 和数组资历 ID"""
|
||||
result = []
|
||||
@@ -3016,36 +3084,73 @@ class JX3Service:
|
||||
"""区服交易行"""
|
||||
return_data = self._init_return_data()
|
||||
|
||||
# 1. 构造请求参数
|
||||
params = {"server": server, "name": name,"token": self.token}
|
||||
|
||||
# 2. 调用基础请求
|
||||
data: Optional[List[Dict[str, Any]]] = await self._base_request(
|
||||
"jx3_jiaoyihang", "GET", params=params
|
||||
)
|
||||
|
||||
if not data:
|
||||
return_data["msg"] = "未找到该物品"
|
||||
item_groups = await self._get_trade_item_groups()
|
||||
if not item_groups:
|
||||
return_data["msg"] = "交易行基础物品数据获取失败"
|
||||
return return_data
|
||||
|
||||
# 2. 数据处理
|
||||
result = []
|
||||
trade_items = self._flatten_trade_items(item_groups)
|
||||
matched_items = self._match_trade_items(trade_items, name, 50)
|
||||
if not matched_items:
|
||||
return_data["msg"] = "未找到匹配的交易行物品"
|
||||
return return_data
|
||||
|
||||
item_map = {item["item_id"]: item for item in matched_items}
|
||||
params = {
|
||||
"item_ids": list(item_map.keys()),
|
||||
"server": server,
|
||||
"aggregate_type": "hourly",
|
||||
}
|
||||
price_data: Optional[List[Dict[str, Any]]] = await self._base_request(
|
||||
"jx3_jiaoyihang", "POST", params=params, out_key=""
|
||||
)
|
||||
|
||||
if not price_data or not isinstance(price_data, list):
|
||||
return_data["msg"] = "未查询到交易行价格数据"
|
||||
return return_data
|
||||
|
||||
try:
|
||||
for item in data:
|
||||
if isinstance(item, dict):
|
||||
inner_list = item.get("data", [])
|
||||
first = inner_list[0] if inner_list else {}
|
||||
new_item = {
|
||||
"name": item.get("name"),
|
||||
"icon": f"https://icon.jx3box.com/icon/{item.get('icon')}.png",
|
||||
"sever": first.get("server"),
|
||||
"count": len(inner_list),
|
||||
"unit_price": gold_to_string(first.get("unit_price")),
|
||||
"created": datetime.fromtimestamp(first.get("created", "")).strftime("%Y-%m-%d %H:%M:%S"),
|
||||
result = []
|
||||
for price_item in price_data:
|
||||
if not isinstance(price_item, dict):
|
||||
continue
|
||||
|
||||
item_id = str(price_item.get("item_id") or "")
|
||||
base_item = item_map.get(item_id)
|
||||
if not base_item:
|
||||
continue
|
||||
|
||||
timestamp = price_item.get("timestamp")
|
||||
try:
|
||||
created = datetime.fromtimestamp(int(timestamp)).strftime("%Y-%m-%d %H:%M:%S")
|
||||
except (TypeError, ValueError, OSError):
|
||||
created = ""
|
||||
|
||||
result.append(
|
||||
{
|
||||
"item_id": item_id,
|
||||
"name": base_item.get("label", ""),
|
||||
"icon": f"https://icon.jx3box.com/icon/{base_item.get('icon', '')}.png",
|
||||
"server": price_item.get("server", server),
|
||||
"price": price_item.get("price", 0),
|
||||
"price_parts": gold_to_parts(price_item.get("price", 0)),
|
||||
"sample": price_item.get("sample", 0),
|
||||
"created": created,
|
||||
}
|
||||
result.append(new_item)
|
||||
return_data["data"]["list"] = result
|
||||
)
|
||||
|
||||
if not result:
|
||||
return_data["msg"] = "未查询到交易行价格数据"
|
||||
return return_data
|
||||
|
||||
return_data["data"] = {
|
||||
"search_name": name,
|
||||
"server": server,
|
||||
"matched_count": len(matched_items),
|
||||
"result_count": len(result),
|
||||
"list": result,
|
||||
"update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"处理交易行数据失败: {e}")
|
||||
return_data["msg"] = "处理交易行数据失败"
|
||||
|
||||
+13
-5
@@ -491,13 +491,21 @@
|
||||
}
|
||||
},
|
||||
"jx3_jiaoyihang":{
|
||||
"url":"https://www.jx3api.com/data/trade/market",
|
||||
"method":"GET",
|
||||
"description":"获取剑网三区服交易行数据",
|
||||
"url":"https://next2.jx3box.com/api/auction/",
|
||||
"method":"POST",
|
||||
"description":"获取剑网三区服交易行物品价格数据",
|
||||
"params":{
|
||||
"item_ids": [],
|
||||
"server": "梦江南",
|
||||
"name": "玄晶",
|
||||
"token": ""
|
||||
"aggregate_type": "hourly"
|
||||
}
|
||||
},
|
||||
"jx3box_trade_items":{
|
||||
"url":"https://cms.jx3box.com/api/cms/pvx/item/group",
|
||||
"method":"GET",
|
||||
"description":"魔盒交易行物品分组列表",
|
||||
"params":{
|
||||
"client": "std"
|
||||
}
|
||||
},
|
||||
"jx3_tiebawujia":{
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
name: astrbot_plugin_jx3
|
||||
display_name: 剑网三游戏数据查询工具
|
||||
desc: 通过接口调用剑网三API接口获取游戏数据,处理发送。
|
||||
version: v2.6
|
||||
version: v2.7
|
||||
author: 飞翔大野猪
|
||||
repo: https://github.com/qsc20001102/astrbot_plugin_jx3
|
||||
astrbot_version: ">=4.11.0"
|
||||
|
||||
@@ -837,7 +837,8 @@ body {
|
||||
</div>
|
||||
<div class="section-meta">
|
||||
<span class="mini">4 条</span>
|
||||
<span class="mini">需令牌 4</span>
|
||||
<span class="mini">免令牌 1</span>
|
||||
<span class="mini">需令牌 3</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="command-grid">
|
||||
@@ -973,10 +974,10 @@ body {
|
||||
<p class="desc">查询指定外观在服务器内的物价行情。</p>
|
||||
</article>
|
||||
|
||||
<article class="cmd-card advanced-source">
|
||||
<article class="cmd-card basic-source">
|
||||
<div class="cmd-top">
|
||||
<div class="cmd-name">交易行</div>
|
||||
<div class="source-tag">需令牌</div>
|
||||
<div class="source-tag">免令牌</div>
|
||||
</div>
|
||||
<code class="usage">交易行 物品名称 [服务器]</code>
|
||||
<p class="desc">查询指定物品交易行价格与挂单信息。</p>
|
||||
|
||||
+147
-57
@@ -5,119 +5,209 @@
|
||||
<title>剑网3交易行价格查询</title>
|
||||
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Microsoft YaHei", Arial, sans-serif;
|
||||
background: #f1f2f6;
|
||||
background: #f3f4f7;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
padding: 24px;
|
||||
color: #1f2933;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1220px;
|
||||
width: 1120px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e6e8ee;
|
||||
border-radius: 8px;
|
||||
padding: 22px 26px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 6px 18px rgba(35, 38, 47, 0.06);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 32px;
|
||||
margin-bottom: 10px;
|
||||
color: #222;
|
||||
margin: 0;
|
||||
font-size: 30px;
|
||||
line-height: 1.3;
|
||||
font-weight: 800;
|
||||
color: #20232a;
|
||||
}
|
||||
|
||||
.server-info {
|
||||
font-size: 16px;
|
||||
color: #555;
|
||||
margin-bottom: 25px;
|
||||
.meta {
|
||||
display: flex;
|
||||
gap: 18px;
|
||||
margin-top: 10px;
|
||||
font-size: 15px;
|
||||
color: #667085;
|
||||
}
|
||||
|
||||
/* ===== 表格样式 ===== */
|
||||
.table-wrapper {
|
||||
overflow-x: auto;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e6e8ee;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 6px 18px rgba(35, 38, 47, 0.06);
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: #ffffff;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,0.08);
|
||||
text-align: left;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
thead {
|
||||
background: #d93939;
|
||||
background: #b92b35;
|
||||
color: #ffffff;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 14px 12px;
|
||||
border-bottom: 1px solid #eee;
|
||||
th {
|
||||
padding: 14px 16px;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
background: #fff2f2;
|
||||
th:nth-child(2),
|
||||
th:nth-child(3),
|
||||
th:nth-child(4),
|
||||
td:nth-child(2),
|
||||
td:nth-child(3),
|
||||
td:nth-child(4) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ===== 物品列 ===== */
|
||||
.item-col {
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.item-col img {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
td {
|
||||
padding: 14px 16px;
|
||||
border-bottom: 1px solid #edf0f5;
|
||||
font-size: 15px;
|
||||
vertical-align: middle;
|
||||
border-radius: 4px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
tbody tr:nth-child(even) {
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 6px;
|
||||
flex: 0 0 auto;
|
||||
background: #edf0f5;
|
||||
}
|
||||
|
||||
.item-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-weight: 700;
|
||||
color: #20232a;
|
||||
}
|
||||
|
||||
.price {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
white-space: nowrap;
|
||||
font-weight: 700;
|
||||
color: #2f343d;
|
||||
}
|
||||
|
||||
.money-part {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.money-icon {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.sample {
|
||||
font-weight: 700;
|
||||
color: #2f343d;
|
||||
}
|
||||
|
||||
.time {
|
||||
color: #667085;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<h1>剑网3交易行价格查询</h1>
|
||||
<div class="header">
|
||||
<h1>交易行:{{ search_name }} · {{ server }}</h1>
|
||||
<div class="meta">
|
||||
<span>展示 {{ result_count }} 条价格数据</span>
|
||||
<span>匹配 {{ matched_count }} 个物品</span>
|
||||
<span>生成时间:{{ update_time }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>服务器</th>
|
||||
<th>图标</th>
|
||||
<th>物品名称</th>
|
||||
<th>价格</th>
|
||||
<th>数量</th>
|
||||
<th>时间</th>
|
||||
<th style="width: 44%;">物品</th>
|
||||
<th style="width: 24%;">物价</th>
|
||||
<th style="width: 12%;">数量</th>
|
||||
<th style="width: 20%;">数据时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for item in list %}
|
||||
<tr>
|
||||
<td>{{ item.sever }}</td>
|
||||
<td class="item-col">
|
||||
<img src="{{ item.icon }}" alt="icon">
|
||||
<td>
|
||||
<div class="item">
|
||||
<img class="item-icon" src="{{ item.icon }}" alt="">
|
||||
<div class="item-name">{{ item.name }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ item.unit_price }}</td>
|
||||
<td>{{ item.count }}</td>
|
||||
<td>{{ item.created }}</td>
|
||||
<td>
|
||||
<div class="price">
|
||||
{% if item.price_parts %}
|
||||
{% for part in item.price_parts %}
|
||||
<span class="money-part">
|
||||
<span>{{ part.value }}</span>
|
||||
<img class="money-icon" src="{{ icons.img[part.key] }}" alt="{{ part.name }}">
|
||||
</span>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<span>无价格</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td class="sample">{{ item.sample }}</td>
|
||||
<td class="time">{{ item.created }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user