leadguru-jobs 0.561.0__py3-none-any.whl → 0.563.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.561.0
3
+ Version: 0.563.0
4
4
  Summary: LGT jobs builds
5
5
  Author-email: developer@leadguru.co
6
6
  Classifier: Development Status :: 5 - Production/Stable
@@ -1,4 +1,4 @@
1
- lgt_jobs/__init__.py,sha256=lrhMUJ7a5YSkYswQkQe981b_w8h8NHyncwfEduXEq6Q,1992
1
+ lgt_jobs/__init__.py,sha256=kmM5J3qBJd-fOZa2I4gkh36IytrgN6_18gWwwpR3n6Y,2156
2
2
  lgt_jobs/basejobs.py,sha256=LPEclvY6fIG17YMGy9pRK0Q58n62gkI5W1BTK6R2p-c,1234
3
3
  lgt_jobs/env.py,sha256=cRO03GGvstUjBsv3uYO-iakrOvAk_ZWUP_fnmf21iZQ,789
4
4
  lgt_jobs/main.py,sha256=cK_nkBtJHnUNDbba1WZotqPtI_6OWxiYQkAgco9OAmE,1539
@@ -13,11 +13,12 @@ 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=R6mf5FxhWbyx92AYXlj0FZVjz2Tgz86mBvLHrsQ9AFE,7502
16
- lgt_jobs/jobs/chat_history.py,sha256=QZdSWlk_DrDJt5yrNApYHiUEWQMYcKyuXaP_PlzkjYw,6303
16
+ lgt_jobs/jobs/chat_history.py,sha256=tZ4XyOGdy1k6r4uotxSSKbXjTR_oRK6Ym0YthFYBtl8,6200
17
+ lgt_jobs/jobs/connect_sources.py,sha256=uVO_kH-Hxn7HkYUNGNcS7Mu3jB7KlB4pU5qcdtR2hrM,4260
17
18
  lgt_jobs/jobs/inbox_leads.py,sha256=EYxdQPseNBY5ON7BK3LpnXDCgQpzHTPLRJCZmZiAAHA,5582
18
19
  lgt_jobs/jobs/mass_message.py,sha256=1mFcBlL2MhzLbj5yrd_NyJc7TXDWCcROAzGDnr0miMU,2035
19
20
  lgt_jobs/jobs/send_code.py,sha256=dIlLPkG3GgGKIEqGiElyzrtdrnJNTL1Ak2V0xnE-WIQ,824
20
- lgt_jobs/jobs/send_slack_message.py,sha256=WdXXh7QpjJKSvDU93YT9QKzoGRNBpPCJgiIxPs5gp1s,2782
21
+ lgt_jobs/jobs/send_slack_message.py,sha256=IuRqDGcDrz2EAZzF6nrLXO0aSA4mETB_tGOEbcMhjHE,2562
21
22
  lgt_jobs/jobs/update_slack_profile.py,sha256=0fUHBd2gEBne52sTfxwji2Wu-oYDM5dLGsL-rHq5kwM,2765
22
23
  lgt_jobs/jobs/user_balance_update.py,sha256=HxeEmDdloK9EcoEj1ybYTgxIkB0IQNLnss_kk3rLI1Q,2404
23
24
  lgt_jobs/jobs/workspace_connect.py,sha256=qgjIRzAQRbJBx--eYsTtYgOUh2-p3N_-LIltfbJrYYo,7102
@@ -35,18 +36,18 @@ lgt_jobs/lgt_common/pubsub/pubsubfactory.py,sha256=rfUDooYuhBQ2pE9FdDxZOpXjbrvlp
35
36
  lgt_jobs/lgt_common/slack_client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
37
  lgt_jobs/lgt_common/slack_client/methods.py,sha256=ctKF_1UHEmSWaRsoGEgMZicVabV7eEufZ7pjxdMAx8g,1455
37
38
  lgt_jobs/lgt_common/slack_client/slack_client.py,sha256=icHapSOszFYnSofJ-eWl4lUWSOqng6S9XSRFfMx87Rg,13836
