Skip to content

Commit ad6c91e

Browse files
authored
Ticket watcher (#45)
- Ticket and Status created - bot now has ticket commands (`add_ticket`, `remove_ticket` and `list_tickets`) - TicketWatcher created
1 parent 783a1b0 commit ad6c91e

18 files changed

Lines changed: 334 additions & 32 deletions

Makefile

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,15 @@ docker.bot.stop:
6161

6262
docker.bot.restart: docker.bot.stop docker.up
6363

64-
docker.worker.stop:
65-
sudo docker stop bot-worker
64+
docker.session_watcher.stop:
65+
sudo docker stop session-watcher
6666

67-
docker.worker.restart: docker.worker.stop docker.up
67+
docker.session_watcher.restart: docker.session_watcher.stop docker.up
68+
69+
docker.ticket_watcher.stop:
70+
sudo docker stop ticket-watcher
71+
72+
docker.ticket_watcher.restart: docker.ticket_watcher.stop docker.up
6873

6974
docker.volumes.remove: docker.down
7075
sudo docker volume rm $(current_dir)_mongo_volume

bot/commands/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
from bot.commands.callback import config_handlers as callback_handlers
44
from bot.commands.exchange import config_handlers as exchange_handlers
55
from bot.commands.subject import config_handlers as subject_handlers
6+
from bot.commands.ticket import config_handlers as ticket_handlers
67

78
handlers = [
89
base_handlers,
910
subscription_handlers,
1011
callback_handlers,
1112
exchange_handlers,
12-
subject_handlers
13+
subject_handlers,
14+
ticket_handlers
1315
]

bot/commands/subject.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from mongoengine.errors import DoesNotExist, MultipleObjectsReturned
66

77
from bot.core import BotTelegramCore
8-
from bot.messages import NOW_GROUP_RESTRICTED
8+
from bot.messages import GROUP_RESTRICTED, CALL_NOW
99
from db.subject import Subject
1010
from db.observer import UserObserver
1111
from db.update_message import UpdateMessage
@@ -25,7 +25,8 @@ def now(update: Update, context: CallbackContext):
2525
if not chat.type == "private":
2626
available_vsps = "\n".join([subject.header for
2727
subject in Subject.objects.all()])
28-
message.reply_text(NOW_GROUP_RESTRICTED, parse_mode='MARKDOWN')
28+
message.reply_text(f"{GROUP_RESTRICTED} {CALL_NOW}",
29+
parse_mode='MARKDOWN')
2930
message.reply_text(f"<b>Available VSP's are:</b>\n\n{available_vsps}",
3031
parse_mode='HTML')
3132
return

bot/commands/ticket.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import logging
2+
3+
from mongoengine.errors import DoesNotExist
4+
from telegram import Update
5+
from telegram.ext import CommandHandler, CallbackContext
6+
7+
from bot.core import BotTelegramCore
8+
from bot.messages import GROUP_RESTRICTED, TX_ID_ERROR
9+
from db.observer import UserObserver
10+
from db.ticket import Ticket
11+
12+
13+
logging.basicConfig(
14+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
15+
level=logging.INFO)
16+
17+
logger = logging.getLogger(__name__)
18+
19+
20+
def add_ticket(update: Update, context: CallbackContext):
21+
chat = update.effective_chat
22+
message = update.effective_message
23+
24+
if chat.type != "private":
25+
message.reply_text(GROUP_RESTRICTED)
26+
return
27+
28+
try:
29+
observer = UserObserver.objects.get(chat_id=f"{chat.id}")
30+
except DoesNotExist:
31+
observer = UserObserver(f"{chat.username}",
32+
f"{chat.id}").save()
33+
34+
try:
35+
tx_id = context.args[0]
36+
except IndexError:
37+
message.reply_text(TX_ID_ERROR)
38+
return
39+
40+
try:
41+
Ticket.objects.get(observer=observer, tx_id=tx_id)
42+
message.reply_text(f"Ticket with transaction id {tx_id}"
43+
f" is already registered!", parse_mode='HTML')
44+
return
45+
except DoesNotExist:
46+
ticket = Ticket(observer, tx_id)
47+
48+
if ticket.fetch():
49+
message.reply_text(f"Ticket has been saved!"
50+
f"\n\n{ticket.html}", parse_mode='HTML')
51+
52+
53+
def remove_ticket(update: Update, context: CallbackContext):
54+
chat = update.effective_chat
55+
message = update.effective_message
56+
57+
if chat.type != "private":
58+
message.reply_text(GROUP_RESTRICTED)
59+
return
60+
61+
try:
62+
observer = UserObserver.objects.get(chat_id=f"{chat.id}")
63+
except DoesNotExist:
64+
observer = UserObserver(f"{chat.username}",
65+
f"{chat.id}").save()
66+
67+
try:
68+
tx_id = context.args[0]
69+
except IndexError:
70+
message.reply_text(TX_ID_ERROR)
71+
return
72+
73+
try:
74+
ticket = Ticket.objects.get(observer=observer, tx_id=tx_id)
75+
except DoesNotExist:
76+
message.reply_text(f"Ticket with transaction id {tx_id} doesn't exist!")
77+
return
78+
79+
ticket.delete()
80+
message.reply_text(f"Ticket {tx_id} has been removed!")
81+
82+
83+
def list_tickets(update: Update, context: CallbackContext):
84+
chat = update.effective_chat
85+
message = update.effective_message
86+
87+
if chat.type != "private":
88+
message.reply_text(GROUP_RESTRICTED)
89+
return
90+
91+
try:
92+
observer = UserObserver.objects.get(chat_id=f"{chat.id}")
93+
except DoesNotExist:
94+
observer = UserObserver(f"{chat.username}",
95+
f"{chat.id}").save()
96+
97+
tickets = Ticket.objects(observer=observer)
98+
99+
if not tickets:
100+
message.reply_text("<b>There are no watched tickets!"
101+
"</b>", parse_mode='HTML')
102+
return
103+
104+
text = "<b>Watched tickets</b>\n\n"
105+
for index, ticket in enumerate(tickets):
106+
text += ticket.html
107+
text += '\n' if index != len(tickets) else ''
108+
109+
message.reply_text(text, parse_mode='HTML')
110+
111+
112+
def config_handlers(instance: BotTelegramCore):
113+
logger.info("Setting ticket commands...")
114+
115+
instance.add_handler(CommandHandler("add_ticket", add_ticket))
116+
instance.add_handler(CommandHandler("remove_ticket", remove_ticket))
117+
instance.add_handler(CommandHandler("list_tickets", list_tickets))

bot/messages.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,8 @@
66

77
ADMIN_RESTRICTED = "This command is restricted to admins only!"
88

9-
NOW_GROUP_RESTRICTED = "The command `/now` without args is restricted on groups! Please call `/now` with args"
9+
GROUP_RESTRICTED = "This command is restricted on groups!"
10+
11+
CALL_NOW = "Please call `/now` with args"
12+
13+
TX_ID_ERROR = "Please pass a valid transaction id!"

db/observer.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,12 @@ def notify(self, update_message):
5454
self._remove_last_message_from_subject(update_message.subject)
5555
self._send_update_message(update_message)
5656

57+
def send_message(self, message):
58+
return BotTelegramCore.send_message(
59+
f'{message}', chat_id=self.chat_id)
60+
5761
def _send_update_message(self, update_message):
58-
telegram_message = BotTelegramCore.send_message(
59-
f'{update_message}', chat_id=self.chat_id)
62+
telegram_message = self.send_message(update_message)
6063
self._create_message(telegram_message.message_id,
6164
update_message.subject)
6265

db/ticket.py

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33

44
import pendulum
55
from mongoengine import (
6-
Document,
7-
FloatField, DateTimeField)
6+
Document, ReferenceField,
7+
FloatField, DateTimeField, StringField)
88

