leadguru-jobs 0.614.0__py3-none-any.whl → 0.616.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.616.0.dist-info}/METADATA +1 -1
- {leadguru_jobs-0.614.0.dist-info → leadguru_jobs-0.616.0.dist-info}/RECORD +8 -8
- {leadguru_jobs-0.614.0.dist-info → leadguru_jobs-0.616.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 +99 -6
- lgt_jobs/lgt_data/mongo_repository.py +5 -0
- {leadguru_jobs-0.614.0.dist-info → leadguru_jobs-0.616.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=KSpsImqJhjoTTBuo2XszNpHBu8hFQdgEXZnWMHx6k8Q,33081
|
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.616.0.dist-info/METADATA,sha256=H0m_mcWzj2Wijg_C80NOKaXZJW0aphAL8Ei4zosw640,1319
|
52
|
+
leadguru_jobs-0.616.0.dist-info/WHEEL,sha256=-oYQCr74JF3a37z2nRlQays_SX2MqOANoqVjBBAP2yE,91
|
53
|
+
leadguru_jobs-0.616.0.dist-info/top_level.txt,sha256=rIuw1DqwbnZyeoarBSC-bYeGOhv9mZBs7_afl9q4_JI,9
|
54
|
+
leadguru_jobs-0.616.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,96 @@ 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.day: int | None = None
|
264
|
+
self.hour: int | None = None
|
265
|
+
self.minute: int | None = None
|
266
|
+
self.last_notification: datetime | None = None
|
267
|
+
self.need_to_notify: bool = False
|
268
|
+
self.attributes: list[str] = []
|
269
|
+
|
270
|
+
@property
|
271
|
+
def need_to_notify_now(self) -> bool:
|
272
|
+
if not self.enabled or not self.need_to_notify:
|
273
|
+
return False
|
274
|
+
|
275
|
+
now = datetime.now(UTC)
|
276
|
+
current_week_day = datetime.isoweekday(now)
|
277
|
+
if self.type == NotificationType.ONCE_A_WEEK and current_week_day != self.day:
|
278
|
+
return False
|
279
|
+
|
280
|
+
if ((self.type == NotificationType.ONCE_A_DAY or self.type == NotificationType.ONCE_A_WEEK)
|
281
|
+
and now.hour != self.hour and now.minute < self.minute):
|
282
|
+
return False
|
283
|
+
|
284
|
+
if ((self.type == NotificationType.ONCE_A_DAY or self.type == NotificationType.ONCE_A_WEEK)
|
285
|
+
and self.last_notification and self.last_notification > now - timedelta(hours=1)):
|
286
|
+
return False
|
287
|
+
|
288
|
+
return True
|
289
|
+
|
290
|
+
@staticmethod
|
291
|
+
def need_to_notify_week_before(date: datetime) -> bool:
|
292
|
+
return datetime.now(UTC) < (date + timedelta(7))
|
293
|
+
|
294
|
+
|
295
|
+
class NotificationSettings(DictionaryModel):
|
296
|
+
def __init__(self):
|
297
|
+
self.incoming_messages: Notification | None = None
|
298
|
+
self.inbox: Notification | None = None
|
299
|
+
self.source_deactivation: Notification | None = None
|
300
|
+
self.billing: Notification | None = None
|
301
|
+
self.bulk_replies: Notification | None = None
|
302
|
+
self.bulk_reactions: Notification | None = None
|
303
|
+
self.follow_ups: Notification | None = None
|
304
|
+
|
305
|
+
@classmethod
|
306
|
+
def from_dic(cls, dic: dict):
|
307
|
+
if not dic:
|
308
|
+
return None
|
309
|
+
|
310
|
+
model: NotificationSettings = cls()
|
311
|
+
model.incoming_messages = Notification.from_dic(dic.get('incoming_messages'))
|
312
|
+
model.inbox = Notification.from_dic(dic.get('inbox'))
|
313
|
+
model.source_deactivation = Notification.from_dic(dic.get('source_deactivation'))
|
314
|
+
model.billing = Notification.from_dic(dic.get('billing'))
|
315
|
+
model.bulk_replies = Notification.from_dic(dic.get('bulk_replies'))
|
316
|
+
model.bulk_reactions = Notification.from_dic(dic.get('bulk_reactions'))
|
317
|
+
model.follow_ups = Notification.from_dic(dic.get('follow_ups'))
|
318
|
+
return model
|
319
|
+
|
320
|
+
def to_dic(self):
|
321
|
+
result = copy.deepcopy(self.__dict__)
|
322
|
+
|
323
|
+
if result.get('incoming_messages'):
|
324
|
+
result['incoming_messages'] = Notification.to_dic(result.get('incoming_messages'))
|
325
|
+
if result.get('inbox'):
|
326
|
+
result['inbox'] = Notification.to_dic(result.get('inbox'))
|
327
|
+
if result.get('source_deactivation'):
|
328
|
+
result['source_deactivation'] = Notification.to_dic(result.get('source_deactivation'))
|
329
|
+
if result.get('billing'):
|
330
|
+
result['billing'] = Notification.to_dic(result.get('billing'))
|
331
|
+
if result.get('bulk_replies'):
|
332
|
+
result['bulk_replies'] = Notification.to_dic(result.get('bulk_replies'))
|
333
|
+
if result.get('bulk_reactions'):
|
334
|
+
result['bulk_reactions'] = Notification.to_dic(result.get('bulk_reactions'))
|
335
|
+
if result.get('follow_ups'):
|
336
|
+
result['follow_ups'] = Notification.to_dic(result.get('follow_ups'))
|
337
|
+
|
338
|
+
return result
|
339
|
+
|
340
|
+
|
341
|
+
class GeneralSettings(DictionaryModel):
|
342
|
+
def __init__(self):
|
343
|
+
self.theme: str | None = None
|
344
|
+
self.ask_pipeline_and_status: bool = False
|
345
|
+
self.ask_follow_up: bool = False
|
346
|
+
self.dashboard_is_starting_page: bool = False
|
347
|
+
|
348
|
+
|
259
349
|
class UserModel(BaseModel):
|
260
350
|
def __init__(self):
|
261
351
|
super().__init__()
|
@@ -271,13 +361,11 @@ class UserModel(BaseModel):
|
|
271
361
|
self.company_web_site: str = ''
|
272
362
|
self.company_description: str = ''
|
273
363
|
self.position: str = ''
|
274
|
-
self.new_message_notified_at: Optional[datetime] = None
|
364
|
+
# self.new_message_notified_at: Optional[datetime] = None # TODO: Move to settings
|
275
365
|
self.leads_limit: Optional[int] = None
|
276
366
|
self.leads_proceeded: Optional[int] = None
|
277
367
|
self.leads_filtered: Optional[int] = None
|
278
368
|
self.leads_limit_updated_at: Optional[int] = None
|
279
|
-
self.keywords: Optional[List[str]] = None
|
280
|
-
self.block_words: Optional[List[str]] = None
|
281
369
|
self.paid_lead_price: int = 1
|
282
370
|
self.state: int = 0
|
283
371
|
self.credits_exceeded_at: Optional[datetime] = None
|
@@ -292,6 +380,8 @@ class UserModel(BaseModel):
|
|
292
380
|
self.subscription_name: str | None = None
|
293
381
|
self.subscription_expiration_notified = False
|
294
382
|
self.subscription_expiration_warning_notified = False
|
383
|
+
self.notification_settings: NotificationSettings | None = None
|
384
|
+
self.general_setting: GeneralSettings | None = None
|
295
385
|
|
296
386
|
@classmethod
|
297
387
|
def from_dic(cls, dic: dict):
|
@@ -308,13 +398,16 @@ class UserModel(BaseModel):
|
|
308
398
|
model.slack_profile = Profile.from_dic(dic.get('slack_profile'))
|
309
399
|
model.slack_users = [SlackUser.from_dic(user) for user in dic.get('slack_users', [])]
|
310
400
|
model.discord_users = [DiscordUser.from_dic(user) for user in dic.get('discord_users', [])]
|
401
|
+
model.notification_settings = NotificationSettings.from_dic(dic.get('notification_settings'))
|
311
402
|
return model
|
312
403
|
|
313
404
|
def to_dic(self):
|
314
405
|
result = copy.deepcopy(self.__dict__)
|
315
406
|
|
316
|
-
if result.get('slack_profile'
|
407
|
+
if result.get('slack_profile'):
|
317
408
|
result['slack_profile'] = result.get('slack_profile').__dict__
|
409
|
+
if result.get('notification_settings'):
|
410
|
+
result['notification_settings'] = NotificationSettings.to_dic(result.get('notification_settings'))
|
318
411
|
|
319
412
|
return result
|
320
413
|
|
@@ -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
|