38
- lgt_jobs/lgt_common/slack_client/web_client.py,sha256=hiexBYtCnwz0oZvU6GnlQkD3aDqt9v5CvfPWMbz3Z50,6311
39
+ lgt_jobs/lgt_common/slack_client/web_client.py,sha256=JrkEepL6EhEtX3Psv_sd25bcRB2P23LoGnFHcczQpnc,4778
39
40
  lgt_jobs/lgt_data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
41
  lgt_jobs/lgt_data/analytics.py,sha256=TFUffL8Dn-vIJ3ZaCVN-cj1Qnjb8F9GJyuk6Fo7mR1s,21645
41
42
  lgt_jobs/lgt_data/engine.py,sha256=Rsbz-CApAo_TVDssdjBkv8v_fVOZm_Uh1S6W4fnaEWo,7728
42
- lgt_jobs/lgt_data/enums.py,sha256=GZn6wm-kOd3veEOo1qlOR8QYje4vKaGV0na9jGt9388,2160
43
+ lgt_jobs/lgt_data/enums.py,sha256=jBH5WGBtDAvFrh4iiPIzlQ-XrImMpuwqstuasG55mJ0,2209
43
44
  lgt_jobs/lgt_data/helpers.py,sha256=NDa-V5EYaJfkGoWsmQSwSe6N_jxNxs8tHRQzW1iST6k,480
44
- lgt_jobs/lgt_data/model.py,sha256=DDfoWeH6rLWKoZsFJ2tI5Yagj1WlOa0PIJp-GqBIVSA,28112
45
- lgt_jobs/lgt_data/mongo_repository.py,sha256=qqbt-oJ-RHmICLARgWvHuyPwVeKFkUqJZ2MA1bfIlXs,44762
45
+ lgt_jobs/lgt_data/model.py,sha256=BtVpJC_TRXUOklpzwRiDywyuKCCV2GGXtjsRYcPibDc,28454
46
+ lgt_jobs/lgt_data/mongo_repository.py,sha256=wdhslZTfTU-3Ad4Gm8NVl9HZFM5aK_vgG47EqNQBARk,45179
46
47
  lgt_jobs/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
48
  lgt_jobs/services/web_client.py,sha256=GLWsJkIC8rv6xLFaLwcMm4EwBlVDu0njORwkZqBJaE4,2086
48
49
  lgt_jobs/templates/new_message.html,sha256=BD_E90akmQ1Wf07wtZAjeK_7DUKRmja5HFHVo_AKI24,6994
49
- leadguru_jobs-0.561.0.dist-info/METADATA,sha256=-bP6WNVz3lmRi9b290wt67FhtcWHWcskt_G98oSboBA,1319
50
- leadguru_jobs-0.561.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
51
- leadguru_jobs-0.561.0.dist-info/top_level.txt,sha256=rIuw1DqwbnZyeoarBSC-bYeGOhv9mZBs7_afl9q4_JI,9
52
- leadguru_jobs-0.561.0.dist-info/RECORD,,
50
+ leadguru_jobs-0.563.0.dist-info/METADATA,sha256=ByYQCPc556MrgZCGalD3QReO8x1QBG7hw0TgwIcs7ok,1319
51
+ leadguru_jobs-0.563.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
52
+ leadguru_jobs-0.563.0.dist-info/top_level.txt,sha256=rIuw1DqwbnZyeoarBSC-bYeGOhv9mZBs7_afl9q4_JI,9
53
+ leadguru_jobs-0.563.0.dist-info/RECORD,,
lgt_jobs/__init__.py CHANGED
@@ -3,6 +3,7 @@ name = "lgt_jobs"
3
3
  from .jobs.user_balance_update import UpdateUserBalanceJob, UpdateUserBalanceJobData
4
4
  from .jobs.send_slack_message import SendSlackMessageJob, SendSlackMessageJobData
5
5
  from .jobs.analytics import (TrackAnalyticsJob, TrackAnalyticsJobData)
6
+ from .jobs.connect_sources import (ConnectSourceJobData, ConnectSourceJob)
6
7
  from .jobs.bot_stats_update import (BotStatsUpdateJob, BotStatsUpdateJobData)
7
8
  from .jobs.chat_history import (LoadChatHistoryJob, LoadChatHistoryJobData)
8
9
  from .jobs.update_slack_profile import (UpdateExternalUserProfileJob, UpdateExternalUserProfileJobData)
