Skip to content

Commit af693da

Browse files
authored
Merge pull request #51 from OpenBuilders/feat/stickerdom-integration
feat: StickerDom intergration
2 parents 47cea44 + 913e7f1 commit af693da

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+31756
-162
lines changed

backend/api/pos/sticker.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from api.pos.base import BaseFDO
2+
from core.dtos.sticker import MinimalStickerCollectionWithCharactersDTO
3+
4+
5+
class MinimalStickerCollectionWithCharactersFDO(
6+
BaseFDO, MinimalStickerCollectionWithCharactersDTO
7+
):
8+
...

backend/api/routes/admin/resource.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
NftCollectionFDO,
1414
)
1515
from api.pos.common import StatusFDO, CategoriesFDO
16+
from api.pos.sticker import MinimalStickerCollectionWithCharactersFDO
1617
from core.actions.jetton import JettonAction
1718
from core.actions.nft_collection import NftCollectionAction
1819
from core.actions.sticker import StickerCharacterAction
19-
from core.dtos.sticker import MinimalStickerCollectionWithCharactersDTO
2020
from core.enums.jetton import CurrencyCategory
2121
from core.enums.nft import ASSET_TO_CATEGORY_TYPE_MAPPING
2222
from core.exceptions.external import ExternalResourceNotFound
@@ -84,9 +84,16 @@ async def get_currency_categories() -> list[CategoriesFDO]:
8484
@admin_resource_router.get("/stickers")
8585
async def get_stickers_collections(
8686
db_session: Session = Depends(get_db_session),
87-
) -> list[MinimalStickerCollectionWithCharactersDTO]:
87+
) -> list[MinimalStickerCollectionWithCharactersFDO]:
8888
action = StickerCharacterAction(db_session)
89-
return await action.get_all_grouped()
89+
collections = await action.get_all_grouped()
90+
91+
return [
92+
MinimalStickerCollectionWithCharactersFDO.model_validate(
93+
collection.model_dump()
94+
)
95+
for collection in collections
96+
]
9097

9198

9299
@admin_resource_router.get("/jettons", deprecated=True)

backend/community_manager/actions/__init__.py

