Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
- /abyss 指令增加星鐵忘卻之庭紀錄
- 星鐵即時便箋增加後備開拓力顯示
- 修正星鐵展示櫃忘卻之庭已完成樓層顯示錯誤的問題
  • Loading branch information
KT-Yeh committed Oct 8, 2023
2 parents df2cf32 + 062e8d4 commit 3e0fdb4
Show file tree
Hide file tree
Showing 19 changed files with 598 additions and 105 deletions.
148 changes: 85 additions & 63 deletions Pipfile.lock

Large diffs are not rendered by default.

Binary file added assets/image/character/hsr_4star_bg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/image/character/hsr_5star_bg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/image/forgotten_hall/bg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/image/forgotten_hall/star.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 17 additions & 4 deletions cogs/abyss/cog.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from typing import Literal, Optional

import discord
import genshin
from discord import app_commands
from discord.app_commands import Choice
from discord.ext import commands

from utility import EmbedTemplate, config, custom_log

from .ui import SpiralAbyssUI
from .ui_genshin import SpiralAbyssUI
from .ui_starrail import ForgottenHallUI


class SpiralAbyssCog(commands.Cog, name="深境螺旋"):
Expand All @@ -16,23 +18,34 @@ def __init__(self, bot: commands.Bot):

@app_commands.command(name="abyss深淵紀錄", description="查詢深境螺旋紀錄")
@app_commands.checks.cooldown(1, config.slash_cmd_cooldown)
@app_commands.rename(season="時間", user="使用者")
@app_commands.rename(game="遊戲", season="時間", user="使用者")
@app_commands.describe(season="選擇本期、上期或是歷史紀錄", user="查詢其他成員的資料,不填寫則查詢自己")
@app_commands.choices(
game=[
Choice(name="原神", value="genshin"),
Choice(name="星穹鐵道", value="hkrpg"),
],
season=[
Choice(name="本期紀錄", value="THIS_SEASON"),
Choice(name="上期紀錄", value="PREVIOUS_SEASON"),
Choice(name="歷史紀錄", value="HISTORICAL_RECORD"),
]
],
)
@custom_log.SlashCommandLogger
async def slash_abyss(
self,
interaction: discord.Interaction,
game: genshin.Game,
season: Literal["THIS_SEASON", "PREVIOUS_SEASON", "HISTORICAL_RECORD"],
user: Optional[discord.User] = None,
):
await SpiralAbyssUI.abyss(interaction, user or interaction.user, season)
match game:
case genshin.Game.GENSHIN:
await SpiralAbyssUI.abyss(interaction, user or interaction.user, season)
case genshin.Game.STARRAIL:
await ForgottenHallUI.launch(interaction, user or interaction.user, season)
case _:
return

