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