Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions services/ps_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@
# Add a currency here if displayed price is 100x too small (e.g. Rs 49.99 instead of Rs 4999).
_WHOLE_UNIT_CURRENCIES = {"INR", "JPY", "KRW", "CLP", "COP"}

_TRADEMARK_RE = re.compile(r"[™®©]")


def is_effectively_ascii(title: str) -> bool:
"""Return True if the title is ASCII after stripping trademark/copyright symbols."""
return _TRADEMARK_RE.sub("", title).isascii()

STORE_HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
Expand Down
10 changes: 7 additions & 3 deletions services/subscription.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from db.models.subscription import Subscription
from db.models.user import User
from db.models.user_region import UserRegion
from services.ps_store import GameInfo, RegionPrice, best_ps_id, get_game_info, search_games
from services.ps_store import GameInfo, RegionPrice, best_ps_id, get_game_info, is_effectively_ascii, search_games

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -117,8 +117,12 @@ async def subscribe_to_game(
subscriptions_already_exists.inc()
return False

# Prefer ASCII title (same rule as in search merge: localized prefixes like "Набір" lose to ASCII)
if game_info.title.isascii() and not game.title.isascii():
# Prefer effectively-ASCII or canonical (en-us) title over non-ASCII stored title.
# Strip trademark symbols (™, ®, ©) before the ASCII check so titles like
# "Spider-Man™" are treated as ASCII and can replace non-ASCII stored titles.
new_is_ascii = is_effectively_ascii(game_info.title)
stored_is_ascii = is_effectively_ascii(game.title)
if not stored_is_ascii and game_info.title != game.title and (new_is_ascii or "en-us" in prices):
logger.info("updating title for game_id=%d: %r -> %r", game.id, game.title, game_info.title)
game.title = game_info.title

Expand Down
62 changes: 62 additions & 0 deletions tests/integration/test_subscription_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,68 @@ async def test_subscribe_keeps_title_if_new_is_non_ascii(session: AsyncSession,
assert game.title == "Test Game"


@pytest.mark.asyncio
async def test_subscribe_updates_title_when_new_has_trademark_symbols(session: AsyncSession, user, region):
"""Title with ™/® is effectively ASCII and should replace a non-ASCII stored title."""
localized = _make_game_info(title="Набір Test Game")
trademark = _make_game_info(title="Test Game™")
prices = {region.code: _make_region_price()}

user2 = User(telegram_id=999999999, username="other")
session.add(user2)
await session.flush()
await subscribe_to_game(session, user2, localized, prices)

await subscribe_to_game(session, user, trademark, prices)

game = await session.scalar(select(Game).where(Game.composite_key == trademark.composite_key))
assert game.title == "Test Game™"


@pytest.mark.asyncio
async def test_subscribe_updates_title_from_en_us_region(session: AsyncSession, user, region, region2):
"""When en-us is in the subscription's regions, non-ASCII stored title is updated to the canonical title."""
localized = _make_game_info(title="Набір геймера")
canonical = _make_game_info(title="Gamer Bundle")
prices_localized = {region.code: _make_region_price(ps_id="EP0001")}
prices_canonical = {
region.code: _make_region_price(ps_id="EP0001"),
region2.code: _make_region_price(ps_id="UP0001"),
}

user2 = User(telegram_id=999999999, username="other")
session.add(user2)
await session.flush()
await subscribe_to_game(session, user2, localized, prices_localized)

await subscribe_to_game(session, user, canonical, prices_canonical)

game = await session.scalar(select(Game).where(Game.composite_key == canonical.composite_key))
assert game.title == "Gamer Bundle"


@pytest.mark.asyncio
async def test_subscribe_keeps_ascii_title_even_with_en_us_region(session: AsyncSession, user, region, region2):
"""ASCII stored title is not replaced when new title is non-ASCII, even if en-us is in prices."""
ascii_game = _make_game_info(title="Test Game")
localized = _make_game_info(title="Набір Test Game")
prices_ascii = {region.code: _make_region_price(ps_id="EP0001")}
prices_localized = {
region.code: _make_region_price(ps_id="EP0001"),
region2.code: _make_region_price(ps_id="UP0001"),
}

user2 = User(telegram_id=999999999, username="other")
session.add(user2)
await session.flush()
await subscribe_to_game(session, user2, ascii_game, prices_ascii)

await subscribe_to_game(session, user, localized, prices_localized)

game = await session.scalar(select(Game).where(Game.composite_key == ascii_game.composite_key))
assert game.title == "Test Game"



# ── is_subscribed ─────────────────────────────────────────────────────────────

Expand Down
5 changes: 0 additions & 5 deletions worker/tasks/price_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,6 @@ async def _check_game_region(session, gr: GameRegion) -> str:
gr.discount_end = region_price.discount_end
gr.last_checked = datetime.now(timezone.utc)

gr.game.title = game_info.title
gr.game.cover_url = game_info.cover_url
gr.game.game_type = game_info.type
gr.game.platforms = game_info.platforms

if price_dropped:
await session.execute(
insert(PriceDrop)
Expand Down
Loading