@slash_abyss.error
async def on_slash_abyss_error(
Expand Down
File renamed without changes.
193 changes: 193 additions & 0 deletions cogs/abyss/ui_starrail.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import asyncio
import typing

import discord

import genshin_py
from database import Database, StarrailForgottenHall, User
from utility import EmbedTemplate, config


class HallRecordDropdown(discord.ui.Select):
"""選擇忘卻之庭歷史紀錄的下拉選單"""

def __init__(
self,
user: discord.User | discord.Member,
nickname: str,
uid: int,
hall_data_list: typing.Sequence[StarrailForgottenHall],
):
hall_data_list = sorted(
hall_data_list, key=lambda x: x.data.begin_time.datetime, reverse=True
)
options = [
discord.SelectOption(
label=f"[{hall.data.begin_time.datetime.strftime('%Y.%m.%d')} ~ "
f"{hall.data.end_time.datetime.strftime('%Y.%m.%d')}] ★ {hall.data.total_stars}",
value=str(i),
)
for i, hall in enumerate(hall_data_list)
]
super().__init__(placeholder="選擇期數:", options=options)
self.user = user
self.nickname = nickname
self.uid = uid
self.hall_data_list = hall_data_list

async def callback(self, interaction: discord.Interaction):
await interaction.response.defer()
index = int(self.values[0])
await ForgottenHallUI.present(
interaction,
self.user,
self.nickname,
self.uid,
self.hall_data_list[index],
view_item=self,
)


class HallFloorDropdown(discord.ui.Select):
"""選擇忘卻之庭樓層的下拉選單"""

def __init__(
self,
overview: discord.Embed,
avatar: bytes,
nickname: str,
uid: int,
hall_data: StarrailForgottenHall,
save_or_remove: typing.Literal["SAVE", "REMOVE"],
):
# 第一個選項依據參數顯示為保存或是刪除紀錄
_descr = "保存此次紀錄到資料庫,之後可從歷史紀錄查看" if save_or_remove == "SAVE" else "從資料庫中刪除本次忘卻之庭紀錄"
options = [
discord.SelectOption(
label=f"{'📁 儲存本次紀錄' if save_or_remove == 'SAVE' else '❌ 刪除本次紀錄'}",
description=_descr,
value=save_or_remove,
)
]
options += [
discord.SelectOption(
label=f"[★{floor.star_num}] {floor.name}",
value=str(i),
)
for i, floor in enumerate(hall_data.data.floors)
]

super().__init__(
placeholder="選擇樓層(可多選):",
options=options,
max_values=(min(3, len(hall_data.data.floors))),
)
self.embed = overview
self.avatar = avatar
self.nickname = nickname
self.uid = uid
self.hall_data = hall_data
self.save_or_remove = save_or_remove

async def callback(self, interaction: discord.Interaction):
# 儲存或刪除忘卻之庭資料
if self.save_or_remove in self.values:
# 檢查互動者是否為忘卻之庭資料本人
if interaction.user.id == self.hall_data.discord_id:
if self.save_or_remove == "SAVE":
await Database.insert_or_replace(self.hall_data)
await interaction.response.send_message(
embed=EmbedTemplate.normal("已儲存本次忘卻之庭紀錄"), ephemeral=True
)
else: # self.save_or_remove == 'REMOVE'
await Database.delete_instance(self.hall_data)
await interaction.response.send_message(
embed=EmbedTemplate.normal("已刪除本次忘卻之庭紀錄"), ephemeral=True
)
else:
await interaction.response.send_message(
embed=EmbedTemplate.error("僅限本人才能操作"), ephemeral=True
)
else: # 繪製樓層圖片
await interaction.response.defer()
values = sorted(self.values, key=lambda x: int(x))
floors = [self.hall_data.data.floors[int(value)] for value in values]
fp = await genshin_py.draw_starrail_forgottenhall_card(
self.avatar, self.nickname, self.uid, self.hall_data.data, floors
)
fp.seek(0)
self.embed.set_image(url="attachment://image.jpeg")
await interaction.edit_original_response(
embed=self.embed, attachments=[discord.File(fp, "image.jpeg")]
)


class ForgottenHallUI:
@staticmethod
async def present(
interaction: discord.Interaction,
user: discord.User | discord.Member,
nickname: str,
uid: int,
hall_data: StarrailForgottenHall,
*,
view_item: discord.ui.Item | None = None,
):
hall = hall_data.data
embed = genshin_py.parse_starrail_hall_overview(hall)
embed.title = f"{user.display_name} 的忘卻之庭戰績"
embed.set_thumbnail(url=user.display_avatar.url)
view = None
if len(hall.floors) > 0:
view = discord.ui.View(timeout=config.discord_view_short_timeout)
avatar = await user.display_avatar.read()
if view_item: # 從歷史紀錄取得資料,所以第一個選項是刪除紀錄
view.add_item(HallFloorDropdown(embed, avatar, nickname, uid, hall_data, "REMOVE"))
view.add_item(view_item)
else: # 從Hoyolab取得資料,所以第一個選項是保存紀錄
view.add_item(HallFloorDropdown(embed, avatar, nickname, uid, hall_data, "SAVE"))
await interaction.edit_original_response(embed=embed, view=view, attachments=[])

@staticmethod
async def launch(
interaction: discord.Interaction,
user: discord.User | discord.Member,
season_choice: typing.Literal["THIS_SEASON", "PREVIOUS_SEASON", "HISTORICAL_RECORD"],
):
try:
defer, userstats = await asyncio.gather(
interaction.response.defer(),
genshin_py.get_starrail_userstats(user.id),
)
except Exception as e:
await interaction.edit_original_response(embed=EmbedTemplate.error(e))
return

nickname = userstats.info.nickname
_u = await Database.select_one(User, User.discord_id == user.id)
uid = _u.uid_starrail if _u else 0
uid = uid or 0

if season_choice == "HISTORICAL_RECORD": # 查詢歷史紀錄
hall_data_list = await Database.select_all(
StarrailForgottenHall,
StarrailForgottenHall.discord_id.is_(user.id),
)
if len(hall_data_list) == 0:
await interaction.edit_original_response(
embed=EmbedTemplate.normal("此使用者沒有保存任何歷史紀錄")
)
else:
view = discord.ui.View(timeout=config.discord_view_short_timeout)
view.add_item(HallRecordDropdown(user, nickname, uid, hall_data_list))
await interaction.edit_original_response(view=view)
else: # 查詢 Hoyolab 紀錄 (THIS_SEASON、PREVIOUS_SEASON)
try:
hall = await genshin_py.get_starrail_forgottenhall(
user.id, (season_choice == "PREVIOUS_SEASON")
)
except Exception as e:
await interaction.edit_original_response(embed=EmbedTemplate.error(e))
else:
hall_data = StarrailForgottenHall(user.id, hall.season, hall)
await ForgottenHallUI.present(interaction, user, nickname, uid, hall_data)
1 change: 1 addition & 0 deletions database/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
GenshinShowcase,
GenshinSpiralAbyss,
ScheduleDailyCheckin,
StarrailForgottenHall,
StarrailScheduleNotes,
StarrailShowcase,
User,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""增加星鐵忘卻之庭Table
Revision ID: 0deac4be9347
Revises: ff5dbf6e00bb
Create Date: 2023-09-17 22:34:43.120635
"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "0deac4be9347"
down_revision = "ff5dbf6e00bb"
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"starrail_forgotten_hall",
sa.Column("discord_id", sa.Integer(), nullable=False),
sa.Column("season", sa.Integer(), nullable=False),
sa.Column("_raw_data", sa.LargeBinary(), nullable=False),
sa.PrimaryKeyConstraint("discord_id", "season"),
)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("starrail_forgotten_hall")
# ### end Alembic commands ###
36 changes: 36 additions & 0 deletions database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,42 @@ class StarrailScheduleNotes(Base):
"""下次檢查本周的歷戰餘響還未完成的時間"""


