leadguru-jobs 0.614.0__tar.gz → 0.616.0__tar.gz

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.
Files changed (61) hide show
  1. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/PKG-INFO +1 -1
  2. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/leadguru_jobs.egg-info/PKG-INFO +1 -1
  3. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/jobs/chat_history.py +11 -31
  4. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/lgt_data/enums.py +7 -6
  5. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/lgt_data/model.py +99 -6
  6. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/lgt_data/mongo_repository.py +5 -0
  7. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/MANIFEST.in +0 -0
  8. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/README.md +0 -0
  9. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/leadguru_jobs.egg-info/SOURCES.txt +0 -0
  10. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/leadguru_jobs.egg-info/dependency_links.txt +0 -0
  11. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/leadguru_jobs.egg-info/not-zip-safe +0 -0
  12. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/leadguru_jobs.egg-info/requires.txt +0 -0
  13. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/leadguru_jobs.egg-info/top_level.txt +0 -0
  14. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/__init__.py +0 -0
  15. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/assets/images/arrow.png +0 -0
  16. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/assets/images/firework.png +0 -0
  17. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/assets/images/lock.png +0 -0
  18. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/assets/images/logo.png +0 -0
  19. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/assets/images/mail.png +0 -0
  20. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/basejobs.py +0 -0
  21. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/env.py +0 -0
  22. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/jobs/__init__.py +0 -0
  23. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/jobs/analytics.py +0 -0
  24. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/jobs/bot_stats_update.py +0 -0
  25. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/jobs/connect_sources.py +0 -0
  26. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/jobs/inbox_leads.py +0 -0
  27. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/jobs/load_slack_people.py +0 -0
  28. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/jobs/mass_message.py +0 -0
  29. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/jobs/send_code.py +0 -0
  30. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/jobs/send_slack_message.py +0 -0
  31. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/jobs/update_slack_profile.py +0 -0
  32. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/jobs/user_balance_update.py +0 -0
  33. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/jobs/workspace_connect.py +0 -0
  34. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/lgt_common/__init__.py +0 -0
  35. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/lgt_common/discord_client/__init__.py +0 -0
  36. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/lgt_common/discord_client/discord_client.py +0 -0
  37. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/lgt_common/discord_client/methods.py +0 -0
  38. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/lgt_common/enums/__init__.py +0 -0
  39. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/lgt_common/enums/slack_errors.py +0 -0
  40. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/lgt_common/helpers.py +0 -0
  41. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/lgt_common/lgt_logging.py +0 -0
  42. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/lgt_common/pubsub/__init__.py +0 -0
  43. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/lgt_common/pubsub/messages.py +0 -0
  44. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/lgt_common/pubsub/pubsubfactory.py +0 -0
  45. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/lgt_common/slack_client/__init__.py +0 -0
  46. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/lgt_common/slack_client/methods.py +0 -0
  47. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/lgt_common/slack_client/slack_client.py +0 -0
  48. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/lgt_common/slack_client/web_client.py +0 -0
  49. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/lgt_data/__init__.py +0 -0
  50. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/lgt_data/analytics.py +0 -0
  51. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/lgt_data/engine.py +0 -0
  52. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/lgt_data/helpers.py +0 -0
  53. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/main.py +0 -0
  54. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/runner.py +0 -0
  55. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/services/__init__.py +0 -0
  56. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/services/web_client.py +0 -0
  57. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/simple_job.py +0 -0
  58. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/smtp.py +0 -0
  59. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/lgt_jobs/templates/new_message.html +0 -0
  60. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/setup.cfg +0 -0
  61. {leadguru_jobs-0.614.0 → leadguru_jobs-0.616.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: leadguru_jobs
3
- Version: 0.614.0
3
+ Version: 0.616.0
4
4
  Summary: LGT jobs builds
5
5
  Author-email: developer@leadguru.co
6
6
  Classifier: Development Status :: 5 - Production/Stable
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: leadguru_jobs
3
- Version: 0.614.0
3
+ Version: 0.616.0
4
4
  Summary: LGT jobs builds
5
5
  Author-email: developer@leadguru.co
6
6
  Classifier: Development Status :: 5 - Production/Stable
@@ -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,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', None):
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