|
@@ -7,7 +7,12 @@ from typing import Any
|
|
|
|
|
|
|
|
from pyrogram import filters
|
|
from pyrogram import filters
|
|
|
from pyrogram.enums import ParseMode
|
|
from pyrogram.enums import ParseMode
|
|
|
-from pyrogram.types import CallbackQuery, Message
|
|
|
|
|
|
|
+from pyrogram.types import (
|
|
|
|
|
+ CallbackQuery,
|
|
|
|
|
+ InlineKeyboardButton,
|
|
|
|
|
+ InlineKeyboardMarkup,
|
|
|
|
|
+ Message,
|
|
|
|
|
+)
|
|
|
|
|
|
|
|
from wbb import app, log
|
|
from wbb import app, log
|
|
|
from wbb.core.decorators.permissions import adminsOnly
|
|
from wbb.core.decorators.permissions import adminsOnly
|
|
@@ -17,6 +22,7 @@ from wbb.services.giveaways import (
|
|
|
create_and_publish_giveaway,
|
|
create_and_publish_giveaway,
|
|
|
finish_and_publish_giveaway,
|
|
finish_and_publish_giveaway,
|
|
|
format_giveaway_time,
|
|
format_giveaway_time,
|
|
|
|
|
+ giveaway_starts_at,
|
|
|
join_giveaway,
|
|
join_giveaway,
|
|
|
parse_giveaway_time,
|
|
parse_giveaway_time,
|
|
|
refresh_giveaway_message,
|
|
refresh_giveaway_message,
|
|
@@ -28,10 +34,12 @@ from wbb.services.giveaways import (
|
|
|
from wbb.services.member_identity import display_name
|
|
from wbb.services.member_identity import display_name
|
|
|
from wbb.utils.dbgiveaway import (
|
|
from wbb.utils.dbgiveaway import (
|
|
|
add_giveaway_ban,
|
|
add_giveaway_ban,
|
|
|
|
|
+ as_utc,
|
|
|
count_participants,
|
|
count_participants,
|
|
|
get_giveaway,
|
|
get_giveaway,
|
|
|
list_due_giveaways,
|
|
list_due_giveaways,
|
|
|
- list_participants,
|
|
|
|
|
|
|
+ list_giveaways_page,
|
|
|
|
|
+ list_participants_page,
|
|
|
list_pending_cancellations,
|
|
list_pending_cancellations,
|
|
|
list_running_giveaways,
|
|
list_running_giveaways,
|
|
|
list_unannounced_started_giveaways,
|
|
list_unannounced_started_giveaways,
|
|
@@ -45,6 +53,8 @@ from wbb.utils.functions import extract_user_and_reason
|
|
|
__MODULE__ = "抽奖管理"
|
|
__MODULE__ = "抽奖管理"
|
|
|
__HELP__ = """/giveaway <报名开始时间 ~ 开奖时间> | <奖项> | <标题> | [说明] | [最低积分] | [报名消耗] | [参与奖励]
|
|
__HELP__ = """/giveaway <报名开始时间 ~ 开奖时间> | <奖项> | <标题> | [说明] | [最低积分] | [报名消耗] | [参与奖励]
|
|
|
在当前群创建抽奖。
|
|
在当前群创建抽奖。
|
|
|
|
|
+群成员也可以直接发送“抽奖”查看当前抽奖、报名和参与者,
|
|
|
|
|
+或发送“开奖”“中奖名单”查看最近一次开奖结果。
|
|
|
|
|
|
|
|
奖项格式:`1`、`一等奖:1, 二等奖:3`,或 `一等奖:1:50`;
|
|
奖项格式:`1`、`一等奖:1, 二等奖:3`,或 `一等奖:1:50`;
|
|
|
最后的 50 表示该奖项每位中奖者获得 50 积分。
|
|
最后的 50 表示该奖项每位中奖者获得 50 积分。
|
|
@@ -61,7 +71,7 @@ __HELP__ = """/giveaway <报名开始时间 ~ 开奖时间> | <奖项> | <标题
|
|
|
/gunban <成员> - 解除抽奖参与限制。"""
|
|
/gunban <成员> - 解除抽奖参与限制。"""
|
|
|
|
|
|
|
|
MAX_TOTAL_WINNERS = 100
|
|
MAX_TOTAL_WINNERS = 100
|
|
|
-PARTICIPANT_PREVIEW_LIMIT = 30
|
|
|
|
|
|
|
+PARTICIPANTS_PAGE_SIZE = 10
|
|
|
SWEEP_INTERVAL_SECONDS = 30
|
|
SWEEP_INTERVAL_SECONDS = 30
|
|
|
_sweeper_started = False
|
|
_sweeper_started = False
|
|
|
|
|
|
|
@@ -147,6 +157,152 @@ def _participant_mention(participant: dict[str, Any]) -> str:
|
|
|
return f'<a href="tg://user?id={user_id}">{escape(str(label))}</a>'
|
|
return f'<a href="tg://user?id={user_id}">{escape(str(label))}</a>'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def _participants_markup(
|
|
|
|
|
+ giveaway_id: str, *, page: int, total_pages: int
|
|
|
|
|
+) -> InlineKeyboardMarkup | None:
|
|
|
|
|
+ if total_pages <= 1:
|
|
|
|
|
+ return None
|
|
|
|
|
+ buttons: list[InlineKeyboardButton] = []
|
|
|
|
|
+ if page > 1:
|
|
|
|
|
+ buttons.append(
|
|
|
|
|
+ InlineKeyboardButton(
|
|
|
|
|
+ "‹ 上一页",
|
|
|
|
|
+ callback_data=f"giveaway_participants_page:{giveaway_id}:{page - 1}",
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ buttons.append(
|
|
|
|
|
+ InlineKeyboardButton(
|
|
|
|
|
+ f"{page}/{total_pages}",
|
|
|
|
|
+ callback_data=f"giveaway_participants_noop:{giveaway_id}",
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ if page < total_pages:
|
|
|
|
|
+ buttons.append(
|
|
|
|
|
+ InlineKeyboardButton(
|
|
|
|
|
+ "下一页 ›",
|
|
|
|
|
+ callback_data=f"giveaway_participants_page:{giveaway_id}:{page + 1}",
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ return InlineKeyboardMarkup([buttons])
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+async def _render_participants_page(
|
|
|
|
|
+ giveaway_id: str, page: int
|
|
|
|
|
+) -> tuple[str, InlineKeyboardMarkup | None]:
|
|
|
|
|
+ page = max(1, page)
|
|
|
|
|
+ participants, total = await list_participants_page(
|
|
|
|
|
+ giveaway_id=giveaway_id,
|
|
|
|
|
+ active_only=True,
|
|
|
|
|
+ page=page,
|
|
|
|
|
+ page_size=PARTICIPANTS_PAGE_SIZE,
|
|
|
|
|
+ )
|
|
|
|
|
+ total_pages = max(1, (total + PARTICIPANTS_PAGE_SIZE - 1) // PARTICIPANTS_PAGE_SIZE)
|
|
|
|
|
+ if page > total_pages:
|
|
|
|
|
+ page = total_pages
|
|
|
|
|
+ participants, total = await list_participants_page(
|
|
|
|
|
+ giveaway_id=giveaway_id,
|
|
|
|
|
+ active_only=True,
|
|
|
|
|
+ page=page,
|
|
|
|
|
+ page_size=PARTICIPANTS_PAGE_SIZE,
|
|
|
|
|
+ )
|
|
|
|
|
+ lines = [
|
|
|
|
|
+ f"<b>抽奖 #{escape(giveaway_id)} 的参与者</b>",
|
|
|
|
|
+ f"第 {page}/{total_pages} 页 · 共 {total} 人",
|
|
|
|
|
+ ]
|
|
|
|
|
+ if participants:
|
|
|
|
|
+ offset = (page - 1) * PARTICIPANTS_PAGE_SIZE
|
|
|
|
|
+ lines.extend(
|
|
|
|
|
+ f"{offset + index}. {_participant_mention(item)}"
|
|
|
|
|
+ for index, item in enumerate(participants, 1)
|
|
|
|
|
+ )
|
|
|
|
|
+ else:
|
|
|
|
|
+ lines.append("暂时还没有参与者。")
|
|
|
|
|
+ return "\n".join(lines), _participants_markup(
|
|
|
|
|
+ giveaway_id, page=page, total_pages=total_pages
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _keyword_giveaways_markup(
|
|
|
|
|
+ giveaways: list[dict[str, Any]],
|
|
|
|
|
+) -> InlineKeyboardMarkup:
|
|
|
|
|
+ rows: list[list[InlineKeyboardButton]] = []
|
|
|
|
|
+ for giveaway in giveaways:
|
|
|
|
|
+ giveaway_id = giveaway["giveaway_id"]
|
|
|
|
|
+ title = str(giveaway["title"])
|
|
|
|
|
+ short_title = title if len(title) <= 12 else title[:11] + "…"
|
|
|
|
|
+ rows.append(
|
|
|
|
|
+ [
|
|
|
|
|
+ InlineKeyboardButton(
|
|
|
|
|
+ f"报名 · {short_title}",
|
|
|
|
|
+ callback_data=f"giveaway_join:{giveaway_id}",
|
|
|
|
|
+ ),
|
|
|
|
|
+ InlineKeyboardButton(
|
|
|
|
|
+ "查看参与者",
|
|
|
|
|
+ callback_data=f"giveaway_participants:{giveaway_id}",
|
|
|
|
|
+ ),
|
|
|
|
|
+ ]
|
|
|
|
|
+ )
|
|
|
|
|
+ return InlineKeyboardMarkup(rows)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _winners_markup(
|
|
|
|
|
+ giveaway_id: str, *, page: int, total_pages: int
|
|
|
|
|
+) -> InlineKeyboardMarkup | None:
|
|
|
|
|
+ if total_pages <= 1:
|
|
|
|
|
+ return None
|
|
|
|
|
+ buttons: list[InlineKeyboardButton] = []
|
|
|
|
|
+ if page > 1:
|
|
|
|
|
+ buttons.append(
|
|
|
|
|
+ InlineKeyboardButton(
|
|
|
|
|
+ "‹ 上一页",
|
|
|
|
|
+ callback_data=f"giveaway_winners_page:{giveaway_id}:{page - 1}",
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ buttons.append(
|
|
|
|
|
+ InlineKeyboardButton(
|
|
|
|
|
+ f"{page}/{total_pages}",
|
|
|
|
|
+ callback_data=f"giveaway_winners_noop:{giveaway_id}",
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ if page < total_pages:
|
|
|
|
|
+ buttons.append(
|
|
|
|
|
+ InlineKeyboardButton(
|
|
|
|
|
+ "下一页 ›",
|
|
|
|
|
+ callback_data=f"giveaway_winners_page:{giveaway_id}:{page + 1}",
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ return InlineKeyboardMarkup([buttons])
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _render_winners_page(
|
|
|
|
|
+ giveaway: dict[str, Any], page: int
|
|
|
|
|
+) -> tuple[str, InlineKeyboardMarkup | None]:
|
|
|
|
|
+ winners = list(giveaway.get("winners", []))
|
|
|
|
|
+ total = len(winners)
|
|
|
|
|
+ total_pages = max(1, (total + PARTICIPANTS_PAGE_SIZE - 1) // PARTICIPANTS_PAGE_SIZE)
|
|
|
|
|
+ page = min(max(1, page), total_pages)
|
|
|
|
|
+ offset = (page - 1) * PARTICIPANTS_PAGE_SIZE
|
|
|
|
|
+ current = winners[offset : offset + PARTICIPANTS_PAGE_SIZE]
|
|
|
|
|
+ lines = [
|
|
|
|
|
+ f"<b>抽奖结果 #{escape(giveaway['giveaway_id'])}</b>",
|
|
|
|
|
+ f"<b>标题:</b>{escape(giveaway['title'])}",
|
|
|
|
|
+ f"第 {page}/{total_pages} 页 · 共 {total} 位中奖者",
|
|
|
|
|
+ ]
|
|
|
|
|
+ if current:
|
|
|
|
|
+ for index, winner in enumerate(current, offset + 1):
|
|
|
|
|
+ points = int(winner.get("points_reward", 0))
|
|
|
|
|
+ suffix = f"(+{points} 积分)" if points else ""
|
|
|
|
|
+ lines.append(
|
|
|
|
|
+ f"{index}. {escape(str(winner['tier']))}:"
|
|
|
|
|
+ f"{_participant_mention(winner)}{suffix}"
|
|
|
|
|
+ )
|
|
|
|
|
+ else:
|
|
|
|
|
+ lines.append("本次没有符合条件的中奖者。")
|
|
|
|
|
+ return "\n".join(lines), _winners_markup(
|
|
|
|
|
+ giveaway["giveaway_id"], page=page, total_pages=total_pages
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
async def _join_user(giveaway_id: str, user) -> str:
|
|
async def _join_user(giveaway_id: str, user) -> str:
|
|
|
status, _ = await join_giveaway(
|
|
status, _ = await join_giveaway(
|
|
|
giveaway_id,
|
|
giveaway_id,
|
|
@@ -240,6 +396,72 @@ async def giveaway_join_callback(_, query: CallbackQuery):
|
|
|
await query.answer(await _join_user(giveaway_id, query.from_user), show_alert=True)
|
|
await query.answer(await _join_user(giveaway_id, query.from_user), show_alert=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+@app.on_callback_query(filters.regex(r"^giveaway_participants:[a-f0-9]+$"))
|
|
|
|
|
+async def giveaway_participants_callback(_, query: CallbackQuery):
|
|
|
|
|
+ giveaway_id = normalize_giveaway_id(query.data.rsplit(":", 1)[1])
|
|
|
|
|
+ giveaway = await get_giveaway(giveaway_id)
|
|
|
|
|
+ if not giveaway or int(giveaway["chat_id"]) != int(query.message.chat.id):
|
|
|
|
|
+ return await query.answer("本群中未找到该抽奖。", show_alert=True)
|
|
|
|
|
+ text, markup = await _render_participants_page(giveaway_id, 1)
|
|
|
|
|
+ await query.message.reply_text(
|
|
|
|
|
+ text,
|
|
|
|
|
+ parse_mode=ParseMode.HTML,
|
|
|
|
|
+ reply_markup=markup,
|
|
|
|
|
+ disable_web_page_preview=True,
|
|
|
|
|
+ )
|
|
|
|
|
+ return await query.answer()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@app.on_callback_query(
|
|
|
|
|
+ filters.regex(r"^giveaway_participants_page:[a-f0-9]+:\d+$")
|
|
|
|
|
+)
|
|
|
|
|
+async def giveaway_participants_page_callback(_, query: CallbackQuery):
|
|
|
|
|
+ _, giveaway_id, raw_page = query.data.split(":", 2)
|
|
|
|
|
+ giveaway_id = normalize_giveaway_id(giveaway_id)
|
|
|
|
|
+ giveaway = await get_giveaway(giveaway_id)
|
|
|
|
|
+ if not giveaway or int(giveaway["chat_id"]) != int(query.message.chat.id):
|
|
|
|
|
+ return await query.answer("本群中未找到该抽奖。", show_alert=True)
|
|
|
|
|
+ text, markup = await _render_participants_page(giveaway_id, int(raw_page))
|
|
|
|
|
+ await query.message.edit_text(
|
|
|
|
|
+ text,
|
|
|
|
|
+ parse_mode=ParseMode.HTML,
|
|
|
|
|
+ reply_markup=markup,
|
|
|
|
|
+ disable_web_page_preview=True,
|
|
|
|
|
+ )
|
|
|
|
|
+ return await query.answer()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@app.on_callback_query(filters.regex(r"^giveaway_participants_noop:"))
|
|
|
|
|
+async def giveaway_participants_noop_callback(_, query: CallbackQuery):
|
|
|
|
|
+ return await query.answer()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@app.on_callback_query(filters.regex(r"^giveaway_winners_page:[a-f0-9]+:\d+$"))
|
|
|
|
|
+async def giveaway_winners_page_callback(_, query: CallbackQuery):
|
|
|
|
|
+ _, giveaway_id, raw_page = query.data.split(":", 2)
|
|
|
|
|
+ giveaway_id = normalize_giveaway_id(giveaway_id)
|
|
|
|
|
+ giveaway = await get_giveaway(giveaway_id)
|
|
|
|
|
+ if (
|
|
|
|
|
+ not giveaway
|
|
|
|
|
+ or giveaway["status"] != "finished"
|
|
|
|
|
+ or int(giveaway["chat_id"]) != int(query.message.chat.id)
|
|
|
|
|
+ ):
|
|
|
|
|
+ return await query.answer("本群中未找到该开奖结果。", show_alert=True)
|
|
|
|
|
+ text, markup = _render_winners_page(giveaway, int(raw_page))
|
|
|
|
|
+ await query.message.edit_text(
|
|
|
|
|
+ text,
|
|
|
|
|
+ parse_mode=ParseMode.HTML,
|
|
|
|
|
+ reply_markup=markup,
|
|
|
|
|
+ disable_web_page_preview=True,
|
|
|
|
|
+ )
|
|
|
|
|
+ return await query.answer()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@app.on_callback_query(filters.regex(r"^giveaway_winners_noop:"))
|
|
|
|
|
+async def giveaway_winners_noop_callback(_, query: CallbackQuery):
|
|
|
|
|
+ return await query.answer()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
@app.on_message(filters.command("gjoin") & ~filters.private)
|
|
@app.on_message(filters.command("gjoin") & ~filters.private)
|
|
|
async def giveaway_join_command(_, message: Message):
|
|
async def giveaway_join_command(_, message: Message):
|
|
|
if not message.from_user:
|
|
if not message.from_user:
|
|
@@ -249,6 +471,58 @@ async def giveaway_join_command(_, message: Message):
|
|
|
return await message.reply_text(await _join_user(message.command[1], message.from_user))
|
|
return await message.reply_text(await _join_user(message.command[1], message.from_user))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+@app.on_message(filters.regex(r"^抽奖$") & filters.group)
|
|
|
|
|
+async def giveaway_keyword_command(_, message: Message):
|
|
|
|
|
+ giveaways = await list_running_giveaways(chat_id=message.chat.id, limit=10)
|
|
|
|
|
+ now = utc_now()
|
|
|
|
|
+ giveaways = [
|
|
|
|
|
+ giveaway for giveaway in giveaways if as_utc(giveaway["ends_at"]) > now
|
|
|
|
|
+ ]
|
|
|
|
|
+ if not giveaways:
|
|
|
|
|
+ return await message.reply_text("本群当前没有进行中的抽奖。")
|
|
|
|
|
+ lines = ["<b>本群抽奖</b>"]
|
|
|
|
|
+ for giveaway in giveaways:
|
|
|
|
|
+ state = "报名中" if giveaway_starts_at(giveaway) <= now else "待报名"
|
|
|
|
|
+ lines.append(
|
|
|
|
|
+ "#{id} · {title} · {state} · {count} 人参与 · {ends_at} 开奖".format(
|
|
|
|
|
+ id=escape(giveaway["giveaway_id"]),
|
|
|
|
|
+ title=escape(giveaway["title"]),
|
|
|
|
|
+ state=state,
|
|
|
|
|
+ count=await count_participants(giveaway["giveaway_id"]),
|
|
|
|
|
+ ends_at=format_giveaway_time(giveaway["ends_at"]),
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ return await message.reply_text(
|
|
|
|
|
+ "\n".join(lines),
|
|
|
|
|
+ parse_mode=ParseMode.HTML,
|
|
|
|
|
+ reply_markup=_keyword_giveaways_markup(giveaways),
|
|
|
|
|
+ disable_web_page_preview=True,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@app.on_message(filters.regex(r"^(?:开奖|中奖名单)$") & filters.group)
|
|
|
|
|
+async def giveaway_winners_keyword_command(_, message: Message):
|
|
|
|
|
+ giveaways, _ = await list_giveaways_page(
|
|
|
|
|
+ chat_id=message.chat.id,
|
|
|
|
|
+ status="finished",
|
|
|
|
|
+ page=1,
|
|
|
|
|
+ page_size=20,
|
|
|
|
|
+ )
|
|
|
|
|
+ if not giveaways:
|
|
|
|
|
+ return await message.reply_text("本群暂无已开奖的抽奖。")
|
|
|
|
|
+ giveaway = max(
|
|
|
|
|
+ giveaways,
|
|
|
|
|
+ key=lambda item: as_utc(item.get("finished_at") or item["updated_at"]),
|
|
|
|
|
+ )
|
|
|
|
|
+ text, markup = _render_winners_page(giveaway, 1)
|
|
|
|
|
+ return await message.reply_text(
|
|
|
|
|
+ text,
|
|
|
|
|
+ parse_mode=ParseMode.HTML,
|
|
|
|
|
+ reply_markup=markup,
|
|
|
|
|
+ disable_web_page_preview=True,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
@app.on_message(filters.command("glist") & ~filters.private)
|
|
@app.on_message(filters.command("glist") & ~filters.private)
|
|
|
@adminsOnly("can_change_info")
|
|
@adminsOnly("can_change_info")
|
|
|
async def list_giveaways_command(_, message: Message):
|
|
async def list_giveaways_command(_, message: Message):
|
|
@@ -277,14 +551,12 @@ async def participants_command(_, message: Message):
|
|
|
giveaway = await get_giveaway(giveaway_id)
|
|
giveaway = await get_giveaway(giveaway_id)
|
|
|
if not giveaway or giveaway["chat_id"] != message.chat.id:
|
|
if not giveaway or giveaway["chat_id"] != message.chat.id:
|
|
|
return await message.reply_text("本群中未找到该抽奖。")
|
|
return await message.reply_text("本群中未找到该抽奖。")
|
|
|
- total = await count_participants(giveaway_id)
|
|
|
|
|
- participants = await list_participants(giveaway_id, limit=PARTICIPANT_PREVIEW_LIMIT)
|
|
|
|
|
- lines = [f"<b>抽奖 #{escape(giveaway_id)} 的参与者</b>", f"共 {total} 人"]
|
|
|
|
|
- lines.extend("- " + _participant_mention(item) for item in participants)
|
|
|
|
|
- if total > len(participants):
|
|
|
|
|
- lines.append(f"另有 {total - len(participants)} 人未显示。")
|
|
|
|
|
|
|
+ text, markup = await _render_participants_page(giveaway_id, 1)
|
|
|
return await message.reply_text(
|
|
return await message.reply_text(
|
|
|
- "\n".join(lines), parse_mode=ParseMode.HTML, disable_web_page_preview=True
|
|
|
|
|
|
|
+ text,
|
|
|
|
|
+ parse_mode=ParseMode.HTML,
|
|
|
|
|
+ reply_markup=markup,
|
|
|
|
|
+ disable_web_page_preview=True,
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
|