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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: leadguru_jobs
3
- Version: 0.614.0
3
+ Version: 0.615.0
4
4
  Summary: LGT jobs builds
5
5
  Author-email: developer@leadguru.co
6
6
  Classifier: Development Status :: 5 - Production/Stable
@@ -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=KiwiPl8FuI7FphfZT5VgKSTiO_W9j7tNyiSbnSe5GAs,6106
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=jBH5WGBtDAvFrh4iiPIzlQ-XrImMpuwqstuasG55mJ0,2209
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=7a10N7WAqX2ZdstHTQrvp87rtYB9UPhWLidOYQJnKXM,29040
47
- lgt_jobs/lgt_data/mongo_repository.py,sha256=m1UHtBwiZ8JGIMZ6ud2PZadm8sgq4InG0glR7-sBLwE,46849
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.614.0.dist-info/METADATA,sha256=0-McWeTe_tD4ftBoxZKKzosIGfXv4hU7Gbv5H3ljqJM,1319
52
- leadguru_jobs-0.614.0.dist-info/WHEEL,sha256=YiKiUUeZQGmGJoR_0N1Y933DOBowq4AIvDe2-UIy8E4,91
53
- leadguru_jobs-0.614.0.dist-info/top_level.txt,sha256=rIuw1DqwbnZyeoarBSC-bYeGOhv9mZBs7_afl9q4_JI,9
54
- leadguru_jobs-0.614.0.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (71.0.2)
2
+ Generator: setuptools (71.0.3)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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.helpers import get_help_text
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, ImageName
12
- from lgt_jobs.runner import BaseBackgroundJob, BaseBackgroundJobData, BackgroundJobRunner
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(user=user, contact=contact, bot=bot)
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
- has_to_be_notified = (not user.new_message_notified_at or
69
- (last_message and last_message.created_at > user.new_message_notified_at))
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
- LoadChatHistoryJob._notify_about_new_messages(user, last_message_contact, last_message_bot,
73
- data.template_path)
74
- UserMongoRepository().set(data.user_id, new_message_notified_at=datetime.now(UTC))
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, user: UserModel, contact: UserContact, bot: DedicatedBotModel) -> Optional[ChatMessage]:
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))
@@ -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'
@@ -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', None):
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