@@ -22,6 +23,7 @@ jobs_map = {
22
23
  "SendSlackMessageJob": SendSlackMessageJob,
23
24
  "UpdateUserBalanceJob": UpdateUserBalanceJob,
24
25
  "SendMassMessageSlackChannelJob": SendMassMessageSlackChannelJob,
26
+ "ConnectSourceJob": ConnectSourceJob
25
27
  }
26
28
  __all__ = [
27
29
  # Jobs
@@ -35,6 +37,7 @@ __all__ = [
35
37
  SendSlackMessageJob,
36
38
  UpdateUserBalanceJob,
37
39
  SendMassMessageSlackChannelJob,
40
+ ConnectSourceJob,
38
41
 
39
42
  # module classes
40
43
  BackgroundJobRunner,
@@ -50,6 +53,7 @@ __all__ = [
50
53
  SendSlackMessageJobData,
51
54
  UpdateUserBalanceJobData,
52
55
  SendMassMessageSlackChannelJobData,
56
+ ConnectSourceJobData,
53
57
  # mapping
54
58
  jobs_map
55
59
  ]
@@ -100,9 +100,8 @@ class LoadChatHistoryJob(BaseBackgroundJob, ABC):
100
100
  if not messages:
101
101
  return None
102
102
 
103
- messages = [SlackMessageConvertService.from_slack_response(user.email, "slack_files",
104
- bot.token, m, contact.sender_id, bot.id,
105
- user.id, bot.cookies) for m in messages]
103
+ messages = [SlackMessageConvertService.from_slack_response(bot.user_name, m, contact.sender_id,
104
+ bot.id, user.id) for m in messages]
106
105
  new_messages = self._get_new_messages(contact, bot, messages)
107
106
  chat_history = [message.to_dic() for message in new_messages]
108
107
  self.chat_repo.upsert_messages(chat_history)
@@ -0,0 +1,90 @@
1
+ from abc import ABC
2
+ from typing import List
3
+ import logging as log
4
+ from lgt_jobs.basejobs import BaseBackgroundJob, BaseBackgroundJobData
5
+ from lgt_jobs.jobs.bot_stats_update import BotStatsUpdateJob, BotStatsUpdateJobData
6
+ from lgt_jobs.runner import BackgroundJobRunner
7
+ from lgt_jobs.lgt_data.engine import UserTrackAction
8
+ from lgt_jobs.lgt_data.enums import SourceType, UserAction, StatusConnection
9
+ from lgt_jobs.lgt_data.model import BaseModel, DedicatedBotModel, Server, Source, SlackUser
10
+ from lgt_jobs.lgt_data.mongo_repository import DedicatedBotRepository, UserMongoRepository
11
+
12
+
13
+ class ConnectSourceJobData(BaseBackgroundJobData, BaseModel):
14
+ slack_email: str
15
+ sources_ids: List[str]
16
+ action_user_email: str
17
+ user_email: str
18
+
19
+
20
+ class ConnectSourceJob(BaseBackgroundJob, ABC):
21
+ @property
22
+ def job_data_type(self) -> type:
23
+ return ConnectSourceJobData
24
+
25
+ def exec(self, data: ConnectSourceJobData):
26
+ users_repository = UserMongoRepository()
27
+ bots_repository = DedicatedBotRepository()
28
+ action_user = users_repository.get_by_email(data.action_user_email)
29
+ user = users_repository.get_by_email(data.user_email)
30
+ if not action_user:
31
+ log.info(f'[ConnectSourceJobData]: Unable to find user with email: {data.action_user_email}')
32
+ return
33
+
34
+ slack_user: SlackUser = action_user.get_slack_user(data.slack_email)
35
+ if not slack_user:
36
+ log.info(f"[ConnectSourceJobData]: You have no credentials for {data.slack_email}. Try to login to Slack.")
37
+ slack_user.status = StatusConnection.COMPLETE
38
+ users_repository.set(action_user.id, slack_users=[user.to_dic() for user in action_user.slack_users])
39
+ return
40
+ bots = []
41
+ for ws in slack_user.workspaces:
42
+ try:
43
+ if ws.id in data.sources_ids:
44
+ active_bot = bots_repository.get_one(user_id=action_user.id, active_server_id=ws.id)
45
+ bot = bots_repository.get_one(user_id=action_user.id, server_id=ws.id, include_deleted=True,
46
+ user_name=data.slack_email)
47
+ if active_bot:
48
+ continue
49
+ if not bot:
50
+ bot = DedicatedBotModel()
51
+ bot.servers = [Server()]
52
+
53
+ bot.user_name = slack_user.email
54
+ bot.slack_url = ws.url
55
+ bot.user_id = action_user.id
56
+ bot.created_by = user.email
57
+ bot.registration_link = ws.url
58
+ bot.token = ws.token
59
+ bot.cookies = slack_user.cookies
60
+ bot.deleted = False
61
+ bot.paused = False
62
+ bot.invalid_creds = False
63
+ bot.banned = False
64
+ bot.two_factor_required = ws.two_factor_required
65
+ source = Source()
66
+
67
+ for server in bot.servers:
68
+ server.id = ws.id
69
+ server.name = ws.domain
70
+ server.deleted = False
71
+ server.active = True
72
+ server.icon = ws.icon
73
+
74
+ source.source_id = ws.id
75
+ source.source_name = ws.domain
76
+ bot.type = source.source_type = SourceType.SLACK
77
+ bot.icon = ws.icon
78
+ bot.source = source
79
+ bots_repository.add_or_update(bot)
80
+ UserTrackAction.track(action_user.id, UserAction.START_SOURCE, ws.id)
81
+ bot = bots_repository.get_one(user_id=action_user.id, server_id=ws.id, user_name=slack_user.email)
82
+ bots.append(bot)
83
+ BackgroundJobRunner.submit(BotStatsUpdateJob, BotStatsUpdateJobData(bot_id=str(bot.id)))
84
+ except Exception:
85
+ import traceback
86
+ log.exception(f"[ConnectSourceJobData]: Error to connect {ws.id}")
87
+ traceback.print_exc()
88
+ slack_user.status = StatusConnection.COMPLETE
89
+ users_repository.set(action_user.id, slack_users=[user.to_dic() for user in action_user.slack_users])
90
+ return bots
@@ -54,9 +54,7 @@ class SendSlackMessageJob(BaseBackgroundJob, ABC):
54
54
  message = resp.get('message') if 'message' in resp \
55
55
  else slack_client.conversation_replies(channel_id, resp['file_msg_ts'])['messages'][0]
56
56
 
57
- message_model: ChatMessage = SlackMessageConvertService.from_slack_response(user.email, "slack_files",
58
- bot.token, message, data.sender_id,
59
- bot.id, user.id,
60
- slack_client.client.cookies)
57
+ message_model: ChatMessage = SlackMessageConvertService.from_slack_response(bot.user_name, message,
58
+ data.sender_id, bot.id, user.id)
61
59
  message_model.viewed = True
62
60
  ChatRepository().upsert_messages([message_model.to_dic()])
@@ -1,28 +1,13 @@
1
- import requests
2
1
  from google.cloud import storage
3
2
  from datetime import datetime, timedelta
4
- from lgt_jobs.lgt_data.model import ChatMessage
5
- from .slack_client import SlackClient
6
- from ...lgt_data.mongo_repository import to_object_id
7
-
8
-
9
- def get_file_url(blob_path):
10
- storage_client = storage.Client()
11
- bucket = storage_client.get_bucket(SlackFilesClient.bucket_name)
12
- blob = bucket.get_blob(blob_path)
13
- if not blob:
14
- return None
15
- # valid for 3 days
16
- return blob.generate_signed_url(timedelta(3))
3
+ from lgt_jobs.lgt_data.model import ChatMessage, LeadGuruFile
4
+ from lgt_jobs.lgt_common.slack_client.slack_client import SlackClient
5
+ from lgt_jobs.lgt_data.mongo_repository import to_object_id
17
6
 
18
7
 
19
8
  class SlackMessageConvertService:
20
9
  @staticmethod
21
- def from_slack_response(user_email, bot_name, bot_token, dic, sender_id, bot_id, user_id, cookies=None):
22
-
23
- """
24
- :rtype: SlackHistoryMessageModel
25
- """
10
+ def from_slack_response(bot_name, dic, sender_id, bot_id, user_id) -> ChatMessage:
26
11
  result = ChatMessage()
27
12
  result.sender_id = sender_id
28
13
  result.bot_id = bot_id
@@ -32,24 +17,15 @@ class SlackMessageConvertService:
32
17
  result.attachments = dic.get('attachments', [])
33
18
  result.files = []
34
19
  result.user_id = to_object_id(user_id)
35
-
36
20
  if 'files' in dic:
37
21
  for file in dic.get('files'):
38
- if file.get('mode', '') == "tombstone" or not file.get('url_private_download'):
39
- continue
40
- new_file = ChatMessage.SlackFileModel()
41
- new_file.id = file.get('id')
42
- new_file.name = file.get('name')
43
- new_file.title = file.get('title')
44
- new_file.filetype = file.get('filetype')
45
- new_file.size = file.get('size')
46
- new_file.mimetype = file.get('mimetype')
47
-
48
- url_private_download = file.get('url_private_download')
49
- new_file.download_url = SlackFilesClient.get_file_url(user_email, bot_name, bot_token,
50
- new_file.id, url_private_download,
51
- new_file.mimetype, cookies)
52
- result.files.append(new_file)
22
+ if file.get('mode') != "tombstone" and file.get('url_private_download'):
23
+ leadguru_file = LeadGuruFile()
24
+ leadguru_file.id = file['id']
25
+ leadguru_file.content_type = file['mimetype']
26
+ leadguru_file.file_name = file['name']
27
+ leadguru_file.blob_path = f'slack_files/{bot_name}/slack_files/{file["id"]}'
28
+ result.files.append(leadguru_file)
53
29
 
54
30
  js_ticks = int(result.id.split('.')[0] + result.id.split('.')[1][3:])
55
31
  result.created_at = datetime.fromtimestamp(js_ticks / 1000.0)
@@ -59,23 +35,12 @@ class SlackMessageConvertService:
59
35
  class SlackFilesClient:
60
36
  bucket_name = 'lgt_service_file'
61
37
 
62
- # Consider to cache these file somewhere in the super-fast cache solution
63
- @staticmethod
64
- def get_file_url(user_email, bot_name, bot_token, file_id, url_private_download, mimetype, cookies=None):
38
+ def get_file_url(self, blob_path):
65
39
  storage_client = storage.Client()
66
- bucket = storage_client.get_bucket(SlackFilesClient.bucket_name)
67
- blob_path = f'slack_files/{user_email}/{bot_name}/{file_id}'
40
+ bucket = storage_client.get_bucket(self.bucket_name)
68
41
  blob = bucket.get_blob(blob_path)
69
-
70
42
  if not blob:
71
- res = requests.get(url_private_download, headers={'Authorization': f'Bearer {bot_token}'}, cookies=cookies)
72
- if res.status_code != 200:
73
- raise Exception(
74
- f'Failed to download file: {url_private_download} from slack due to response: '
75
- f'Code: {res.status_code} Error: {res.content}')
76
- blob = bucket.blob(blob_path)
77
- blob.upload_from_string(res.content, content_type=mimetype)
78
-
43
+ return None
79
44
  # valid for 3 days
80
45
  return blob.generate_signed_url(timedelta(3))
81
46
 
@@ -138,10 +103,10 @@ class SlackWebClient:
138
103
  def confirm_email(self, email: str, user_agent: str, locale: str = 'en-US') -> bool:
139
104
  return self.client.confirm_email(email, user_agent, locale)
140
105
 
141
- def confirm_code(self, email: str, code: str, user_agent: str, ) -> requests.Response:
106
+ def confirm_code(self, email: str, code: str, user_agent: str):
142
107
  return self.client.confirm_code(email, code, user_agent)
143
108
 
144
- def find_workspaces(self, user_agent: str, ) -> requests.Response:
109
+ def find_workspaces(self, user_agent: str):
145
110
  return self.client.find_workspaces(user_agent)
146
111
 
147
112
  def conversation_replies(self, channel: str, id: str) -> dict:
@@ -43,6 +43,7 @@ class UserAction(str, Enum):
43
43
 
44
44
 
45
45
  class StatusConnection(str, Enum):
46
+ SOURCES_IN_PROGRESS = 'Sources in progress',
46
47
  IN_PROGRESS = 'In progress',
47
48
  COMPLETE = 'Complete',
48
49
  FAILED = 'Failed'
@@ -322,6 +322,10 @@ class UserModel(BaseModel):
322
322
  def is_admin(self):
323
323
  return UserRole.ADMIN in self.roles
324
324
 
325
+ @property
326
+ def subscription_expired(self):
327
+ return self.subscription_expired_at.replace(tzinfo=UTC) < datetime.now(UTC)
328
+
325
329
  def get_slack_user(self, slack_email: str):
326
330
  return next(filter(lambda x: slack_email == x.email, self.slack_users), None)
327
331
 
@@ -579,9 +583,7 @@ class ChatMessage:
579
583
 
580
584
  def to_dic(self):
581
585
  result = copy.deepcopy(self.__dict__)
582
- if self.files and 'files' in result:
583
- result['files'] = [x.to_dic() if isinstance(x, ChatMessage.SlackFileModel) else x for x in self.files]
584
-
586
+ result['files'] = [x.to_dic() for x in result.get('files', [])]
585
587
  return result
586
588
 
587
589
  @classmethod
@@ -869,7 +871,8 @@ class ExtendedSlackMemberInformation(SlackMemberInformation):
869
871
  if not hasattr(contact, "display_name"):
870
872
  contact.display_name = contact.name
871
873
 
872
- lead.linkedin_urls = [f"https://www.linkedin.com/search/results/all/?keywords={contact.real_name}&origin=GLOBAL_SEARCH_HEADER&sid=u%40F"]
874
+ lead.linkedin_urls = [
875
+ f"https://www.linkedin.com/search/results/all/?keywords={contact.real_name}&origin=GLOBAL_SEARCH_HEADER&sid=u%40F"]
873
876
  lead.message = MessageModel()
874
877
  lead.message.message = contact.real_name
875
878
  if contact.title:
@@ -971,3 +974,19 @@ class Subscription(BaseModel):
971
974
  model: Subscription | None = super().from_dic(dic)
972
975
  model.features = [Feature.from_dic(feature) for feature in dic.get('features', [])]
973
976
  return model
977
+
978
+
979
+ class LeadGuruFile(DictionaryModel):
980
+ id: str = None
981
+ blob_path: str
982
+ content_type: str
983
+ file_name: str = None
984
+
985
+
986
+ class ScheduledMessage(BaseModel):
987
+ post_at: datetime
988
+ bot_id: ObjectId
989
+ user_id: ObjectId
990
+ sender_id: str
991
+ text: str | None
992
+ files: list
@@ -9,7 +9,8 @@ from lgt_jobs.lgt_data.enums import SourceType
9
9
  from lgt_jobs.lgt_data.model import (LeadModel, BaseModel, UserModel, UserResetPasswordModel, BoardModel, BoardedStatus,
10
10
  DedicatedBotModel, SlackMemberInformation, UserTemplateModel,
11
11
  ExtendedUserLeadModel, UserLeadModel, ExtendedLeadModel, UserContact, ChatMessage,
12
- GroupedMessagesModel, UserVerificationModel, UsersPage, Subscription)
12
+ GroupedMessagesModel, UserVerificationModel, UsersPage, Subscription,
13
+ ScheduledMessage)
13
14
  from datetime import datetime, UTC, timedelta
14
15
  from collections import OrderedDict
15
16
 
@@ -1207,3 +1208,15 @@ class SubscriptionsRepository(BaseMongoRepository):
1207
1208
  pipeline['expired'] = expired
1208
1209
 
1209
1210
  return Subscription.from_dic(self.collection().find_one(pipeline))
1211
+
1212
+
1213
+ class ScheduledMessagesRepository(BaseMongoRepository):
1214
+ pass
1215
+
1216
+ collection_name = 'scheduled_messages'
1217
+
1218
+ def find_all(self, only_actual: bool = True):
1219
+ pipeline = {}
1220
+ if only_actual:
1221
+ pipeline['post_at'] = {'$lte': datetime.now(UTC)}
1222
+ return [ScheduledMessage.from_dic(doc) for doc in self.collection().find(pipeline)]