Skip to content

Commit 073c8e3

Browse files
konardclaude
andcommitted
Implement VK Bot Questions Desk feature
This implementation adds a comprehensive questions desk system to the VK bot as requested in issue #106. Features: - Question submission with karma rewards using 'ask [question] [reward]' - Question resolution with karma distribution using 'resolve [ID]' - Automatic pinned message updates with top questions - Questions sorted by karma reward amount - Full documentation and help integration - Comprehensive test coverage The system allows users to: 1. Submit questions with optional karma rewards 2. Resolve questions and receive karma rewards 3. View the questions desk with 'questions' command 4. Automatically maintain a pinned message with top questions 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 7fe2ff5 commit 073c8e3

File tree

7 files changed

+642
-3
lines changed

7 files changed

+642
-3
lines changed

python/README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
|| += [ССЫЛКА] | Добавить ссылку на профиль github в свой профиль. |
3333
|| -= [ССЫЛКА] | Убрать ссылку на профиль github из своего профиля. |
3434
|| what is [] | Искать в википедии |
35+
| ✔️ | ask [ВОПРОС] [НАГРАДА] | Добавить вопрос на доску с наградой в карме. |
36+
| ✔️ | resolve [ID] | Решить вопрос и получить награду. |
37+
| ✔️ | questions | Показать доску вопросов. |
3538

3639
Так же возможно использование альтернативных названий команд.
3740

@@ -45,6 +48,9 @@
4548
| info | инфо |
4649
| update | обновить |
4750
| what is | что такое |
51+
| ask | спросить |
52+
| resolve | решить |
53+
| questions | вопросы | desk | доска |
4854

