leadguru-jobs 0.614.0__py3-none-any.whl → 0.615.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {leadguru_jobs-0.614.0.dist-info → leadguru_jobs-0.615.0.dist-info}/METADATA +1 -1
- {leadguru_jobs-0.614.0.dist-info → leadguru_jobs-0.615.0.dist-info}/RECORD +8 -8
- {leadguru_jobs-0.614.0.dist-info → leadguru_jobs-0.615.0.dist-info}/WHEEL +1 -1
- lgt_jobs/jobs/chat_history.py +11 -31
- lgt_jobs/lgt_data/enums.py +7 -6
- lgt_jobs/lgt_data/model.py +97 -6
- lgt_jobs/lgt_data/mongo_repository.py +5 -0
- {leadguru_jobs-0.614.0.dist-info → leadguru_jobs-0.615.0.dist-info}/top_level.txt +0 -0
@@ -13,7 +13,7 @@ lgt_jobs/assets/images/mail.png,sha256=eORzQcAMkFr7DjgtABVhJ_zFuXgO7OXv78lLF4b39
|
|
13
13
|
lgt_jobs/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
14
|
lgt_jobs/jobs/analytics.py,sha256=IIsWt4A1qUw3w-S-8W16uKY1FRVWfXXA41_mu4uCNiM,979
|
15
15
|
lgt_jobs/jobs/bot_stats_update.py,sha256=JRPPyqFvGHWuH_NQJNzIDy3DC0Joz14goNExqp-YUrw,8683
|
16
|
-
lgt_jobs/jobs/chat_history.py,sha256=
|
16
|
+
lgt_jobs/jobs/chat_history.py,sha256=uHJhZ9euY49k7iai56PqGSOuWBSV6buKpwtSqHxbV8I,5134
|
17
17
|
lgt_jobs/jobs/connect_sources.py,sha256=_eA86KnS3AC6YCI1xk7VCV7lFmPRxta-wUStfdmajQU,4790
|
18
18
|
lgt_jobs/jobs/inbox_leads.py,sha256=OSX-FNx27gWEKNBBc-hyq2odCxXytz7WHtQJajtz274,5670
|
19
19
|
lgt_jobs/jobs/load_slack_people.py,sha256=az3Pl8_0nUXizShpCksH6XMHFALLkta4QpMr_MkM9Io,3199
|
@@ -41,14 +41,14 @@ lgt_jobs/lgt_common/slack_client/web_client.py,sha256=6ybC5v-oK-Kuat8qM2fskS6nCn
|
|
41
41
|
lgt_jobs/lgt_data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
42
42
|
lgt_jobs/lgt_data/analytics.py,sha256=fiN88zcIxs_bRMmXL7ftp4FvBeJ5I7QBPE4tbwKJ39E,21689
|
43
43
|
lgt_jobs/lgt_data/engine.py,sha256=Rsbz-CApAo_TVDssdjBkv8v_fVOZm_Uh1S6W4fnaEWo,7728
|
44
|
-
lgt_jobs/lgt_data/enums.py,sha256=
|
44
|
+
lgt_jobs/lgt_data/enums.py,sha256=jXrOcpy4awVuiZ51nSncfFospg0KsBZ4UwL650_VZVg,2259
|
45
45
|
lgt_jobs/lgt_data/helpers.py,sha256=NDa-V5EYaJfkGoWsmQSwSe6N_jxNxs8tHRQzW1iST6k,480
|
46
|
-
lgt_jobs/lgt_data/model.py,sha256=
|
47
|
-
lgt_jobs/lgt_data/mongo_repository.py,sha256=
|
46
|
+
lgt_jobs/lgt_data/model.py,sha256=91A1X4Ik85rEb41JULaAE6vNr8FE81bAtPzuRHRoFMA,33026
|
47
|
+
lgt_jobs/lgt_data/mongo_repository.py,sha256=SKIVxqc1u4I_uXTa3gSJRomRLt_6zKbQtLreC4EZZ8w,47111
|
48
48
|
lgt_jobs/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
49
49
|
lgt_jobs/services/web_client.py,sha256=GLWsJkIC8rv6xLFaLwcMm4EwBlVDu0njORwkZqBJaE4,2086
|
50
50
|
lgt_jobs/templates/new_message.html,sha256=dZl8UmdAOOMq4nidvAgMFroSrTV7Pw0RWt2yLp_2idg,6989
|
51
|
-
leadguru_jobs-0.
|
52
|
-
leadguru_jobs-0.
|
53
|
-
leadguru_jobs-0.
|
54
|
-
leadguru_jobs-0.
|
51
|
+
leadguru_jobs-0.615.0.dist-info/METADATA,sha256=J3ML-PlTzx3YgMMysV_GF8WkqBus-MuD1zVeBFfpZOs,1319
|
52
|
+
leadguru_jobs-0.615.0.dist-info/WHEEL,sha256=-oYQCr74JF3a37z2nRlQays_SX2MqOANoqVjBBAP2yE,91
|
53
|
+
leadguru_jobs-0.615.0.dist-info/top_level.txt,sha256=rIuw1DqwbnZyeoarBSC-bYeGOhv9mZBs7_afl9q4_JI,9
|
54
|
+
leadguru_jobs-0.615.0.dist-info/RECORD,,
|
lgt_jobs/jobs/chat_history.py
CHANGED
@@ -3,15 +3,12 @@ from abc import ABC
|
|
3
3
|
from typing import Optional, List
|
4
4
|
import logging as log
|
5
5
|
from lgt_jobs.lgt_common.slack_client.web_client import SlackWebClient, SlackMessageConvertService
|
6
|
-
from lgt_jobs.lgt_data.
|
7
|
-
from lgt_jobs.lgt_data.model import ChatMessage, UserModel, UserContact, DedicatedBotModel
|
6
|
+
from lgt_jobs.lgt_data.model import ChatMessage, UserContact, DedicatedBotModel
|
8
7
|
from lgt_jobs.lgt_data.mongo_repository import UserMongoRepository, DedicatedBotRepository, UserContactsRepository, \
|
9
8
|
ChatRepository
|
10
9
|
from pydantic import BaseModel
|
11
|
-
from lgt_jobs.lgt_data.enums import SourceType
|
12
|
-
from lgt_jobs.runner import BaseBackgroundJob, BaseBackgroundJobData
|
13
|
-
from lgt_jobs.env import portal_url
|
14
|
-
from lgt_jobs.smtp import SendMailJobData, SendMailJob
|
10
|
+
from lgt_jobs.lgt_data.enums import SourceType
|
11
|
+
from lgt_jobs.runner import BaseBackgroundJob, BaseBackgroundJobData
|
15
12
|
|
16
13
|
"""
|
17
14
|
Load slack chat history
|
@@ -50,7 +47,7 @@ class LoadChatHistoryJob(BaseBackgroundJob, ABC):
|
|
50
47
|
|
51
48
|
log.info(f"[LoadChatHistoryJob]: processing {len(contacts)} contacts for user: {user.email}")
|
52
49
|
for contact in contacts:
|
53
|
-
message = self._update_history(
|
50
|
+
message = self._update_history(contact=contact, bot=bot)
|
54
51
|
|
55
52
|
if not message:
|
56
53
|
continue
|
@@ -65,13 +62,13 @@ class LoadChatHistoryJob(BaseBackgroundJob, ABC):
|
|
65
62
|
last_message = message
|
66
63
|
last_message_contact = contact
|
67
64
|
|
68
|
-
|
69
|
-
|
70
|
-
|
65
|
+
last_notification = user.notification_settings.incoming_messages.last_notification
|
66
|
+
has_to_be_notified = (not last_notification or (last_message and last_message.created_at > last_notification))
|
71
67
|
if last_message and has_to_be_notified and last_message.user == last_message_contact.sender_id:
|
72
|
-
|
73
|
-
|
74
|
-
|
68
|
+
user.notification_settings.incoming_messages.need_to_notify = True
|
69
|
+
user.notification_settings.incoming_messages.attributes = [last_message_contact.sender_id,
|
70
|
+
last_message_bot.source.source_id]
|
71
|
+
UserMongoRepository().set(data.user_id, notification_settings=user.notification_settings.to_dic())
|
75
72
|
|
76
73
|
def _get_new_messages(self, contact: UserContact, bot: DedicatedBotModel, slack_chat: List[ChatMessage]):
|
77
74
|
messages = self.chat_repo.get_list(sender_id=contact.sender_id, bot_id=bot.id)
|
@@ -82,7 +79,7 @@ class LoadChatHistoryJob(BaseBackgroundJob, ABC):
|
|
82
79
|
new_messages.append(message)
|
83
80
|
return new_messages
|
84
81
|
|
85
|
-
def _update_history(self,
|
82
|
+
def _update_history(self, contact: UserContact, bot: DedicatedBotModel) -> Optional[ChatMessage]:
|
86
83
|
slack_client = SlackWebClient(bot.token, bot.cookies)
|
87
84
|
try:
|
88
85
|
chat_id = slack_client.im_open(contact.sender_id).get('channel', {}).get('id')
|
@@ -111,20 +108,3 @@ class LoadChatHistoryJob(BaseBackgroundJob, ABC):
|
|
111
108
|
return new_messages[-1]
|
112
109
|
|
113
110
|
return None
|
114
|
-
|
115
|
-
@staticmethod
|
116
|
-
def _notify_about_new_messages(user: UserModel, contact: UserContact, bot: DedicatedBotModel, template_path: str):
|
117
|
-
with open(template_path, mode='r') as template_file:
|
118
|
-
html = template_file.read()
|
119
|
-
chat_url = f'{portal_url}/feed?senderId={contact.sender_id}&sourceId={bot.source.source_id}'
|
120
|
-
html = html.replace("$$USER_NAME$$", contact.name if hasattr(contact, 'name') else contact.real_name)
|
121
|
-
html = html.replace("$$PORTAL_LINK$$", chat_url)
|
122
|
-
html = html.replace("$$HELP_TEXT$$", get_help_text(user))
|
123
|
-
message_data = {
|
124
|
-
"html": html,
|
125
|
-
"subject": 'New message(s) on Leadguru',
|
126
|
-
"recipient": user.email,
|
127
|
-
"images": [ImageName.LOGO, ImageName.ARROW, ImageName.MAIL]
|
128
|
-
}
|
129
|
-
|
130
|
-
BackgroundJobRunner.submit(SendMailJob, SendMailJobData(**message_data))
|
lgt_jobs/lgt_data/enums.py
CHANGED
@@ -54,12 +54,6 @@ class DefaultBoards(str, Enum):
|
|
54
54
|
Primary = 'Primary board'
|
55
55
|
|
56
56
|
|
57
|
-
class BotEventType(str, Enum):
|
58
|
-
CREATE = 'BotAdded'
|
59
|
-
UPDATE = 'BotUpdated'
|
60
|
-
DELETE = 'BotDeleted'
|
61
|
-
|
62
|
-
|
63
57
|
class ImageName(str, Enum):
|
64
58
|
ARROW = 'arrow.png'
|
65
59
|
CRY = 'cry.png'
|
@@ -96,3 +90,10 @@ class FeaturesEnum(str, Enum):
|
|
96
90
|
class FeatureOptions(str, Enum):
|
97
91
|
BASIC = 'basic'
|
98
92
|
ADVANCED = 'advanced'
|
93
|
+
|
94
|
+
|
95
|
+
class NotificationType(str, Enum):
|
96
|
+
INSTANTLY = 'instantly'
|
97
|
+
ONCE_A_DAY = 'once_a_day'
|
98
|
+
ONCE_A_WEEK = 'once_a_week'
|
99
|
+
WEEK_BEFORE = 'week_before'
|
lgt_jobs/lgt_data/model.py
CHANGED
@@ -2,9 +2,9 @@ from __future__ import annotations
|
|
2
2
|
import copy
|
3
3
|
import json
|
4
4
|
from abc import ABC
|
5
|
-
from datetime import datetime, UTC
|
5
|
+
from datetime import datetime, UTC, timedelta
|
6
6
|
from typing import Optional, List
|
7
|
-
from .enums import UserRole, SourceType, FeaturesEnum, FeatureOptions
|
7
|
+
from lgt_jobs.lgt_data.enums import UserRole, SourceType, FeaturesEnum, FeatureOptions, NotificationType
|
8
8
|
from bson import ObjectId
|
9
9
|
|
10
10
|
|
@@ -256,6 +256,94 @@ class MessageModel:
|
|
256
256
|
return result
|
257
257
|
|
258
258
|
|
259
|
+
class Notification(DictionaryModel):
|
260
|
+
def __init__(self):
|
261
|
+
self.enabled: bool = True
|
262
|
+
self.type: NotificationType = NotificationType.INSTANTLY
|
263
|
+
self.time: datetime | None = None
|
264
|
+
self.last_notification: datetime | None = None
|
265
|
+
self.need_to_notify: bool = False
|
266
|
+
self.attributes: list[str] = []
|
267
|
+
|
268
|
+
@property
|
269
|
+
def need_to_notify_now(self) -> bool:
|
270
|
+
if not self.enabled or not self.need_to_notify:
|
271
|
+
return False
|
272
|
+
|
273
|
+
now = datetime.now(UTC)
|
274
|
+
current_week_day = datetime.isoweekday(now)
|
275
|
+
if self.type == NotificationType.ONCE_A_WEEK and current_week_day != self.time.day:
|
276
|
+
return False
|
277
|
+
|
278
|
+
if ((self.type == NotificationType.ONCE_A_DAY or self.type == NotificationType.ONCE_A_WEEK)
|
279
|
+
and now.hour != self.time.hour and now.minute < self.time.minute):
|
280
|
+
return False
|
281
|
+
|
282
|
+
if ((self.type == NotificationType.ONCE_A_DAY or self.type == NotificationType.ONCE_A_WEEK)
|
283
|
+
and self.last_notification and self.last_notification > now - timedelta(hours=1)):
|
284
|
+
return False
|
285
|
+
|
286
|
+
return True
|
287
|
+
|
288
|
+
@staticmethod
|
289
|
+
def need_to_notify_week_before(date: datetime) -> bool:
|
290
|
+
return datetime.now(UTC) < (date + timedelta(7))
|
291
|
+
|
292
|
+
|
293
|
+
class NotificationSettings(DictionaryModel):
|
294
|
+
def __init__(self):
|
295
|
+
self.incoming_messages: Notification | None = None
|
296
|
+
self.inbox: Notification | None = None
|
297
|
+
self.source_deactivation: Notification | None = None
|
298
|
+
self.billing: Notification | None = None
|
299
|
+
self.bulk_replies: Notification | None = None
|
300
|
+
self.bulk_reactions: Notification | None = None
|
301
|
+
self.follow_ups: Notification | None = None
|
302
|
+
|
303
|
+
@classmethod
|
304
|
+
def from_dic(cls, dic: dict):
|
305
|
+
if not dic:
|
306
|
+
return None
|
307
|
+
|
308
|
+
model: NotificationSettings = cls()
|
309
|
+
model.incoming_messages = Notification.from_dic(dic.get('incoming_messages'))
|
310
|
+
model.inbox = Notification.from_dic(dic.get('inbox'))
|
311
|
+
model.source_deactivation = Notification.from_dic(dic.get('source_deactivation'))
|
312
|
+
model.billing = Notification.from_dic(dic.get('billing'))
|
313
|
+
model.bulk_replies = Notification.from_dic(dic.get('bulk_replies'))
|
314
|
+
model.bulk_reactions = Notification.from_dic(dic.get('bulk_reactions'))
|
315
|
+
model.follow_ups = Notification.from_dic(dic.get('follow_ups'))
|
316
|
+
return model
|
317
|
+
|
318
|
+
def to_dic(self):
|
319
|
+
result = copy.deepcopy(self.__dict__)
|
320
|
+
|
321
|
+
if result.get('incoming_messages'):
|
322
|
+
result['incoming_messages'] = Notification.to_dic(result.get('incoming_messages'))
|
323
|
+
if result.get('inbox'):
|
324
|
+
result['inbox'] = Notification.to_dic(result.get('inbox'))
|
325
|
+
if result.get('source_deactivation'):
|
326
|
+
result['source_deactivation'] = Notification.to_dic(result.get('source_deactivation'))
|
327
|
+
if result.get('billing'):
|
328
|
+
result['billing'] = Notification.to_dic(result.get('billing'))
|
329
|
+
if result.get('bulk_replies'):
|
330
|
+
result['bulk_replies'] = Notification.to_dic(result.get('bulk_replies'))
|
331
|
+
if result.get('bulk_reactions'):
|
332
|
+
result['bulk_reactions'] = Notification.to_dic(result.get('bulk_reactions'))
|
333
|
+
if result.get('follow_ups'):
|
334
|
+
result['follow_ups'] = Notification.to_dic(result.get('follow_ups'))
|
335
|
+
|
336
|
+
return result
|
337
|
+
|
338
|
+
|
339
|
+
class GeneralSettings(DictionaryModel):
|
340
|
+
def __init__(self):
|
341
|
+
self.theme: str | None = None
|
342
|
+
self.ask_pipeline_and_status: bool = False
|
343
|
+
self.ask_follow_up: bool = False
|
344
|
+
self.dashboard_is_starting_page: bool = False
|
345
|
+
|
346
|
+
|
259
347
|
class UserModel(BaseModel):
|
260
348
|
def __init__(self):
|
261
349
|
super().__init__()
|
@@ -271,13 +359,11 @@ class UserModel(BaseModel):
|
|
271
359
|
self.company_web_site: str = ''
|
272
360
|
self.company_description: str = ''
|
273
361
|
self.position: str = ''
|
274
|
-
self.new_message_notified_at: Optional[datetime] = None
|
362
|
+
# self.new_message_notified_at: Optional[datetime] = None # TODO: Move to settings
|
275
363
|
self.leads_limit: Optional[int] = None
|
276
364
|
self.leads_proceeded: Optional[int] = None
|
277
365
|
self.leads_filtered: Optional[int] = None
|
278
366
|
self.leads_limit_updated_at: Optional[int] = None
|
279
|
-
self.keywords: Optional[List[str]] = None
|
280
|
-
self.block_words: Optional[List[str]] = None
|
281
367
|
self.paid_lead_price: int = 1
|
282
368
|
self.state: int = 0
|
283
369
|
self.credits_exceeded_at: Optional[datetime] = None
|
@@ -292,6 +378,8 @@ class UserModel(BaseModel):
|
|
292
378
|
self.subscription_name: str | None = None
|
293
379
|
self.subscription_expiration_notified = False
|
294
380
|
self.subscription_expiration_warning_notified = False
|
381
|
+
self.notification_settings: NotificationSettings | None = None
|
382
|
+
self.general_setting: GeneralSettings | None = None
|
295
383
|
|
296
384
|
@classmethod
|
297
385
|
def from_dic(cls, dic: dict):
|
@@ -308,13 +396,16 @@ class UserModel(BaseModel):
|
|
308
396
|
model.slack_profile = Profile.from_dic(dic.get('slack_profile'))
|
309
397
|
model.slack_users = [SlackUser.from_dic(user) for user in dic.get('slack_users', [])]
|
310
398
|
model.discord_users = [DiscordUser.from_dic(user) for user in dic.get('discord_users', [])]
|
399
|
+
model.notification_settings = NotificationSettings.from_dic(dic.get('notification_settings'))
|
311
400
|
return model
|
312
401
|
|
313
402
|
def to_dic(self):
|
314
403
|
result = copy.deepcopy(self.__dict__)
|
315
404
|
|
316
|
-
if result.get('slack_profile'
|
405
|
+
if result.get('slack_profile'):
|
317
406
|
result['slack_profile'] = result.get('slack_profile').__dict__
|
407
|
+
if result.get('notification_settings'):
|
408
|
+
result['notification_settings'] = NotificationSettings.to_dic(result.get('notification_settings'))
|
318
409
|
|
319
410
|
return result
|
320
411
|
|
@@ -71,6 +71,7 @@ class UserMongoRepository(BaseMongoRepository):
|
|
71
71
|
subscription_expired = kwargs.get('subscription_expired')
|
72
72
|
connected_slack_email = kwargs.get('connected_slack_email')
|
73
73
|
soon_subscription_expiration = kwargs.get('soon_subscription_expiration')
|
74
|
+
has_new_message = kwargs.get('has_new_message')
|
74
75
|
min_days = kwargs.get('min_days_soon_subscription_expiration', 3)
|
75
76
|
|
76
77
|
if subscription_expired:
|
@@ -81,6 +82,10 @@ class UserMongoRepository(BaseMongoRepository):
|
|
81
82
|
pipeline['subscription_expired_at'] = {'$lte': datetime.now(UTC) + timedelta(min_days)}
|
82
83
|
pipeline['subscription_expiration_warning_notified'] = False
|
83
84
|
|
85
|
+
if has_new_message:
|
86
|
+
pipeline['notification_settings.incoming_messages.enabled'] = True
|
87
|
+
pipeline['notification_settings.incoming_messages.need_to_notify'] = True
|
88
|
+
|
84
89
|
if connected_slack_email:
|
85
90
|
pipeline['slack_users.email'] = connected_slack_email
|
86
91
|
|
File without changes
|