class StarrailForgottenHall(Base):
"""星穹鐵道忘卻之庭資料庫 Table"""

__tablename__ = "starrail_forgotten_hall"

discord_id: Mapped[int] = mapped_column(primary_key=True)
"""使用者 Discord ID"""
season: Mapped[int] = mapped_column(primary_key=True)
"""忘卻之庭期數"""
_raw_data: Mapped[bytes] = mapped_column()
"""忘卻之庭 bytes 資料"""

def __init__(self, discord_id: int, season: int, data: genshin.models.StarRailChallenge):
"""初始化星穹鐵道忘卻之庭資料表的物件。
Parameters:
------
discord_id: `int`
使用者 Discord ID。
season: `int`
忘卻之庭期數。
data: `genshin.models.StarRailChallenge`
genshin.py 忘卻之庭資料。
"""
json_str = data.json(by_alias=True, ensure_ascii=False)
self.discord_id = discord_id
self.season = season
self._raw_data = zlib.compress(json_str.encode("utf-8"), level=5)

@property
def data(self) -> genshin.models.StarRailChallenge:
"""genshin.py 忘卻之庭資料"""
data = zlib.decompress(self._raw_data).decode("utf-8")
return genshin.models.StarRailChallenge.parse_raw(data)


class StarrailShowcase(Base):
"""星穹鐵道展示櫃資料庫 Table"""

Expand Down
2 changes: 1 addition & 1 deletion genshin_py/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .client import *
from .draw import *
from .errors import *
from .painter import *
from .parser import *
14 changes: 14 additions & 0 deletions genshin_py/client/starrail.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,17 @@ async def get_starrail_characters(user_id: int) -> list[genshin.models.StarRailD
client = await get_client(user_id, game=genshin.Game.STARRAIL)
r = await client.get_starrail_characters(client.uid)
return r.avatar_list


@generalErrorHandler
async def get_starrail_forgottenhall(
user_id: int, previous_season: bool = False
) -> genshin.models.StarRailChallenge:
client = await get_client(user_id, game=genshin.Game.STARRAIL)
return await client.get_starrail_challenge(client.uid, previous=previous_season)


@generalErrorHandler
async def get_starrail_userstats(user_id: int) -> genshin.models.StarRailUserStats:
client = await get_client(user_id, game=genshin.Game.STARRAIL)
return await client.get_starrail_user(client.uid)
2 changes: 2 additions & 0 deletions genshin_py/painter/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .genshin import *
from .starrail import *
24 changes: 24 additions & 0 deletions genshin_py/painter/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from PIL import Image, ImageDraw, ImageFont


def draw_avatar(img: Image.Image, avatar: Image.Image, pos: tuple[int, int]):
"""以圓形畫個人頭像"""
mask = Image.new("L", avatar.size, 0)
draw = ImageDraw.Draw(mask)
draw.ellipse(((0, 0), avatar.size), fill=255)
img.paste(avatar, pos, mask=mask)


def draw_text(
img: Image.Image,
pos: tuple[float, float],
text: str,
font_name: str,
size: int,
fill,
anchor=None,
):
"""在圖片上印文字"""
draw = ImageDraw.Draw(img)
font = ImageFont.truetype(f"data/font/{font_name}", size) # type: ignore
draw.text(pos, text, fill, font, anchor=anchor)
Loading

0 comments on commit 3e0fdb4

Please sign in to comment.