4955
![image](https://user-images.githubusercontent.com/1431904/146941784-670f052c-7b4e-4367-89c5-00466dfc92c9.png)
5056

@@ -162,6 +168,37 @@
162168

163169
Если нужного языка нет в списке, о его добавлении можно попросить [здесь](https://github.com/linksplatform/Bot/issues/15)
164170

171+
## 📋 Доска вопросов
172+
173+
Доска вопросов позволяет участникам беседы задавать вопросы и получать на них ответы с системой кармических наград.
174+
175+
### Как это работает
176+
177+
1. **Задать вопрос**: Используйте команду `ask [вопрос] [награда]`
178+
- Вопрос будет добавлен на доску
179+
- Можно указать награду в карме за решение (необязательно)
180+
- Карма за награду будет списана с вашего счета
181+
182+
2. **Решить вопрос**: Используйте команду `resolve [ID]`
183+
- Вопрос будет помечен как решенный
184+
- Вы получите награду в карме (если была указана)
185+
186+
3. **Просмотр доски**: Используйте команду `questions`
187+
- Показывает все открытые вопросы
188+
- Вопросы сортируются по размеру награды
189+
190+
### Автоматическое закрепление
191+
192+
Доска вопросов автоматически закрепляется в чате и обновляется при изменениях. Закрепленное сообщение содержит топ-10 вопросов с наибольшими наградами.
193+
194+
### Примеры использования
195+
196+
```
197+
ask Как работает декоратор @property в Python? 5
198+
resolve 123
199+
questions
200+
```
201+
165202
## Prerequisites
166203
* [Git](https://git-scm.com/downloads)
167204
* [Python 3](https://www.python.org/downloads)

python/__main__.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,10 @@ def __init__(
6363
(patterns.WHAT_IS, self.commands.what_is),
6464
(patterns.WHAT_MEAN, self.commands.what_is),
6565
(patterns.APPLY_KARMA, self.commands.apply_karma),
66-
(patterns.GITHUB_COPILOT, self.commands.github_copilot)
66+
(patterns.GITHUB_COPILOT, self.commands.github_copilot),
67+
(patterns.ASK_QUESTION, self.commands.ask_question),
68+
(patterns.RESOLVE_QUESTION, self.commands.resolve_question),
69+
(patterns.QUESTIONS_DESK, self.commands.show_questions_desk)
6770
)
6871

6972
def message_new(
@@ -168,6 +171,55 @@ def send_msg(
168171
message=msg, peer_id=peer_id,
169172
disable_mentions=1, random_id=0))
170173

174+
def pin_message(
175+
self,
176+
peer_id: int,
177+
message_id: int
178+
) -> Dict[str, Any]:
179+
"""Pins a message in chat.
180+
181+
:param peer_id: chat ID
182+
:param message_id: message ID to pin
183+
"""
184+
return self.call_method(
185+
'messages.pin',
186+
dict(peer_id=peer_id, message_id=message_id))
187+
188+
def unpin_message(
189+
self,
190+
peer_id: int
191+
) -> Dict[str, Any]:
192+
"""Unpins current pinned message in chat.
193+
194+
:param peer_id: chat ID
195+
"""
196+
return self.call_method(
197+
'messages.unpin',
198+
dict(peer_id=peer_id))
199+
200+
def send_and_pin_msg(
201+
self,
202+
msg: str,
203+
peer_id: int
204+
) -> int:
205+
"""Sends a message and pins it, returning the message ID.
206+
207+
:param msg: message text
208+
:param peer_id: chat ID
209+
:return: message ID of the sent message
210+
"""
211+
response = self.call_method(
212+
'messages.send',
213+
dict(
214+
message=msg, peer_id=peer_id,
215+
disable_mentions=1, random_id=0))
216+
217+
if 'response' in response:
218+
message_id = response['response']
219+
self.pin_message(peer_id, message_id)
220+
return message_id
221+
return 0
222+
171223
def get_user_name(
172224
self,
173225
uid: int,

python/modules/commands.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from .commands_builder import CommandsBuilder
1414
from .data_service import BetterBotBaseDataService
1515
from .data_builder import DataBuilder
16+
from .questions_service import QuestionsService
1617
from .utils import (
1718
get_default_programming_language,
1819
contains_all_strings,
@@ -43,6 +44,7 @@ def __init__(
4344
self.selected_message: Dict[str, Any] = {}
4445
self.vk_instance: Vk = vk_instance
4546
self.data_service: BetterBotBaseDataService = data_service
47+
self.questions_service: QuestionsService = QuestionsService()
4648
self.matched: Match = None
4749
wikipedia.set_lang('en')
4850

@@ -379,6 +381,175 @@ def github_copilot(self) -> NoReturn:
379381
f'Пожалуйста, подождите {round(config.GITHUB_COPILOT_TIMEOUT - (now - self.now))} секунд', self.peer_id
380382
)
381383

384+
def ask_question(self) -> NoReturn:
385+
"""Submit a new question to the questions desk."""
386+
if not self.karma_enabled:
387+
self.vk_instance.send_msg(
388+
"Вопросы доступны только в чатах с включенной кармой.",
389+
self.peer_id
390+
)
391+
return
392+
393+
question = self.matched.group('question').strip()
394+
reward_str = self.matched.group('reward')
395+
reward = 0
396+
397+
if reward_str:
398+
try:
399+
reward = int(reward_str)
400+
if reward < 0:
401+
self.vk_instance.send_msg(
402+
"Награда не может быть отрицательной.",
403+
self.peer_id
404+
)
405+
return
406+
if reward > self.current_user.karma:
407+
self.vk_instance.send_msg(
408+
f"У вас недостаточно кармы. Доступно: {self.current_user.karma}",
409+
self.peer_id
410+
)
411+
return
412+
except ValueError:
413+
reward = 0
414+
415+
# Deduct karma if reward is specified
416+
if reward > 0:
417+
self.current_user.karma -= reward
418+
self.data_service.save_user(self.current_user)
419+
420+
# Add question to the desk
421+
question_id = self.questions_service.add_question(
422+
question=question,
423+
user_id=self.current_user.uid,
424+
user_name=self.current_user.name,
425+
peer_id=self.peer_id,
426+
reward=reward
427+
)
428+
429+
# Send confirmation
430+
reward_text = f" с наградой {reward} кармы" if reward > 0 else ""
431+
self.vk_instance.send_msg(
432+
f"Вопрос #{question_id} добавлен на доску{reward_text}.\n\n"
433+
f"Вопрос: {question}",
434+
self.peer_id
435+
)
436+
437+
# Update pinned message
438+
self.update_questions_desk()
439+
440+
def resolve_question(self) -> NoReturn:
441+
"""Resolve a question and award karma to resolver."""
442+
if not self.karma_enabled:
443+
self.vk_instance.send_msg(
444+
"Вопросы доступны только в чатах с включенной кармой.",
445+
self.peer_id
446+
)
447+
return
448+
449+
question_id = int(self.matched.group('question_id'))
450+
question = self.questions_service.get_question_by_id(question_id)
451+
452+
if not question:
453+
self.vk_instance.send_msg(
454+
f"Вопрос #{question_id} не найден.",
455+
self.peer_id
456+
)
457+
return
458+
459+
if question['status'] != 'open':
460+
self.vk_instance.send_msg(
461+
f"Вопрос #{question_id} уже решен.",
462+
self.peer_id
463+
)
464+
return
465+
466+
if question['peer_id'] != self.peer_id:
467+
self.vk_instance.send_msg(
468+
f"Вопрос #{question_id} из другого чата.",
469+
self.peer_id
470+
)
471+
return
472+
473+
# Resolve the question
474+
resolved_question = self.questions_service.resolve_question(
475+
question_id=question_id,
476+
resolver_id=self.current_user.uid,
477+
resolver_name=self.current_user.name
478+
)
479+
480+
if resolved_question:
481+
# Award karma to resolver
482+
reward = resolved_question['reward']
483+
if reward > 0:
484+
self.current_user.karma += reward
485+
self.data_service.save_user(self.current_user)
486+
487+
# Send confirmation
488+
reward_text = f" и получил {reward} кармы" if reward > 0 else ""
489+
self.vk_instance.send_msg(
490+
f"{self.current_user.name} решил вопрос #{question_id}{reward_text}!\n\n"
491+
f"Вопрос: {resolved_question['question']}",
492+
self.peer_id
493+
)
494+
495+
# Update pinned message
496+
self.update_questions_desk()
497+
498+
def show_questions_desk(self) -> NoReturn:
499+
"""Display the current questions desk."""
500+
open_questions = self.questions_service.get_open_questions(self.peer_id)
501+
502+
if not open_questions:
503+
self.vk_instance.send_msg(
504+
"📋 Доска вопросов пуста.\n\n"
505+
"Используйте 'ask [вопрос] [награда]' чтобы добавить вопрос.",
506+
self.peer_id
507+
)
508+
return
509+
510+
message = "📋 Доска вопросов:\n\n"
511+
512+
for i, question in enumerate(open_questions[:10], 1): # Show top 10
513+
reward_text = f" (🏆 {question['reward']})" if question['reward'] > 0 else ""
514+
message += f"{i}. #{question['id']}: {question['question']}{reward_text}\n"
515+
message += f" от {question['user_name']}\n\n"
516+
517+
if len(open_questions) > 10:
518+
message += f"... и еще {len(open_questions) - 10} вопросов\n\n"
519+
520+
message += "Используйте 'resolve [ID]' чтобы решить вопрос."
521+
522+
self.vk_instance.send_msg(message, self.peer_id)
523+
524+
def update_questions_desk(self) -> NoReturn:
525+
"""Update the pinned message with current questions."""
526+
if not self.karma_enabled:
527+
return
528+
529+
# Generate the questions desk message
530+
desk_message = self.questions_service.generate_questions_desk_message(self.peer_id)
531+
532+
# Get current pinned message ID
533+
current_pinned = self.questions_service.get_pinned_message(self.peer_id)
534+
535+
try:
536+
# If there's already a pinned message, unpin it first
537+
if current_pinned:
538+
self.vk_instance.unpin_message(self.peer_id)
539+
540+
# Send and pin the new message
541+
if hasattr(self.vk_instance, 'send_and_pin_msg'):
542+
message_id = self.vk_instance.send_and_pin_msg(desk_message, self.peer_id)
543+
if message_id:
544+
self.questions_service.set_pinned_message(self.peer_id, message_id)
545+
else:
546+
# Fallback if method doesn't exist
547+
self.vk_instance.send_msg(desk_message, self.peer_id)
548+
except Exception as e:
549+
# If pinning fails, just send the message normally
550+
print(f"Pinning failed: {e}")
551+
self.vk_instance.send_msg(desk_message, self.peer_id)
552+
382553
def match_command(
383554
self,
384555
pattern: Pattern

python/modules/commands_builder.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@ def build_help_message(
2525
f"Документация — {documentation_link}")
2626
elif peer_id > 2e9:
2727
if karma:
28+
questions_help = ("\n\n📋 Доска вопросов:\n"
29+
"• ask [вопрос] [награда] — добавить вопрос с кармой в награду\n"
30+
"• resolve [ID] — решить вопрос и получить награду\n"
31+
"• questions — показать доску вопросов")
2832
return ("Вы находитесь в беседе с включённой кармой.\n"
29-
f"Документация — {documentation_link}")
33+
f"Документация — {documentation_link}{questions_help}")
3034
else:
3135
return (f"Вы находитесь в беседе (#{peer_id}) с выключенной кармой.\n"
3236
f"Документация — {documentation_link}")

0 commit comments

Comments
 (0)