9+
from db.observer import Observer
910
from utils.dcrdata import request_dcr_data
11+
from utils.exceptions import DcrDataAPIError
1012

1113

1214
logging.basicConfig(
@@ -65,3 +67,100 @@ def get_last(cls):
6567
cls.lock.release()
6668

6769
return last_ticket_price
70+
71+
72+
class Status(Document):
73+
name = StringField(max_length=8, unique=True)
74+
75+
def __eq__(self, other):
76+
name = None
77+
78+
if isinstance(other, Status):
79+
name = other.name
80+
elif isinstance(other, str):
81+
name = other
82+
return self.name == name
83+
84+
def __str__(self):
85+
return self.name
86+
87+
@classmethod
88+
def immature(cls):
89+
return cls.objects.get(name='immature')
90+
91+
@classmethod
92+
def live(cls):
93+
return cls.objects.get(name='live')
94+
95+
@classmethod
96+
def voted(cls):
97+
return cls.objects.get(name='voted')
98+
99+
100+
class Ticket(Document):
101+
observer = ReferenceField(Observer, unique_with='tx_id')
102+
tx_id = StringField(max_length=64, required=True)
103+
_status = ReferenceField(Status)
104+
vote_id = StringField(max_length=64)
105+
106+
def __str__(self):
107+
message = f"tx {self.tx_id}\n" \
108+
f"status: {self.status}"
109+
if self.vote_id:
110+
message += f"\nvote: {self.vote_id}"
111+
return message
112+
113+
@property
114+
def html(self):
115+
message = f"<b>tx</b>: {self.tx_link}\n" \
116+
f"<b>status</b>: {self.status}"
117+
if self.vote_id:
118+
message += f"\n<b>vote</b>: {self.vote_link}"
119+
return message
120+
121+
def is_same_status(self, new_status_name):
122+
return self.status == new_status_name
123+
124+
@property
125+
def status(self):
126+
return self._status
127+
128+
@status.setter
129+
def status(self, new_status_name):
130+
self._status = Status.objects.get(name=new_status_name)
131+
132+
@property
133+
def tx_link(self):
134+
return f"<a href='https://dcrdata.decred.org/tx/" \
135+
f"{self.tx_id}'>{self.tx_id}</a>"
136+
137+
@property
138+
def vote_link(self):
139+
return f"<a href='https://dcrdata.decred.org/tx/" \
140+
f"{self.vote_id}'>{self.vote_id}</a>"
141+
142+
def notify(self):
143+
self.observer.send_message(self.html)
144+
145+
def fetch(self):
146+
logger.debug(f"fetching ticket {self}")
147+
try:
148+
data = request_dcr_data(f"tx/{self.tx_id}/tinfo")
149+
except DcrDataAPIError as e:
150+
self.delete()
151+
self.observer.send_message(e)
152+
self.observer.send_message(f"Your ticket was removed!")
153+
return False
154+
155+
status = data.get('status')
156+
157+
if self.is_same_status(status):
158+
return True
159+
160+
self.status = status
161+
if self.status == Status.voted():
162+
self.vote_id = data.get('vote')
163+
164+
self.save()
165+
self.notify()
166+
return True

docker-compose.arm.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ services:
2323
- mongo
2424
env_file: .env
2525

26-
bot-worker:
26+
session-watcher:
2727
build:
2828
context: .
2929
dockerfile: Dockerfile.arm
30-
container_name: bot-worker
31-
command: python -m sws.client
30+
container_name: session-watcher
31+
command: python -m watchers.session
3232
depends_on:
3333
- mongo
3434
env_file: .env

docker-compose.prod.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ services:
2121
- mongo
2222
env_file: .env
2323

24-
bot-worker:
24+
session-watcher:
2525
image: dcrguys/jack_bot
26-
container_name: bot-worker
27-
command: python -m sws.client
26+
container_name: session-watcher
27+
command: python -m watchers.session
2828
depends_on:
2929
- mongo
3030
env_file: .env

docker-compose.yml

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,24 @@ services:
2525
- .:/bot
2626
env_file: .env
2727

28-
bot-worker:
28+
session-watcher:
2929
build:
3030
context: .
3131
dockerfile: Dockerfile
32-
container_name: bot-worker
33-
command: python -m sws.client
32+
container_name: session-watcher
33+
command: python -m watchers.session
34+
depends_on:
35+
- mongo
36+
volumes:
37+
- .:/bot
38+
env_file: .env
39+
40+
ticket-watcher:
41+
build:
42+
context: .
43+
dockerfile: Dockerfile
44+
container_name: ticket-watcher
45+
command: python -m watchers.ticket
3446
depends_on:
3547
- mongo
3648
volumes:

0 commit comments

Comments
 (0)