Whitespace-only changes.
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import logging
2+
3+
from sqlalchemy.orm import Session
4+
5+
from community_manager.dtos.chat import TargetChatMembersDTO
6+
from community_manager.entrypoint import init_client
7+
from community_manager.settings import community_manager_settings
8+
from core.actions.authorization import AuthorizationAction
9+
from core.constants import UPDATED_WALLETS_SET_NAME, UPDATED_STICKERS_USER_IDS
10+
from core.services.chat.rule.sticker import TelegramChatStickerCollectionService
11+
from core.services.chat.user import TelegramChatUserService
12+
from core.services.superredis import RedisService
13+
14+
15+
logger = logging.getLogger(__name__)
16+
17+
18+
class CommunityManagerChatAction:
19+
def __init__(self, db_session: Session):
20+
self.db_session = db_session
21+
self.telegram_chat_user_service = TelegramChatUserService(db_session)
22+
self.telegram_chat_sticker_collection_service = (
23+
TelegramChatStickerCollectionService(db_session)
24+
)
25+
self.redis_service = RedisService()
26+
27+
def get_updated_chat_members(self) -> TargetChatMembersDTO:
28+
"""
29+
Fetches and updates the target chat members based on specific criteria including
30+
linked wallets and sticker owners. The method retrieves updated wallet addresses
31+
and sticker owner IDs from the Redis service, identifies relevant chat members
32+
from the Telegram chat services, and compiles the processed data into a
33+
`TargetChatMembersDTO` object.
34+
35+
:raises ValueError: If unexpected data types are retrieved or processed in the logic.
36+
37+
:return: A `TargetChatMembersDTO` object containing the updated wallet addresses,
38+
sticker owner IDs, and the compiled target chat members.
39+
"""
40+
wallets = self.redis_service.pop_from_set(
41+
name=UPDATED_WALLETS_SET_NAME,
42+
count=community_manager_settings.items_per_task,
43+
)
44+
if isinstance(wallets, str):
45+
wallets = [wallets]
46+
47+
sticker_owners_ids = (
48+
self.redis_service.pop_from_set(
49+
name=UPDATED_STICKERS_USER_IDS,
50+
count=community_manager_settings.items_per_task,
51+
)
52+
or []
53+
)
54+
if isinstance(sticker_owners_ids, str):
55+
sticker_owners_ids = [sticker_owners_ids]
56+
sticker_owners_ids = set(map(int, sticker_owners_ids))
57+
58+
target_chat_members: set[tuple[int, int]] = set()
59+
60+
if wallets:
61+
chat_members = self.telegram_chat_user_service.get_all_by_linked_wallet(
62+
addresses=wallets
63+
)
64+
target_chat_members.update(
65+
{(cm.chat_id, cm.user_id) for cm in chat_members}
66+
)
67+
68+
if sticker_owners_ids:
69+
rules = self.telegram_chat_sticker_collection_service.get_all(
70+
enabled_only=True
71+
)
72+
unique_chat_ids = {r.chat_id for r in rules}
73+
target_chat_members.update(
74+
{
75+
(chat_id, user_id)
76+
for chat_id in unique_chat_ids
77+
for user_id in sticker_owners_ids
78+
}
79+
)
80+
81+
return TargetChatMembersDTO(
82+
wallets=wallets,
83+
sticker_owners_ids=sticker_owners_ids,
84+
target_chat_members=target_chat_members,
85+
)
86+
87+
async def sanity_chat_checks(self) -> None:
88+
"""
89+
Performs sanity checks on chat members and validates their eligibility. If there are
90+
any chat members to validate, it initiates the validation process with the help of
91+
a Telegram service client. Ineligible members are removed based on the validation
92+
logic. If an error occurs during validation, a fallback mechanism is triggered
93+
to add wallets and users back to the redis database to try again later.
94+
95+
The method logs the progress at various stages and handles exceptions to ensure
96+
fallback processes are executed if needed.
97+
98+
:raises Exception: If validation of chat members fails during execution.
99+
"""
100+
dto = self.get_updated_chat_members()
101+
if target_chat_members := dto.target_chat_members:
102+
try:
103+
logger.info(f"Validating chat members for {target_chat_members}")
104+
chat_members = self.telegram_chat_user_service.get_all_pairs(
105+
chat_member_pairs=target_chat_members
106+
)
107+
108+
if not chat_members:
109+
logger.info("No chats to validate. Skipping")
110+
return
111+
else:
112+
logger.info(f"Found {len(chat_members)} chat members to validate")
113+
114+
telethon_service = init_client()
115+
authorization_action = AuthorizationAction(
116+
self.db_session, telethon_client=telethon_service.client
117+
)
118+
await authorization_action.kick_ineligible_chat_members(
119+
chat_members=chat_members
120+
)
121+
logger.info(
122+
f"Successfully validated {len(chat_members)} chat members. "
123+
)
124+
except Exception as exc:
125+
logger.error(f"Failed to validate chat members: {exc}", exc_info=True)
126+
self.fallback_update_chat_members(dto=dto)
127+
raise exc
128+
else:
129+
logger.info("No users to validate. Skipping")
130+
131+
def fallback_update_chat_members(self, dto: TargetChatMembersDTO) -> None:
132+
"""
133+
Activates a fallback mechanism to update chat members by storing provided wallets
134+
and sticker owner IDs in Redis sets. This ensures that the required updates are
135+
persisted and managed separately if the primary update mechanism fails.
136+
137+
:param dto: A data transfer object containing the wallets and sticker owner IDs
138+
to be updated in Redis sets.
139+
"""
140+
logger.warning("Activating fallback method for chat members.")
141+
self.redis_service.add_to_set(UPDATED_WALLETS_SET_NAME, *dto.wallets)
142+
self.redis_service.add_to_set(
143+
UPDATED_STICKERS_USER_IDS, *map(str, dto.sticker_owners_ids)
144+
)

backend/community_manager/dtos/__init__.py

