smart-bot-factory 0.3.10__py3-none-any.whl → 1.0.1__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.
Potentially problematic release.
This version of smart-bot-factory might be problematic. Click here for more details.
- smart_bot_factory/admin/admin_events.py +308 -117
- smart_bot_factory/core/decorators.py +17 -13
- smart_bot_factory/core/message_sender.py +57 -51
- smart_bot_factory/event/__init__.py +1 -1
- smart_bot_factory/handlers/handlers.py +21 -16
- {smart_bot_factory-0.3.10.dist-info → smart_bot_factory-1.0.1.dist-info}/METADATA +1 -1
- {smart_bot_factory-0.3.10.dist-info → smart_bot_factory-1.0.1.dist-info}/RECORD +10 -10
- {smart_bot_factory-0.3.10.dist-info → smart_bot_factory-1.0.1.dist-info}/WHEEL +0 -0
- {smart_bot_factory-0.3.10.dist-info → smart_bot_factory-1.0.1.dist-info}/entry_points.txt +0 -0
- {smart_bot_factory-0.3.10.dist-info → smart_bot_factory-1.0.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -116,24 +116,83 @@ async def process_event_name(message: Message, state: FSMContext):
|
|
|
116
116
|
|
|
117
117
|
# Сохраняем название
|
|
118
118
|
await state.update_data(event_name=event_name)
|
|
119
|
-
await state.set_state(AdminStates.create_event_date)
|
|
120
119
|
|
|
121
|
-
#
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
120
|
+
# Создаем клавиатуру с выбором времени
|
|
121
|
+
keyboard = InlineKeyboardMarkup(
|
|
122
|
+
inline_keyboard=[
|
|
123
|
+
[
|
|
124
|
+
InlineKeyboardButton(text="🚀 Запустить сразу", callback_data="timing:immediate"),
|
|
125
|
+
InlineKeyboardButton(text="📅 Выбрать время", callback_data="timing:scheduled")
|
|
126
|
+
]
|
|
127
|
+
]
|
|
127
128
|
)
|
|
128
|
-
calendar_markup = await calendar.start_calendar()
|
|
129
129
|
|
|
130
130
|
await message.answer(
|
|
131
|
-
f"✅ Название события: **{event_name}**\n\n"
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
f"✅ Название события: **{event_name}**\n\n"
|
|
132
|
+
"🕒 Когда запустить событие?",
|
|
133
|
+
reply_markup=keyboard,
|
|
134
|
+
parse_mode="Markdown"
|
|
134
135
|
)
|
|
135
136
|
|
|
136
137
|
|
|
138
|
+
@admin_events_router.callback_query(F.data.startswith("timing:"))
|
|
139
|
+
async def process_event_timing(callback_query: CallbackQuery, state: FSMContext):
|
|
140
|
+
"""Обработка выбора времени запуска события"""
|
|
141
|
+
action = callback_query.data.split(":", 1)[1]
|
|
142
|
+
|
|
143
|
+
if action == "immediate":
|
|
144
|
+
# Устанавливаем текущее время
|
|
145
|
+
now = datetime.now(MOSCOW_TZ)
|
|
146
|
+
await state.update_data(
|
|
147
|
+
event_date=now.strftime("%Y-%m-%d"),
|
|
148
|
+
event_time=now.strftime("%H:%M"),
|
|
149
|
+
is_immediate=True
|
|
150
|
+
)
|
|
151
|
+
# Переходим к выбору сегмента
|
|
152
|
+
await state.set_state(AdminStates.create_event_segment)
|
|
153
|
+
|
|
154
|
+
# Получаем все доступные сегменты
|
|
155
|
+
from ..handlers.handlers import get_global_var
|
|
156
|
+
supabase_client = get_global_var("supabase_client")
|
|
157
|
+
segments = await supabase_client.get_all_segments()
|
|
158
|
+
|
|
159
|
+
# Создаем клавиатуру с сегментами
|
|
160
|
+
keyboard = []
|
|
161
|
+
keyboard.append([InlineKeyboardButton(text="📢 Отправить всем", callback_data="segment:all")])
|
|
162
|
+
if segments:
|
|
163
|
+
for i in range(0, len(segments), 2):
|
|
164
|
+
row = [InlineKeyboardButton(text=f"👥 {segments[i]}", callback_data=f"segment:{segments[i]}")]
|
|
165
|
+
if i + 1 < len(segments):
|
|
166
|
+
row.append(InlineKeyboardButton(text=f"👥 {segments[i+1]}", callback_data=f"segment:{segments[i+1]}"))
|
|
167
|
+
keyboard.append(row)
|
|
168
|
+
|
|
169
|
+
markup = InlineKeyboardMarkup(inline_keyboard=keyboard)
|
|
170
|
+
await callback_query.message.edit_text(
|
|
171
|
+
f"✅ Время: **Сейчас**\n\n"
|
|
172
|
+
f"👥 Выберите сегмент пользователей для отправки:\n"
|
|
173
|
+
f"_(Найдено сегментов: {len(segments)})_",
|
|
174
|
+
reply_markup=markup,
|
|
175
|
+
parse_mode="Markdown"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
else: # scheduled
|
|
179
|
+
await state.set_state(AdminStates.create_event_date)
|
|
180
|
+
# Показываем календарь для выбора даты
|
|
181
|
+
calendar = SimpleCalendar(locale="ru", today_btn="Сегодня", cancel_btn="Отмена")
|
|
182
|
+
# Ограничиваем выбор датами от вчера до +12 месяцев (чтобы сегодня был доступен)
|
|
183
|
+
calendar.set_dates_range(
|
|
184
|
+
datetime.now() + relativedelta(days=-1),
|
|
185
|
+
datetime.now() + relativedelta(months=+12),
|
|
186
|
+
)
|
|
187
|
+
calendar_markup = await calendar.start_calendar()
|
|
188
|
+
|
|
189
|
+
await callback_query.message.edit_text(
|
|
190
|
+
"📅 Выберите дату отправки:",
|
|
191
|
+
reply_markup=calendar_markup,
|
|
192
|
+
parse_mode="Markdown"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
137
196
|
@admin_events_router.callback_query(
|
|
138
197
|
SimpleCalendarCallback.filter(), AdminStates.create_event_date
|
|
139
198
|
)
|
|
@@ -584,17 +643,23 @@ async def process_files_action(callback_query: CallbackQuery, state: FSMContext)
|
|
|
584
643
|
# Переход к подтверждению
|
|
585
644
|
await state.set_state(AdminStates.create_event_confirm)
|
|
586
645
|
|
|
587
|
-
# Формируем дату и время для отображения
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
646
|
+
# Формируем дату и время для отображения
|
|
647
|
+
is_immediate = data.get("is_immediate", False)
|
|
648
|
+
|
|
649
|
+
if is_immediate:
|
|
650
|
+
time_display = "Прямо сейчас 🔥"
|
|
651
|
+
else:
|
|
652
|
+
event_date = data.get("event_date")
|
|
653
|
+
event_time = data.get("event_time")
|
|
654
|
+
naive_datetime = datetime.strptime(f"{event_date} {event_time}", "%Y-%m-%d %H:%M")
|
|
655
|
+
moscow_datetime = MOSCOW_TZ.localize(naive_datetime)
|
|
656
|
+
time_display = f"{moscow_datetime.strftime('%d.%m.%Y %H:%M')} (МСК)"
|
|
592
657
|
|
|
593
658
|
# Отправляем сообщение с подтверждением
|
|
594
659
|
summary = (
|
|
595
660
|
f"📋 **Подтверждение создания события**\n\n"
|
|
596
661
|
f"📝 Название: **{data.get('event_name')}**\n"
|
|
597
|
-
f"📅
|
|
662
|
+
f"📅 Время запуска: **{time_display}**\n"
|
|
598
663
|
f"👥 Сегмент: **{data.get('segment_display')}**\n"
|
|
599
664
|
f"📎 Файлов: **{len(files)}**\n\n"
|
|
600
665
|
"Подтвердите создание события:"
|
|
@@ -727,15 +792,21 @@ async def show_event_preview(callback_query: CallbackQuery, state: FSMContext):
|
|
|
727
792
|
)
|
|
728
793
|
|
|
729
794
|
# 3. Отправляем сообщение с подтверждением (такое же как было)
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
795
|
+
is_immediate = data.get("is_immediate", False)
|
|
796
|
+
|
|
797
|
+
if is_immediate:
|
|
798
|
+
time_display = "Прямо сейчас 🔥"
|
|
799
|
+
else:
|
|
800
|
+
event_date = data.get("event_date")
|
|
801
|
+
event_time = data.get("event_time")
|
|
802
|
+
naive_datetime = datetime.strptime(f"{event_date} {event_time}", "%Y-%m-%d %H:%M")
|
|
803
|
+
moscow_datetime = MOSCOW_TZ.localize(naive_datetime)
|
|
804
|
+
time_display = f"{moscow_datetime.strftime('%d.%m.%Y %H:%M')} (МСК)"
|
|
734
805
|
|
|
735
806
|
summary = (
|
|
736
807
|
f"📋 **Подтверждение создания события**\n\n"
|
|
737
808
|
f"📝 Название: **{data.get('event_name')}**\n"
|
|
738
|
-
f"📅
|
|
809
|
+
f"📅 Время запуска: **{time_display}**\n"
|
|
739
810
|
f"👥 Сегмент: **{data.get('segment_display')}**\n"
|
|
740
811
|
f"📎 Файлов: **{len(files)}**\n\n"
|
|
741
812
|
"Подтвердите создание события:"
|
|
@@ -773,127 +844,247 @@ async def process_event_confirmation(callback_query: CallbackQuery, state: FSMCo
|
|
|
773
844
|
)
|
|
774
845
|
return
|
|
775
846
|
|
|
776
|
-
#
|
|
847
|
+
# Получаем данные события
|
|
777
848
|
data = await state.get_data()
|
|
849
|
+
is_immediate = data.get("is_immediate", False)
|
|
850
|
+
files = data.get("files", [])
|
|
778
851
|
|
|
779
852
|
from ..handlers.handlers import get_global_var
|
|
780
|
-
|
|
853
|
+
from aiogram.types import FSInputFile, InputMediaPhoto, InputMediaVideo
|
|
854
|
+
bot = get_global_var("bot")
|
|
781
855
|
supabase_client = get_global_var("supabase_client")
|
|
782
856
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
857
|
+
if is_immediate:
|
|
858
|
+
# Для немедленной отправки - сразу рассылаем сообщения
|
|
859
|
+
try:
|
|
860
|
+
# Показываем сообщение о начале рассылки
|
|
861
|
+
await callback_query.message.edit_text(
|
|
862
|
+
"📤 **Выполняется рассылка...**",
|
|
863
|
+
parse_mode="Markdown"
|
|
864
|
+
)
|
|
789
865
|
|
|
790
|
-
|
|
791
|
-
|
|
866
|
+
# Получаем список пользователей для рассылки
|
|
867
|
+
segment = data.get("segment")
|
|
868
|
+
users = await supabase_client.get_users_by_segment(segment)
|
|
869
|
+
total_users = len(users)
|
|
870
|
+
sent_count = 0
|
|
871
|
+
failed_count = 0
|
|
872
|
+
|
|
873
|
+
# Преобразуем результат в нужный формат
|
|
874
|
+
user_ids = [user["telegram_id"] for user in users] # используем telegram_id вместо user_id
|
|
875
|
+
|
|
876
|
+
# Группируем файлы по стадиям
|
|
877
|
+
files_with_msg = [f for f in files if f.get("stage") == "with_message"]
|
|
878
|
+
files_after = [f for f in files if f.get("stage") == "after_message"]
|
|
879
|
+
|
|
880
|
+
# Отправляем сообщения каждому пользователю
|
|
881
|
+
for user in users:
|
|
882
|
+
user_id = user["telegram_id"]
|
|
883
|
+
try:
|
|
884
|
+
# 1. Отправляем основное сообщение с медиа (если есть)
|
|
885
|
+
if files_with_msg:
|
|
886
|
+
sorted_files = sorted(files_with_msg, key=lambda x: x.get("order", 0))
|
|
887
|
+
|
|
888
|
+
# Если только один файл - отправляем как обычный файл с caption
|
|
889
|
+
if len(sorted_files) == 1:
|
|
890
|
+
file_info = sorted_files[0]
|
|
891
|
+
if file_info["type"] == "photo":
|
|
892
|
+
await bot.send_photo(
|
|
893
|
+
chat_id=user_id,
|
|
894
|
+
photo=FSInputFile(file_info["file_path"]),
|
|
895
|
+
caption=data.get("event_message"),
|
|
896
|
+
parse_mode="Markdown"
|
|
897
|
+
)
|
|
898
|
+
elif file_info["type"] == "video":
|
|
899
|
+
await bot.send_video(
|
|
900
|
+
chat_id=user_id,
|
|
901
|
+
video=FSInputFile(file_info["file_path"]),
|
|
902
|
+
caption=data.get("event_message"),
|
|
903
|
+
parse_mode="Markdown"
|
|
904
|
+
)
|
|
905
|
+
else:
|
|
906
|
+
# Если несколько файлов - используем media_group
|
|
907
|
+
media_group = []
|
|
908
|
+
first_file = True
|
|
909
|
+
|
|
910
|
+
for file_info in sorted_files:
|
|
911
|
+
if file_info["type"] == "photo":
|
|
912
|
+
media = InputMediaPhoto(
|
|
913
|
+
media=FSInputFile(file_info["file_path"]),
|
|
914
|
+
caption=data.get("event_message") if first_file else None,
|
|
915
|
+
parse_mode="Markdown" if first_file else None,
|
|
916
|
+
)
|
|
917
|
+
media_group.append(media)
|
|
918
|
+
elif file_info["type"] == "video":
|
|
919
|
+
media = InputMediaVideo(
|
|
920
|
+
media=FSInputFile(file_info["file_path"]),
|
|
921
|
+
caption=data.get("event_message") if first_file else None,
|
|
922
|
+
parse_mode="Markdown" if first_file else None,
|
|
923
|
+
)
|
|
924
|
+
media_group.append(media)
|
|
925
|
+
first_file = False
|
|
926
|
+
|
|
927
|
+
if media_group:
|
|
928
|
+
await bot.send_media_group(chat_id=user_id, media=media_group)
|
|
929
|
+
else:
|
|
930
|
+
# Только текст
|
|
931
|
+
await bot.send_message(
|
|
932
|
+
chat_id=user_id,
|
|
933
|
+
text=data.get("event_message"),
|
|
934
|
+
parse_mode="Markdown"
|
|
935
|
+
)
|
|
936
|
+
|
|
937
|
+
# 2. Отправляем дополнительные файлы
|
|
938
|
+
for file_info in files_after:
|
|
939
|
+
if file_info["type"] == "document":
|
|
940
|
+
await bot.send_document(
|
|
941
|
+
chat_id=user_id,
|
|
942
|
+
document=FSInputFile(file_info["file_path"])
|
|
943
|
+
)
|
|
944
|
+
elif file_info["type"] == "photo":
|
|
945
|
+
await bot.send_photo(
|
|
946
|
+
chat_id=user_id,
|
|
947
|
+
photo=FSInputFile(file_info["file_path"])
|
|
948
|
+
)
|
|
949
|
+
elif file_info["type"] == "video":
|
|
950
|
+
await bot.send_video(
|
|
951
|
+
chat_id=user_id,
|
|
952
|
+
video=FSInputFile(file_info["file_path"])
|
|
953
|
+
)
|
|
792
954
|
|
|
793
|
-
|
|
794
|
-
utc_datetime = moscow_datetime.astimezone(pytz.UTC)
|
|
955
|
+
sent_count += 1
|
|
795
956
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
957
|
+
except Exception as e:
|
|
958
|
+
logger.error(f"❌ Ошибка отправки пользователю {user_id}: {e}")
|
|
959
|
+
failed_count += 1
|
|
960
|
+
|
|
961
|
+
# Сохраняем событие в БД
|
|
962
|
+
event_status = "success" if failed_count == 0 else "partial_success"
|
|
963
|
+
await supabase_client.save_admin_event(
|
|
964
|
+
event_name=data.get("event_name"),
|
|
965
|
+
event_data={
|
|
966
|
+
"segment": segment,
|
|
967
|
+
"total_users": total_users,
|
|
968
|
+
"sent_success": sent_count,
|
|
969
|
+
"failed_count": failed_count,
|
|
970
|
+
"type": "immediate_event",
|
|
971
|
+
"admin_id": callback_query.from_user.id,
|
|
972
|
+
"execution_status": event_status,
|
|
973
|
+
"completed_at": datetime.now(pytz.UTC).isoformat()
|
|
974
|
+
},
|
|
975
|
+
scheduled_datetime=datetime.now(pytz.UTC)
|
|
976
|
+
)
|
|
799
977
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
"
|
|
807
|
-
"
|
|
808
|
-
"
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
event_id = event["id"]
|
|
813
|
-
logger.info(f"✅ Создано событие с ID {event_id}")
|
|
978
|
+
# Показываем итоговое сообщение
|
|
979
|
+
status = "✅" if failed_count == 0 else "⚠️"
|
|
980
|
+
|
|
981
|
+
await callback_query.message.edit_text(
|
|
982
|
+
f"{status} **Админское событие выполнено**\n\n"
|
|
983
|
+
f"📝 Название: **{data.get('event_name')}**\n"
|
|
984
|
+
f"👥 Сегмент: **{data.get('segment_display')}**\n\n"
|
|
985
|
+
f"📊 Результат:\n"
|
|
986
|
+
f"• Доставлено: **{sent_count}**\n"
|
|
987
|
+
f"• Не доставлено: **{failed_count}**",
|
|
988
|
+
parse_mode="Markdown"
|
|
989
|
+
)
|
|
814
990
|
|
|
815
|
-
|
|
816
|
-
|
|
991
|
+
except Exception as e:
|
|
992
|
+
logger.error(f"❌ Ошибка массовой рассылки: {e}")
|
|
993
|
+
|
|
994
|
+
# Сохраняем ошибку события в БД
|
|
995
|
+
await supabase_client.save_admin_event(
|
|
996
|
+
event_name=data.get("event_name"),
|
|
997
|
+
event_data={
|
|
998
|
+
"segment": segment,
|
|
999
|
+
"error": str(e),
|
|
1000
|
+
"type": "immediate_event",
|
|
1001
|
+
"admin_id": callback_query.from_user.id,
|
|
1002
|
+
"execution_status": "error",
|
|
1003
|
+
"completed_at": datetime.now(pytz.UTC).isoformat()
|
|
1004
|
+
},
|
|
1005
|
+
scheduled_datetime=datetime.now(pytz.UTC)
|
|
1006
|
+
)
|
|
817
1007
|
|
|
818
|
-
|
|
819
|
-
|
|
1008
|
+
await callback_query.message.edit_text(
|
|
1009
|
+
f"❌ **Ошибка выполнения админского события**\n\n"
|
|
1010
|
+
f"📝 Название: **{data.get('event_name')}**\n"
|
|
1011
|
+
f"👥 Сегмент: **{data.get('segment_display')}**\n\n"
|
|
1012
|
+
f"Произошла техническая ошибка. Попробуйте позже.",
|
|
1013
|
+
parse_mode="Markdown"
|
|
1014
|
+
)
|
|
820
1015
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
1016
|
+
else:
|
|
1017
|
+
# Для отложенного события - стандартная логика
|
|
1018
|
+
try:
|
|
1019
|
+
# Формируем datetime для планирования
|
|
1020
|
+
event_date = data.get("event_date")
|
|
1021
|
+
event_time = data.get("event_time")
|
|
1022
|
+
naive_datetime = datetime.strptime(f"{event_date} {event_time}", "%Y-%m-%d %H:%M")
|
|
1023
|
+
moscow_datetime = MOSCOW_TZ.localize(naive_datetime)
|
|
1024
|
+
utc_datetime = moscow_datetime.astimezone(pytz.UTC)
|
|
1025
|
+
|
|
1026
|
+
# Создаем событие
|
|
1027
|
+
event = await supabase_client.save_admin_event(
|
|
1028
|
+
event_name=data.get("event_name"),
|
|
1029
|
+
event_data={
|
|
1030
|
+
"segment": data.get("segment"),
|
|
1031
|
+
"message": data.get("event_message"),
|
|
1032
|
+
"files": [],
|
|
1033
|
+
},
|
|
1034
|
+
scheduled_datetime=utc_datetime,
|
|
1035
|
+
)
|
|
1036
|
+
event_id = event["id"]
|
|
837
1037
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1038
|
+
# Загружаем файлы в Storage
|
|
1039
|
+
uploaded_files = []
|
|
1040
|
+
for file_info in files:
|
|
1041
|
+
try:
|
|
1042
|
+
with open(file_info["file_path"], "rb") as f:
|
|
1043
|
+
file_bytes = f.read()
|
|
1044
|
+
file_id = generate_file_id()
|
|
1045
|
+
storage_info = await supabase_client.upload_event_file(
|
|
1046
|
+
event_id=event_id,
|
|
1047
|
+
file_data=file_bytes,
|
|
1048
|
+
original_name=file_info["name"],
|
|
1049
|
+
file_id=file_id,
|
|
1050
|
+
)
|
|
1051
|
+
uploaded_files.append({
|
|
841
1052
|
"type": file_info["type"],
|
|
842
1053
|
"storage_path": storage_info["storage_path"],
|
|
843
|
-
"original_name": file_info[
|
|
844
|
-
"name"
|
|
845
|
-
], # Используем оригинальное имя из file_info
|
|
1054
|
+
"original_name": file_info["name"],
|
|
846
1055
|
"stage": file_info["stage"],
|
|
847
1056
|
"has_caption": file_info.get("has_caption", False),
|
|
848
1057
|
"order": file_info.get("order", 0),
|
|
849
|
-
}
|
|
850
|
-
)
|
|
851
|
-
|
|
852
|
-
# Удаляем временный локальный файл
|
|
853
|
-
try:
|
|
854
|
-
os.remove(file_info["file_path"])
|
|
855
|
-
logger.info(f"🗑️ Удален временный файл: {file_info['file_path']}")
|
|
1058
|
+
})
|
|
856
1059
|
except Exception as e:
|
|
857
|
-
logger.
|
|
858
|
-
|
|
859
|
-
except Exception as e:
|
|
860
|
-
logger.error(f"❌ Ошибка загрузки файла {file_info['name']}: {e}")
|
|
861
|
-
# Если ошибка - удаляем все файлы события и само событие
|
|
862
|
-
try:
|
|
1060
|
+
logger.error(f"❌ Ошибка загрузки файла {file_info['name']}: {e}")
|
|
863
1061
|
await supabase_client.delete_event_files(event_id)
|
|
864
|
-
|
|
865
|
-
except Exception:
|
|
866
|
-
logger.error("Ошибка при удалении файлов события")
|
|
867
|
-
raise
|
|
868
|
-
|
|
869
|
-
logger.info(
|
|
870
|
-
f"✅ Загружено {len(uploaded_files)} файлов в Storage для события {event_id}"
|
|
871
|
-
)
|
|
1062
|
+
raise
|
|
872
1063
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
supabase_client.client.table("scheduled_events")
|
|
883
|
-
.update({"event_data": json.dumps(event_data, ensure_ascii=False)})
|
|
884
|
-
.eq("id", event_id)
|
|
885
|
-
.execute()
|
|
886
|
-
)
|
|
1064
|
+
# Обновляем событие с информацией о файлах
|
|
1065
|
+
event_data = {
|
|
1066
|
+
"segment": data.get("segment"),
|
|
1067
|
+
"message": data.get("event_message"),
|
|
1068
|
+
"files": uploaded_files,
|
|
1069
|
+
}
|
|
1070
|
+
supabase_client.client.table("scheduled_events").update(
|
|
1071
|
+
{"event_data": json.dumps(event_data, ensure_ascii=False)}
|
|
1072
|
+
).eq("id", event_id).execute()
|
|
887
1073
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
1074
|
+
except Exception as e:
|
|
1075
|
+
logger.error(f"❌ Ошибка создания отложенного события: {e}")
|
|
1076
|
+
raise
|
|
1077
|
+
is_immediate = data.get("is_immediate", False)
|
|
1078
|
+
|
|
1079
|
+
if is_immediate:
|
|
1080
|
+
time_display = "🔥 Прямо сейчас"
|
|
1081
|
+
else:
|
|
1082
|
+
time_display = f"{moscow_datetime.strftime('%d.%m.%Y %H:%M')} (МСК)"
|
|
891
1083
|
|
|
892
|
-
# Показываем сообщение об успехе
|
|
893
1084
|
await callback_query.message.edit_text(
|
|
894
1085
|
f"✅ **Событие успешно создано!**\n\n"
|
|
895
1086
|
f"📝 Название: `{data.get('event_name')}`\n"
|
|
896
|
-
f"📅
|
|
1087
|
+
f"📅 Время запуска: **{time_display}**\n"
|
|
897
1088
|
f"👥 Сегмент: **{data.get('segment_display')}**\n\n"
|
|
898
1089
|
f"💡 _Нажмите на название для копирования_",
|
|
899
1090
|
parse_mode="Markdown",
|
|
@@ -7,7 +7,7 @@ import logging
|
|
|
7
7
|
import re
|
|
8
8
|
from datetime import datetime, timedelta, timezone
|
|
9
9
|
from functools import wraps
|
|
10
|
-
from typing import Any, Callable, Dict, Union
|
|
10
|
+
from typing import Any, Callable, Dict, Optional, Union
|
|
11
11
|
|
|
12
12
|
logger = logging.getLogger(__name__)
|
|
13
13
|
|
|
@@ -2176,12 +2176,13 @@ async def check_event_already_processed(
|
|
|
2176
2176
|
return False
|
|
2177
2177
|
|
|
2178
2178
|
|
|
2179
|
-
async def process_admin_event(event: Dict):
|
|
2179
|
+
async def process_admin_event(event: Dict, single_user_id: Optional[int] = None):
|
|
2180
2180
|
"""
|
|
2181
2181
|
Обрабатывает одно админское событие - скачивает файлы из Storage и отправляет пользователям
|
|
2182
2182
|
|
|
2183
2183
|
Args:
|
|
2184
2184
|
event: Событие из БД с данными для отправки
|
|
2185
|
+
single_user_id: ID пользователя для тестовой отправки. Если указан, сообщение будет отправлено только ему
|
|
2185
2186
|
"""
|
|
2186
2187
|
import json
|
|
2187
2188
|
import shutil
|
|
@@ -2265,17 +2266,20 @@ async def process_admin_event(event: Dict):
|
|
|
2265
2266
|
raise
|
|
2266
2267
|
|
|
2267
2268
|
# 2. Получаем пользователей
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
"
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2269
|
+
if single_user_id:
|
|
2270
|
+
users = [{"telegram_id": single_user_id}]
|
|
2271
|
+
logger.info(f"🔍 Тестовая отправка для пользователя {single_user_id}")
|
|
2272
|
+
else:
|
|
2273
|
+
users = await supabase_client.get_users_by_segment(segment)
|
|
2274
|
+
if not users:
|
|
2275
|
+
logger.warning(f"⚠️ Нет пользователей для сегмента '{segment}'")
|
|
2276
|
+
return {
|
|
2277
|
+
"success_count": 0,
|
|
2278
|
+
"failed_count": 0,
|
|
2279
|
+
"total_users": 0,
|
|
2280
|
+
"segment": segment or "Все",
|
|
2281
|
+
"warning": "Нет пользователей",
|
|
2282
|
+
}
|
|
2279
2283
|
|
|
2280
2284
|
success_count = 0
|
|
2281
2285
|
failed_count = 0
|
|
@@ -5,11 +5,15 @@
|
|
|
5
5
|
import logging
|
|
6
6
|
import time
|
|
7
7
|
from datetime import datetime
|
|
8
|
-
from typing import Any, Dict, Optional
|
|
8
|
+
from typing import Any, Dict, Optional, Union
|
|
9
9
|
|
|
10
|
-
from aiogram.types import InlineKeyboardMarkup
|
|
10
|
+
from aiogram.types import InlineKeyboardMarkup, FSInputFile
|
|
11
11
|
import pytz
|
|
12
12
|
|
|
13
|
+
from project_root_finder import root
|
|
14
|
+
|
|
15
|
+
PROJECT_ROOT = root
|
|
16
|
+
|
|
13
17
|
logger = logging.getLogger(__name__)
|
|
14
18
|
|
|
15
19
|
|
|
@@ -194,16 +198,18 @@ async def send_message_by_ai(
|
|
|
194
198
|
|
|
195
199
|
|
|
196
200
|
async def send_message_by_human(
|
|
197
|
-
user_id: int, message_text: str, session_id: Optional[str] = None, parse_mode: str = "Markdown", reply_markup: Optional[InlineKeyboardMarkup] = None
|
|
201
|
+
user_id: int, message_text: str, session_id: Optional[str] = None, parse_mode: str = "Markdown", reply_markup: Optional[InlineKeyboardMarkup] = None, photo: Optional[str] = None
|
|
198
202
|
) -> Dict[str, Any]:
|
|
199
203
|
"""
|
|
200
|
-
Отправляет сообщение пользователю от имени человека (готовый текст)
|
|
204
|
+
Отправляет сообщение пользователю от имени человека (готовый текст или фото с подписью).
|
|
201
205
|
|
|
202
206
|
Args:
|
|
203
207
|
user_id: ID пользователя в Telegram
|
|
204
|
-
message_text: Готовый текст сообщения
|
|
208
|
+
message_text: Готовый текст сообщения или подпись к фото
|
|
205
209
|
session_id: ID сессии (опционально, для сохранения в БД)
|
|
206
|
-
|
|
210
|
+
parse_mode: Тип форматирования текста
|
|
211
|
+
reply_markup: Клавиатура/markup (опционально)
|
|
212
|
+
photo: (str) путь к локальному файлу относительно корня проекта
|
|
207
213
|
Returns:
|
|
208
214
|
Результат отправки
|
|
209
215
|
"""
|
|
@@ -214,8 +220,29 @@ async def send_message_by_human(
|
|
|
214
220
|
bot = get_global_var("bot")
|
|
215
221
|
supabase_client = get_global_var("supabase_client")
|
|
216
222
|
|
|
217
|
-
|
|
218
|
-
message =
|
|
223
|
+
msg_type = "text"
|
|
224
|
+
message = None
|
|
225
|
+
|
|
226
|
+
if photo:
|
|
227
|
+
from pathlib import Path
|
|
228
|
+
photo_path = PROJECT_ROOT / photo
|
|
229
|
+
if not photo_path.exists():
|
|
230
|
+
raise FileNotFoundError(f"Файл с фото не найден: {photo}")
|
|
231
|
+
message = await bot.send_photo(
|
|
232
|
+
chat_id=user_id,
|
|
233
|
+
photo=FSInputFile(str(photo_path)),
|
|
234
|
+
caption=message_text,
|
|
235
|
+
parse_mode=parse_mode,
|
|
236
|
+
reply_markup=reply_markup
|
|
237
|
+
)
|
|
238
|
+
msg_type = "photo"
|
|
239
|
+
else:
|
|
240
|
+
message = await bot.send_message(
|
|
241
|
+
chat_id=user_id,
|
|
242
|
+
text=message_text,
|
|
243
|
+
parse_mode=parse_mode,
|
|
244
|
+
reply_markup=reply_markup,
|
|
245
|
+
)
|
|
219
246
|
|
|
220
247
|
# Если указана сессия, сохраняем сообщение в БД
|
|
221
248
|
if session_id:
|
|
@@ -223,10 +250,10 @@ async def send_message_by_human(
|
|
|
223
250
|
session_id=session_id,
|
|
224
251
|
role="assistant",
|
|
225
252
|
content=message_text,
|
|
226
|
-
message_type=
|
|
227
|
-
metadata={"sent_by_human": True},
|
|
253
|
+
message_type=msg_type,
|
|
254
|
+
metadata={"sent_by_human": True, "has_photo": bool(photo)},
|
|
228
255
|
)
|
|
229
|
-
logger.info("💾 Сообщение от человека сохранено в БД")
|
|
256
|
+
logger.info(f"💾 Сообщение от человека сохранено в БД (photo={bool(photo)})")
|
|
230
257
|
|
|
231
258
|
return {
|
|
232
259
|
"status": "success",
|
|
@@ -234,6 +261,7 @@ async def send_message_by_human(
|
|
|
234
261
|
"message_id": message.message_id,
|
|
235
262
|
"message_text": message_text,
|
|
236
263
|
"saved_to_db": bool(session_id),
|
|
264
|
+
"has_photo": bool(photo)
|
|
237
265
|
}
|
|
238
266
|
|
|
239
267
|
except Exception as e:
|
|
@@ -242,54 +270,43 @@ async def send_message_by_human(
|
|
|
242
270
|
|
|
243
271
|
|
|
244
272
|
async def send_message_to_users_by_stage(
|
|
245
|
-
stage: str, message_text: str, bot_id: str
|
|
273
|
+
stage: str, message_text: str, bot_id: str, photo: Optional[str] = None
|
|
246
274
|
) -> Dict[str, Any]:
|
|
247
275
|
"""
|
|
248
|
-
Отправляет сообщение всем пользователям, находящимся на определенной стадии
|
|
276
|
+
Отправляет сообщение (или фото с подписью) всем пользователям, находящимся на определенной стадии
|
|
249
277
|
|
|
250
278
|
Args:
|
|
251
279
|
stage: Стадия диалога (например, 'introduction', 'qualification', 'closing')
|
|
252
|
-
message_text: Текст сообщения для отправки
|
|
280
|
+
message_text: Текст сообщения для отправки / подпись к фото
|
|
253
281
|
bot_id: ID бота (если не указан, используется текущий бот)
|
|
254
|
-
|
|
282
|
+
photo: путь к файлу с фото (относительно корня проекта, опционально)
|
|
255
283
|
Returns:
|
|
256
284
|
Результат отправки с количеством отправленных сообщений
|
|
257
285
|
"""
|
|
258
286
|
try:
|
|
259
|
-
# Импортируем необходимые компоненты
|
|
260
287
|
from ..handlers.handlers import get_global_var
|
|
288
|
+
from pathlib import Path
|
|
261
289
|
|
|
262
290
|
bot = get_global_var("bot")
|
|
263
291
|
supabase_client = get_global_var("supabase_client")
|
|
264
292
|
current_bot_id = (
|
|
265
293
|
get_global_var("config").BOT_ID if get_global_var("config") else bot_id
|
|
266
294
|
)
|
|
267
|
-
|
|
268
295
|
if not current_bot_id:
|
|
269
296
|
return {"status": "error", "error": "Не удалось определить bot_id"}
|
|
270
|
-
|
|
271
297
|
logger.info(
|
|
272
298
|
f"🔍 Ищем пользователей на стадии '{stage}' для бота '{current_bot_id}'"
|
|
273
299
|
)
|
|
274
|
-
|
|
275
|
-
# Получаем последние сессии для каждого пользователя с нужной стадией
|
|
276
|
-
# Сначала получаем все активные сессии с нужной стадией
|
|
277
300
|
sessions_query = (
|
|
278
301
|
supabase_client.client.table("sales_chat_sessions")
|
|
279
302
|
.select("user_id, id, current_stage, created_at")
|
|
280
303
|
.eq("status", "active")
|
|
281
304
|
.eq("current_stage", stage)
|
|
282
305
|
)
|
|
283
|
-
|
|
284
|
-
# Фильтруем по bot_id если указан
|
|
285
306
|
if current_bot_id:
|
|
286
307
|
sessions_query = sessions_query.eq("bot_id", current_bot_id)
|
|
287
|
-
|
|
288
|
-
# Сортируем по дате создания (последние сначала)
|
|
289
308
|
sessions_query = sessions_query.order("created_at", desc=True)
|
|
290
|
-
|
|
291
309
|
sessions_data = sessions_query.execute()
|
|
292
|
-
|
|
293
310
|
if not sessions_data.data:
|
|
294
311
|
logger.info(f"📭 Пользователи на стадии '{stage}' не найдены")
|
|
295
312
|
return {
|
|
@@ -299,56 +316,48 @@ async def send_message_to_users_by_stage(
|
|
|
299
316
|
"messages_sent": 0,
|
|
300
317
|
"errors": [],
|
|
301
318
|
}
|
|
302
|
-
|
|
303
|
-
# Выбираем уникальные user_id (берем только последнюю сессию для каждого пользователя)
|
|
304
319
|
unique_users = {}
|
|
305
320
|
for session in sessions_data.data:
|
|
306
321
|
user_id = session["user_id"]
|
|
307
|
-
# Если пользователь еще не добавлен, добавляем его (так как сессии отсортированы по дате, первая будет самой последней)
|
|
308
322
|
if user_id not in unique_users:
|
|
309
323
|
unique_users[user_id] = {
|
|
310
324
|
"session_id": session["id"],
|
|
311
325
|
"current_stage": session["current_stage"],
|
|
312
326
|
}
|
|
313
|
-
|
|
314
327
|
logger.info(
|
|
315
328
|
f"👥 Найдено {len(unique_users)} уникальных пользователей на стадии '{stage}'"
|
|
316
329
|
)
|
|
317
|
-
|
|
318
|
-
# Отправляем сообщения
|
|
319
330
|
messages_sent = 0
|
|
320
331
|
errors = []
|
|
321
|
-
|
|
332
|
+
photo_path = None
|
|
333
|
+
if photo:
|
|
334
|
+
photo_path = PROJECT_ROOT / photo
|
|
335
|
+
if not photo_path.exists():
|
|
336
|
+
raise FileNotFoundError(f"Файл с фото не найден: {photo}")
|
|
322
337
|
for user_id, user_data in unique_users.items():
|
|
323
338
|
session_id = user_data["session_id"]
|
|
324
|
-
|
|
325
339
|
try:
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
340
|
+
if photo_path:
|
|
341
|
+
msg = await bot.send_photo(chat_id=user_id, photo=FSInputFile(str(photo_path)), caption=message_text)
|
|
342
|
+
msg_type = "photo"
|
|
343
|
+
else:
|
|
344
|
+
msg = await bot.send_message(chat_id=user_id, text=message_text)
|
|
345
|
+
msg_type = "text"
|
|
330
346
|
await supabase_client.add_message(
|
|
331
347
|
session_id=session_id,
|
|
332
348
|
role="assistant",
|
|
333
349
|
content=message_text,
|
|
334
|
-
message_type=
|
|
335
|
-
metadata={
|
|
336
|
-
"sent_by_stage_broadcast": True,
|
|
337
|
-
"target_stage": stage,
|
|
338
|
-
"broadcast_timestamp": datetime.now().isoformat(),
|
|
339
|
-
},
|
|
350
|
+
message_type=msg_type,
|
|
351
|
+
metadata={"sent_by_stage_broadcast": True, "target_stage": stage, "broadcast_timestamp": datetime.now().isoformat(), "has_photo": bool(photo)},
|
|
340
352
|
)
|
|
341
|
-
|
|
342
353
|
messages_sent += 1
|
|
343
354
|
logger.info(
|
|
344
355
|
f"✅ Сообщение отправлено пользователю {user_id} (стадия: {stage})"
|
|
345
356
|
)
|
|
346
|
-
|
|
347
357
|
except Exception as e:
|
|
348
358
|
error_msg = f"Ошибка отправки пользователю {user_id}: {str(e)}"
|
|
349
359
|
errors.append(error_msg)
|
|
350
360
|
logger.error(f"❌ {error_msg}")
|
|
351
|
-
|
|
352
361
|
result = {
|
|
353
362
|
"status": "success",
|
|
354
363
|
"stage": stage,
|
|
@@ -356,13 +365,10 @@ async def send_message_to_users_by_stage(
|
|
|
356
365
|
"messages_sent": messages_sent,
|
|
357
366
|
"errors": errors,
|
|
358
367
|
}
|
|
359
|
-
|
|
360
368
|
logger.info(
|
|
361
369
|
f"📊 Результат рассылки по стадии '{stage}': {messages_sent}/{len(unique_users)} сообщений отправлено"
|
|
362
370
|
)
|
|
363
|
-
|
|
364
371
|
return result
|
|
365
|
-
|
|
366
372
|
except Exception as e:
|
|
367
373
|
logger.error(f"❌ Ошибка в send_message_to_users_by_stage: {e}")
|
|
368
374
|
return {"status": "error", "error": str(e), "stage": stage}
|
|
@@ -79,7 +79,7 @@ async def timeup_handler(message: Message, state: FSMContext):
|
|
|
79
79
|
"""Обработчик команды /timeup (или /вперед) - тестирование запланированных событий"""
|
|
80
80
|
from datetime import datetime
|
|
81
81
|
|
|
82
|
-
from ..core.decorators import process_scheduled_event, update_event_result
|
|
82
|
+
from ..core.decorators import process_scheduled_event, update_event_result, process_admin_event
|
|
83
83
|
|
|
84
84
|
supabase_client = get_global_var("supabase_client")
|
|
85
85
|
|
|
@@ -148,22 +148,27 @@ async def timeup_handler(message: Message, state: FSMContext):
|
|
|
148
148
|
)
|
|
149
149
|
|
|
150
150
|
# Выполняем событие
|
|
151
|
-
if event_category
|
|
151
|
+
if event_category == "admin_event":
|
|
152
|
+
# Для админских событий используем тестовую отправку только текущему пользователю
|
|
153
|
+
await process_admin_event(event, single_user_id=message.from_user.id)
|
|
154
|
+
# Не отмечаем админское событие как выполненное при тестовой отправке
|
|
155
|
+
success_count += 1
|
|
156
|
+
results.append(f"✅ {event_label} (тестовая отправка)")
|
|
157
|
+
logger.info(f"✅ Событие {event_id} протестировано для пользователя {message.from_user.id}")
|
|
158
|
+
else:
|
|
152
159
|
await process_scheduled_event(event)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
)
|
|
166
|
-
if event_category != "admin_event":
|
|
160
|
+
# Помечаем как выполненное только не-админские события
|
|
161
|
+
if event_category != "global_handler":
|
|
162
|
+
await update_event_result(
|
|
163
|
+
event_id,
|
|
164
|
+
"completed",
|
|
165
|
+
{
|
|
166
|
+
"executed": True,
|
|
167
|
+
"test_mode": True,
|
|
168
|
+
"tested_by_user": message.from_user.id,
|
|
169
|
+
"tested_at": datetime.now().isoformat(),
|
|
170
|
+
},
|
|
171
|
+
)
|
|
167
172
|
success_count += 1
|
|
168
173
|
results.append(f"✅ {event_label}")
|
|
169
174
|
logger.info(f"✅ Событие {event_id} успешно выполнено")
|
|
@@ -4,7 +4,7 @@ smart_bot_factory/config.py,sha256=-4jOCAlbPQI2ha8OD01E_Y8FbRrIhUNXcrA-pTu_EFQ,1
|
|
|
4
4
|
smart_bot_factory/setup_checker.py,sha256=MSTMvLrUwgkjCbZBy-vKJcFMeADf2qTnaZClp7TBxWY,19833
|
|
5
5
|
smart_bot_factory/utm_link_generator.py,sha256=eA3Eic4Wvs0QpMdAFPJgYJyzv-_pSQWZ5ySdFHjyJkg,4327
|
|
6
6
|
smart_bot_factory/admin/__init__.py,sha256=xHUtMLeQDDQEXZrg4WAGpt-TWp8adjCC0-mYUBuino4,489
|
|
7
|
-
smart_bot_factory/admin/admin_events.py,sha256=
|
|
7
|
+
smart_bot_factory/admin/admin_events.py,sha256=BrCX8RgRxxDJ7hcRmkduvdISd6wI6KhnsVwb1KCZ-R4,53792
|
|
8
8
|
smart_bot_factory/admin/admin_logic.py,sha256=TiXhZIXiXK1YQkrueY_zPFYL3_sJzoAvd100AP-ZCWU,23136
|
|
9
9
|
smart_bot_factory/admin/admin_manager.py,sha256=u22j1xy2zLTw0kejgzWF5m657_IyMDKnDlM9kBzbjh4,6359
|
|
10
10
|
smart_bot_factory/admin/admin_tester.py,sha256=YwovS51chPJoQ33pIiug-vS3augQzc1hHfbma48HtDk,6352
|
|
@@ -31,8 +31,8 @@ smart_bot_factory/configs/growthmed-october-24/welcome_file/welcome_file_msg.txt
|
|
|
31
31
|
smart_bot_factory/configs/growthmed-october-24/welcome_file/Чек лист по 152ФЗ и 323ФЗ для медицины.pdf,sha256=BiAiQHNnQXJPMsks9AeL6s0beEjRFkRMJLMlAn4WorA,5284954
|
|
32
32
|
smart_bot_factory/core/bot_utils.py,sha256=vbWb4wUkG-FlQIGTQ6RLoSSVg_LRVTzpnX3tyowsbqY,52032
|
|
33
33
|
smart_bot_factory/core/conversation_manager.py,sha256=ga0ThoDpvov97x5KLcHpL8aRlEZB1XRw3JPrexIb-jk,29506
|
|
34
|
-
smart_bot_factory/core/decorators.py,sha256=
|
|
35
|
-
smart_bot_factory/core/message_sender.py,sha256=
|
|
34
|
+
smart_bot_factory/core/decorators.py,sha256=9TjB9mTp2fvopSgFOJOsQ326K220QHK87ZPoNa5BV_g,108733
|
|
35
|
+
smart_bot_factory/core/message_sender.py,sha256=iboiSRTatlBbyM54zdWOp33zFuP_FsZWLTK3gzkV4Z0,33225
|
|
36
36
|
smart_bot_factory/core/router.py,sha256=66sCp7Okyu1ceIQPpxFF88-NizTD1N7rR4ZAdwDYmi8,16285
|
|
37
37
|
smart_bot_factory/core/router_manager.py,sha256=x-cc4zhZUVPCTUH980GdlE_-JjHRtYnPdEBiWnf3ka8,10156
|
|
38
38
|
smart_bot_factory/core/states.py,sha256=mFyQ-oxIjTX03Wy5NHZUgYgh8fsJ5YmQU09_PhPAB04,889
|
|
@@ -40,8 +40,8 @@ smart_bot_factory/creation/__init__.py,sha256=lHFgYIjOLvdsDAW8eLMp8zfFUCd-6wepvU
|
|
|
40
40
|
smart_bot_factory/creation/bot_builder.py,sha256=-gF53eCaWBRKoUbRGR-PDHpqLWlceQ9FLUdEqQRt6lw,42929
|
|
41
41
|
smart_bot_factory/creation/bot_testing.py,sha256=aMPSgj2o2zl6yWiV1a6RMmSJfiwMYp-_0ZkmJ6aFZVs,54561
|
|
42
42
|
smart_bot_factory/dashboard/__init__.py,sha256=Qie1pJgzprBlCPrZVQLhVs1JR5-YrpzfcBvHHD3OLJk,94
|
|
43
|
-
smart_bot_factory/event/__init__.py,sha256=
|
|
44
|
-
smart_bot_factory/handlers/handlers.py,sha256=
|
|
43
|
+
smart_bot_factory/event/__init__.py,sha256=eWKoiZ_DD-jTL9Tjv11ucQat8P8AhP3xWmQs29pd4ZA,190
|
|
44
|
+
smart_bot_factory/handlers/handlers.py,sha256=F7qgSrz_8kXDvolYmJPofF06PZRucsA6lI11iQCjbzk,64372
|
|
45
45
|
smart_bot_factory/integrations/openai_client.py,sha256=R04qglOWNu3yQ32QRMRMzO01T6luWTp83H3OJMvD-uM,24316
|
|
46
46
|
smart_bot_factory/integrations/supabase_client.py,sha256=7Yip9din4PDqFD1X2pkKB4KYAhmf04LUzxo9SnOrVg4,68415
|
|
47
47
|
smart_bot_factory/message/__init__.py,sha256=neUUYAs8ITz3cV1B_T0P9_3XZvvn8cZ2VDpDqrf6lrc,1940
|
|
@@ -52,8 +52,8 @@ smart_bot_factory/utils/__init__.py,sha256=RgP2IAMFsJF7fxhhg8JLfhbGg9mO63xQM-NEa
|
|
|
52
52
|
smart_bot_factory/utils/debug_routing.py,sha256=wV9BFwNmjBbF3rNI7UBdGsTf1bIT5XVQIGnxwIxhNYA,4707
|
|
53
53
|
smart_bot_factory/utils/prompt_loader.py,sha256=YClbQjvRgTWCD42Rr92g77zzVEVMUgrqStI6YETC0c4,20022
|
|
54
54
|
smart_bot_factory/utils/user_prompt_loader.py,sha256=lq1eQ4Hb2qN22osOjaFtkGdEc4OgpFPrzPNpPhsm5kA,2353
|
|
55
|
-
smart_bot_factory-0.
|
|
56
|
-
smart_bot_factory-0.
|
|
57
|
-
smart_bot_factory-0.
|
|
58
|
-
smart_bot_factory-0.
|
|
59
|
-
smart_bot_factory-0.
|
|
55
|
+
smart_bot_factory-1.0.1.dist-info/METADATA,sha256=XDFo6ENRuSxQOSwcT1G9pkBdHqoXBDwsYaOsX79cacw,40748
|
|
56
|
+
smart_bot_factory-1.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
57
|
+
smart_bot_factory-1.0.1.dist-info/entry_points.txt,sha256=ybKEAI0WSb7WoRiey7QE-HHfn88UGV7nxLDxXq7b7SU,50
|
|
58
|
+
smart_bot_factory-1.0.1.dist-info/licenses/LICENSE,sha256=OrK3cwdUTzNzIhJvSPtJaVMoYIyC_sSx5EFE_FDMvGs,1092
|
|
59
|
+
smart_bot_factory-1.0.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|