Whitespace-only changes.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from pydantic import BaseModel
2+
3+
4+
class TargetChatMembersDTO(BaseModel):
5+
wallets: list[str]
6+
sticker_owners_ids: list[int]
7+
target_chat_members: set[tuple[int, int]]

backend/community_manager/entrypoint.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717

1818

1919
def init_client():
20-
# Make session persistent for community manager
20+
# Make session persistent for the community manager
2121
session_path = Path(__file__).parent.parent / "data"
2222
if not session_path.exists():
2323
raise FileNotFoundError(
2424
f"Session path {session_path} does not exist. Please create it."
2525
)
2626

27-
# This session is not thread-safe, and cannot be used by multiple clients at the same time.
27+
# This session is not thread-safe and cannot be used by multiple clients at the same time.
2828
session = SQLiteSession(str(session_path / "gateway.session"))
2929
client = TelegramClient(
3030
session,

backend/community_manager/tasks/chat.py

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from celery.utils.log import get_task_logger
44

5+
from community_manager.actions.chat import CommunityManagerChatAction
56
from community_manager.celery_app import app
67
from community_manager.entrypoint import init_client
78
from community_manager.settings import community_manager_settings
@@ -12,25 +13,26 @@
1213
)
1314
from core.constants import (
1415
CELERY_SYSTEM_QUEUE_NAME,
15-
UPDATED_WALLETS_SET_NAME,
1616
)
1717
from core.services.chat.user import TelegramChatUserService
1818
from core.services.db import DBService
19-
from core.services.superredis import RedisService
2019

2120
logger = get_task_logger(__name__)
2221

2322

24-
async def sanity_chat_checks(wallets: list[str] | None) -> None:
25-
logger.info(f"Validating chat members for {wallets}")
23+
async def sanity_chat_checks(target_chat_members: set[tuple[int, int]]) -> None:
24+
logger.info(f"Validating chat members for {target_chat_members}")
2625
with DBService().db_session() as db_session:
2726
telegram_chat_user_service = TelegramChatUserService(db_session)
28-
chat_members = telegram_chat_user_service.get_all_by_linked_wallet(
29-
addresses=wallets
27+
chat_members = telegram_chat_user_service.get_all_pairs(
28+
chat_member_pairs=target_chat_members
3029
)
3130

3231
if not chat_members:
3332
logger.info("No chats to validate. Skipping")
33+
return
34+
else:
35+
logger.info(f"Found {len(chat_members)} chat members to validate")
3436

3537
telethon_service = init_client()
3638
authorization_action = AuthorizationAction(
@@ -50,23 +52,9 @@ def check_chat_members() -> None:
5052
logger.warning("Community manager is disabled.")
5153
return
5254

53-
redis_service = RedisService()
54-
wallets = redis_service.pop_from_set(
55-
name=UPDATED_WALLETS_SET_NAME,
56-
count=community_manager_settings.items_per_task,
57-
)
58-
if isinstance(wallets, str):
59-
wallets = [wallets]
60-
61-
if wallets:
62-
try:
63-
asyncio.run(sanity_chat_checks(wallets=wallets))
64-
except Exception as exc:
65-
# Add wallets back to the set to retry later
66-
logger.error(f"Failed to validate chat members: {exc}", exc_info=True)
67-
redis_service.add_to_set(UPDATED_WALLETS_SET_NAME, *wallets)
68-
else:
69-
logger.info("No users to validate. Skipping")
55+
with DBService().db_session() as db_session:
56+
action = CommunityManagerChatAction(db_session=db_session)
57+
asyncio.run(action.sanity_chat_checks())
7058

7159

7260
@app.task(

backend/core/actions/chat/rule/whitelist.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ async def refresh_external_source(
7979
raise
8080
return
8181
except TelegramChatInvalidExternalSourceError as e:
82-
logger.exception(f"Invalid external source {source.url!r}: {e}")
82+
logger.warning(f"Invalid external source {source.url!r}: {e}")
8383
if raise_for_error:
8484
raise
8585
return

0 commit comments

Comments
 (0)