medicafe 0.251027.3__py3-none-any.whl → 0.251030.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.
Potentially problematic release.
This version of medicafe might be problematic. Click here for more details.
- MediBot/MediBot.bat +12 -2
- MediBot/__init__.py +1 -1
- MediBot/config_editor.bat +90 -0
- MediBot/config_editor.py +430 -0
- MediBot/config_editor_helpers.py +310 -0
- MediCafe/__init__.py +1 -1
- MediLink/MediLink_Gmail.py +11 -2
- MediLink/__init__.py +1 -1
- MediLink/webapp.html +7 -1
- {medicafe-0.251027.3.dist-info → medicafe-0.251030.0.dist-info}/METADATA +1 -1
- {medicafe-0.251027.3.dist-info → medicafe-0.251030.0.dist-info}/RECORD +15 -12
- {medicafe-0.251027.3.dist-info → medicafe-0.251030.0.dist-info}/LICENSE +0 -0
- {medicafe-0.251027.3.dist-info → medicafe-0.251030.0.dist-info}/WHEEL +0 -0
- {medicafe-0.251027.3.dist-info → medicafe-0.251030.0.dist-info}/entry_points.txt +0 -0
- {medicafe-0.251027.3.dist-info → medicafe-0.251030.0.dist-info}/top_level.txt +0 -0
MediBot/MediBot.bat
CHANGED
|
@@ -732,7 +732,8 @@ echo 4. Toggle Performance Logging ^(session^)
|
|
|
732
732
|
echo 5. Send TEST error report (email)
|
|
733
733
|
echo 6. Forced MediCafe version rollback
|
|
734
734
|
echo 7. Process CSV Files
|
|
735
|
-
echo 8.
|
|
735
|
+
echo 8. Edit Config File (JSON Editor)
|
|
736
|
+
echo 9. Back to Main Menu
|
|
736
737
|
echo.
|
|
737
738
|
set /p tchoice=Enter your choice:
|
|
738
739
|
if "%tchoice%"=="1" goto open_latest_log
|
|
@@ -742,7 +743,8 @@ if "%tchoice%"=="4" goto toggle_perf_logging
|
|
|
742
743
|
if "%tchoice%"=="5" goto send_test_error_report
|
|
743
744
|
if "%tchoice%"=="6" goto forced_version_rollback
|
|
744
745
|
if "%tchoice%"=="7" goto process_csvs
|
|
745
|
-
if "%tchoice%"=="8" goto
|
|
746
|
+
if "%tchoice%"=="8" goto config_editor
|
|
747
|
+
if "%tchoice%"=="9" goto main_menu
|
|
746
748
|
echo Invalid choice. Please try again.
|
|
747
749
|
pause
|
|
748
750
|
goto troubleshooting_menu
|
|
@@ -876,6 +878,14 @@ echo Rollback complete. Current MediCafe version: %medicafe_version%
|
|
|
876
878
|
pause >nul
|
|
877
879
|
goto troubleshooting_menu
|
|
878
880
|
|
|
881
|
+
::: Config Editor
|
|
882
|
+
:config_editor
|
|
883
|
+
cls
|
|
884
|
+
echo Starting Config Editor...
|
|
885
|
+
call "%~dp0config_editor.bat"
|
|
886
|
+
pause
|
|
887
|
+
goto troubleshooting_menu
|
|
888
|
+
|
|
879
889
|
::: Subroutine to display the last N lines of a file
|
|
880
890
|
:tail
|
|
881
891
|
::: Usage: call :tail filename number_of_lines
|
MediBot/__init__.py
CHANGED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
setlocal enabledelayedexpansion
|
|
3
|
+
|
|
4
|
+
:: Config Editor Launcher
|
|
5
|
+
:: Called from MediBot.bat Troubleshooting menu
|
|
6
|
+
|
|
7
|
+
:: Get script directory and set paths
|
|
8
|
+
set "script_dir=%~dp0"
|
|
9
|
+
set "workspace_root=%script_dir%.."
|
|
10
|
+
set "python_script=%script_dir%config_editor.py"
|
|
11
|
+
|
|
12
|
+
:: Use the same path resolution logic as MediLink_ConfigLoader
|
|
13
|
+
:: Try default path first (relative to MediCafe module)
|
|
14
|
+
set "config_path=%workspace_root%\json\config.json"
|
|
15
|
+
|
|
16
|
+
:: If default path doesn't exist, try platform-specific fallbacks
|
|
17
|
+
if not exist "%config_path%" (
|
|
18
|
+
:: Check for Windows XP (F: drive)
|
|
19
|
+
if exist "F:\" (
|
|
20
|
+
set "config_path=F:\Medibot\json\config.json"
|
|
21
|
+
) else (
|
|
22
|
+
:: Use current working directory for other Windows versions
|
|
23
|
+
set "config_path=%CD%\json\config.json"
|
|
24
|
+
)
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
:: Display header
|
|
28
|
+
cls
|
|
29
|
+
echo ========================================
|
|
30
|
+
echo MediCafe Config Editor
|
|
31
|
+
echo ========================================
|
|
32
|
+
echo.
|
|
33
|
+
|
|
34
|
+
:: Check if Python is available
|
|
35
|
+
python --version >nul 2>&1
|
|
36
|
+
if errorlevel 1 (
|
|
37
|
+
echo ERROR: Python is not available or not in PATH
|
|
38
|
+
echo.
|
|
39
|
+
echo Please ensure Python 3.4.4 or later is installed and accessible.
|
|
40
|
+
echo.
|
|
41
|
+
pause
|
|
42
|
+
exit /b 1
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
:: Check if config file exists
|
|
46
|
+
if not exist "%config_path%" (
|
|
47
|
+
echo WARNING: Config file not found at:
|
|
48
|
+
echo %config_path%
|
|
49
|
+
echo.
|
|
50
|
+
echo The editor will start with an empty configuration.
|
|
51
|
+
echo You can create a new config file or navigate to the correct location.
|
|
52
|
+
echo.
|
|
53
|
+
set /p continue_choice="Continue anyway? (Y/N): "
|
|
54
|
+
if /i not "!continue_choice!"=="Y" (
|
|
55
|
+
echo Operation cancelled.
|
|
56
|
+
pause
|
|
57
|
+
exit /b 0
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
:: Check if editor script exists
|
|
62
|
+
if not exist "%python_script%" (
|
|
63
|
+
echo ERROR: Config editor script not found at:
|
|
64
|
+
echo %python_script%
|
|
65
|
+
echo.
|
|
66
|
+
echo Please ensure config_editor.py is in the MediBot directory.
|
|
67
|
+
echo.
|
|
68
|
+
pause
|
|
69
|
+
exit /b 1
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
:: Run the config editor
|
|
73
|
+
echo Starting config editor...
|
|
74
|
+
echo.
|
|
75
|
+
python "%python_script%" "%config_path%"
|
|
76
|
+
|
|
77
|
+
:: Check exit code
|
|
78
|
+
if errorlevel 1 (
|
|
79
|
+
echo.
|
|
80
|
+
echo ERROR: Config editor encountered an error.
|
|
81
|
+
echo.
|
|
82
|
+
) else (
|
|
83
|
+
echo.
|
|
84
|
+
echo Config editor completed successfully.
|
|
85
|
+
echo.
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
:: Return to caller
|
|
89
|
+
pause
|
|
90
|
+
exit /b %errorlevel%
|
MediBot/config_editor.py
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
DOS-Style Config.json Editor
|
|
5
|
+
Interactive JSON configuration editor with DOS-like interface.
|
|
6
|
+
Compatible with Python 3.4.4 and Windows XP.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
import json
|
|
12
|
+
from collections import OrderedDict
|
|
13
|
+
|
|
14
|
+
# Import our helper functions
|
|
15
|
+
from config_editor_helpers import (
|
|
16
|
+
load_config_safe, create_backup, save_config_atomic, validate_json_structure,
|
|
17
|
+
is_sensitive_key, get_value_type, format_value_for_display, clear_config_cache,
|
|
18
|
+
get_nested_value, set_nested_value, get_path_string, validate_key_name, parse_value_input,
|
|
19
|
+
resolve_config_path
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
class ConfigEditor:
|
|
23
|
+
def __init__(self, config_path):
|
|
24
|
+
self.config_path = config_path
|
|
25
|
+
self.config = OrderedDict()
|
|
26
|
+
self.original_config = OrderedDict()
|
|
27
|
+
self.current_path = [] # List of keys representing current location
|
|
28
|
+
self.staged_changes = [] # List of (path, old_value, new_value) tuples
|
|
29
|
+
self.running = True
|
|
30
|
+
|
|
31
|
+
# Load initial config
|
|
32
|
+
self.config, error = load_config_safe(config_path)
|
|
33
|
+
if error:
|
|
34
|
+
print("WARNING: {}".format(error))
|
|
35
|
+
print("Starting with empty configuration.")
|
|
36
|
+
self.config = OrderedDict()
|
|
37
|
+
|
|
38
|
+
# Keep a copy of original for comparison
|
|
39
|
+
self.original_config = json.loads(json.dumps(self.config, ensure_ascii=False), object_pairs_hook=OrderedDict)
|
|
40
|
+
|
|
41
|
+
def clear_screen(self):
|
|
42
|
+
"""Clear the console screen (XP compatible)."""
|
|
43
|
+
os.system('cls' if os.name == 'nt' else 'clear')
|
|
44
|
+
|
|
45
|
+
def print_header(self):
|
|
46
|
+
"""Print the editor header."""
|
|
47
|
+
print("=" * 60)
|
|
48
|
+
print(" MediCafe Config Editor")
|
|
49
|
+
print("=" * 60)
|
|
50
|
+
print("")
|
|
51
|
+
|
|
52
|
+
def print_breadcrumb(self):
|
|
53
|
+
"""Print current navigation path."""
|
|
54
|
+
path_str = get_path_string(self.current_path)
|
|
55
|
+
print("Current Path: {}".format(path_str))
|
|
56
|
+
print("-" * 60)
|
|
57
|
+
|
|
58
|
+
def print_current_level(self):
|
|
59
|
+
"""Print the current level of the config tree."""
|
|
60
|
+
current_data, found = get_nested_value(self.config, self.current_path)
|
|
61
|
+
|
|
62
|
+
if not found:
|
|
63
|
+
print("ERROR: Invalid path")
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
if not isinstance(current_data, dict):
|
|
67
|
+
print("ERROR: Cannot navigate into non-object value")
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
if not current_data:
|
|
71
|
+
print("(Empty object)")
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
# Print numbered items
|
|
75
|
+
items = list(current_data.items())
|
|
76
|
+
for i, (key, value) in enumerate(items, 1):
|
|
77
|
+
value_display = format_value_for_display(value)
|
|
78
|
+
value_type = get_value_type(value)
|
|
79
|
+
|
|
80
|
+
# Mark sensitive keys
|
|
81
|
+
sensitive_mark = " [SENSITIVE]" if is_sensitive_key(key) else ""
|
|
82
|
+
|
|
83
|
+
print("[{}] {}: {} ({}){}".format(i, key, value_display, value_type, sensitive_mark))
|
|
84
|
+
|
|
85
|
+
def print_commands(self):
|
|
86
|
+
"""Print available commands."""
|
|
87
|
+
print("")
|
|
88
|
+
print("Commands:")
|
|
89
|
+
print(" [1-N] Navigate/Edit [0] Back [EDIT #] Edit [ADD] Add Key")
|
|
90
|
+
print(" [DIR] Refresh [HELP] Help [EXIT] Save & Exit")
|
|
91
|
+
print("-" * 60)
|
|
92
|
+
|
|
93
|
+
def print_help(self):
|
|
94
|
+
"""Print detailed help information."""
|
|
95
|
+
self.clear_screen()
|
|
96
|
+
self.print_header()
|
|
97
|
+
print("HELP - Config Editor Commands")
|
|
98
|
+
print("=" * 60)
|
|
99
|
+
print("")
|
|
100
|
+
print("Navigation:")
|
|
101
|
+
print(" [1-N] - Navigate into nested objects or edit simple values")
|
|
102
|
+
print(" [0] - Go back to parent level")
|
|
103
|
+
print(" [..] - Same as [0]")
|
|
104
|
+
print("")
|
|
105
|
+
print("Editing:")
|
|
106
|
+
print(" [EDIT #] - Edit the numbered item")
|
|
107
|
+
print(" [ADD] - Add a new key at current level")
|
|
108
|
+
print("")
|
|
109
|
+
print("Utilities:")
|
|
110
|
+
print(" [DIR] - Refresh current level display")
|
|
111
|
+
print(" [HELP] - Show this help screen")
|
|
112
|
+
print(" [EXIT] - Save changes and exit")
|
|
113
|
+
print(" [Q] - Same as [EXIT]")
|
|
114
|
+
print("")
|
|
115
|
+
print("Safety Features:")
|
|
116
|
+
print(" - All changes are staged until you save")
|
|
117
|
+
print(" - Automatic backup created before saving")
|
|
118
|
+
print(" - Sensitive keys marked with [SENSITIVE]")
|
|
119
|
+
print(" - Preview all changes before committing")
|
|
120
|
+
print("")
|
|
121
|
+
print("Press Enter to continue...")
|
|
122
|
+
input()
|
|
123
|
+
|
|
124
|
+
def navigate_to_item(self, item_number):
|
|
125
|
+
"""Navigate to a numbered item."""
|
|
126
|
+
current_data, found = get_nested_value(self.config, self.current_path)
|
|
127
|
+
|
|
128
|
+
if not found or not isinstance(current_data, dict):
|
|
129
|
+
print("ERROR: Cannot navigate from current location")
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
items = list(current_data.items())
|
|
133
|
+
if item_number < 1 or item_number > len(items):
|
|
134
|
+
print("ERROR: Invalid item number")
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
key, value = items[item_number - 1]
|
|
138
|
+
|
|
139
|
+
# If it's a simple value, offer to edit it
|
|
140
|
+
if not isinstance(value, dict):
|
|
141
|
+
self.edit_simple_value(key, value)
|
|
142
|
+
else:
|
|
143
|
+
# Navigate into the object
|
|
144
|
+
self.current_path.append(key)
|
|
145
|
+
|
|
146
|
+
def edit_simple_value(self, key, current_value):
|
|
147
|
+
"""Edit a simple (non-object) value."""
|
|
148
|
+
current_type = get_value_type(current_value)
|
|
149
|
+
current_display = format_value_for_display(current_value)
|
|
150
|
+
|
|
151
|
+
print("")
|
|
152
|
+
print("Editing: {}".format(key))
|
|
153
|
+
print("Current value: {} ({})".format(current_display, current_type))
|
|
154
|
+
print("")
|
|
155
|
+
|
|
156
|
+
# Get new value
|
|
157
|
+
new_value_str = input("Enter new value: ").strip()
|
|
158
|
+
|
|
159
|
+
if not new_value_str:
|
|
160
|
+
print("No changes made.")
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
# Parse the new value
|
|
164
|
+
new_value, error = parse_value_input(new_value_str, current_type)
|
|
165
|
+
if error:
|
|
166
|
+
print("ERROR: {}".format(error))
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
# Show preview
|
|
170
|
+
new_display = format_value_for_display(new_value)
|
|
171
|
+
print("")
|
|
172
|
+
print("Preview: {} -> {}".format(current_display, new_display))
|
|
173
|
+
|
|
174
|
+
# Confirm change
|
|
175
|
+
if is_sensitive_key(key):
|
|
176
|
+
print("WARNING: This appears to be a sensitive key!")
|
|
177
|
+
|
|
178
|
+
confirm = input("Apply this change? (Y/N): ").strip().upper()
|
|
179
|
+
if confirm in ['Y', 'YES']:
|
|
180
|
+
# Stage the change
|
|
181
|
+
full_path = self.current_path + [key]
|
|
182
|
+
self.staged_changes.append((full_path, current_value, new_value))
|
|
183
|
+
set_nested_value(self.config, full_path, new_value)
|
|
184
|
+
print("Change staged.")
|
|
185
|
+
else:
|
|
186
|
+
print("Change cancelled.")
|
|
187
|
+
|
|
188
|
+
def add_new_key(self):
|
|
189
|
+
"""Add a new key at the current level."""
|
|
190
|
+
current_data, found = get_nested_value(self.config, self.current_path)
|
|
191
|
+
|
|
192
|
+
if not found or not isinstance(current_data, dict):
|
|
193
|
+
print("ERROR: Cannot add key at current location")
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
print("")
|
|
197
|
+
print("Add New Key")
|
|
198
|
+
print("-" * 30)
|
|
199
|
+
|
|
200
|
+
# Get key name
|
|
201
|
+
while True:
|
|
202
|
+
key_name = input("Enter key name: ").strip()
|
|
203
|
+
is_valid, error = validate_key_name(key_name, current_data.keys())
|
|
204
|
+
if is_valid:
|
|
205
|
+
break
|
|
206
|
+
print("ERROR: {}".format(error))
|
|
207
|
+
|
|
208
|
+
# Get value type
|
|
209
|
+
print("")
|
|
210
|
+
print("Value types:")
|
|
211
|
+
print(" 1. string - Text value")
|
|
212
|
+
print(" 2. number - Numeric value")
|
|
213
|
+
print(" 3. boolean - true/false")
|
|
214
|
+
print(" 4. array - List of values")
|
|
215
|
+
print(" 5. object - Nested structure")
|
|
216
|
+
print("")
|
|
217
|
+
|
|
218
|
+
while True:
|
|
219
|
+
type_choice = input("Select value type (1-5): ").strip()
|
|
220
|
+
type_map = {'1': 'string', '2': 'number', '3': 'boolean', '4': 'array', '5': 'object'}
|
|
221
|
+
if type_choice in type_map:
|
|
222
|
+
value_type = type_map[type_choice]
|
|
223
|
+
break
|
|
224
|
+
print("ERROR: Invalid choice. Enter 1-5.")
|
|
225
|
+
|
|
226
|
+
# Get value based on type
|
|
227
|
+
if value_type == 'string':
|
|
228
|
+
value_input = input("Enter string value: ").strip()
|
|
229
|
+
elif value_type == 'number':
|
|
230
|
+
value_input = input("Enter number value: ").strip()
|
|
231
|
+
elif value_type == 'boolean':
|
|
232
|
+
value_input = input("Enter boolean value (true/false): ").strip()
|
|
233
|
+
elif value_type == 'array':
|
|
234
|
+
value_input = input("Enter array values (comma-separated): ").strip()
|
|
235
|
+
elif value_type == 'object':
|
|
236
|
+
value_input = "" # Empty object
|
|
237
|
+
print("Creating empty object. You can add keys to it later.")
|
|
238
|
+
|
|
239
|
+
# Parse the value
|
|
240
|
+
new_value, error = parse_value_input(value_input, value_type)
|
|
241
|
+
if error:
|
|
242
|
+
print("ERROR: {}".format(error))
|
|
243
|
+
return
|
|
244
|
+
|
|
245
|
+
# Show preview
|
|
246
|
+
new_display = format_value_for_display(new_value)
|
|
247
|
+
print("")
|
|
248
|
+
print("Preview: {}: {} ({})".format(key_name, new_display, value_type))
|
|
249
|
+
|
|
250
|
+
# Confirm addition
|
|
251
|
+
if is_sensitive_key(key_name):
|
|
252
|
+
print("WARNING: This appears to be a sensitive key!")
|
|
253
|
+
|
|
254
|
+
confirm = input("Add this key? (Y/N): ").strip().upper()
|
|
255
|
+
if confirm in ['Y', 'YES']:
|
|
256
|
+
# Stage the change
|
|
257
|
+
full_path = self.current_path + [key_name]
|
|
258
|
+
self.staged_changes.append((full_path, None, new_value))
|
|
259
|
+
set_nested_value(self.config, full_path, new_value)
|
|
260
|
+
print("Key added and staged.")
|
|
261
|
+
else:
|
|
262
|
+
print("Addition cancelled.")
|
|
263
|
+
|
|
264
|
+
def show_staged_changes(self):
|
|
265
|
+
"""Show all staged changes."""
|
|
266
|
+
if not self.staged_changes:
|
|
267
|
+
print("No staged changes.")
|
|
268
|
+
return
|
|
269
|
+
|
|
270
|
+
print("")
|
|
271
|
+
print("Staged Changes:")
|
|
272
|
+
print("-" * 30)
|
|
273
|
+
|
|
274
|
+
for i, (path, old_value, new_value) in enumerate(self.staged_changes, 1):
|
|
275
|
+
path_str = " > ".join(path)
|
|
276
|
+
if old_value is None:
|
|
277
|
+
print("{}. ADD: {} = {}".format(i, path_str, format_value_for_display(new_value)))
|
|
278
|
+
else:
|
|
279
|
+
print("{}. EDIT: {} = {} -> {}".format(
|
|
280
|
+
i, path_str,
|
|
281
|
+
format_value_for_display(old_value),
|
|
282
|
+
format_value_for_display(new_value)
|
|
283
|
+
))
|
|
284
|
+
|
|
285
|
+
def save_changes(self):
|
|
286
|
+
"""Save all staged changes to file."""
|
|
287
|
+
if not self.staged_changes:
|
|
288
|
+
print("No changes to save.")
|
|
289
|
+
return True
|
|
290
|
+
|
|
291
|
+
print("")
|
|
292
|
+
print("Saving Changes...")
|
|
293
|
+
print("-" * 30)
|
|
294
|
+
|
|
295
|
+
# Show summary
|
|
296
|
+
self.show_staged_changes()
|
|
297
|
+
print("")
|
|
298
|
+
|
|
299
|
+
# Validate JSON structure
|
|
300
|
+
is_valid, error = validate_json_structure(self.config)
|
|
301
|
+
if not is_valid:
|
|
302
|
+
print("ERROR: {}".format(error))
|
|
303
|
+
return False
|
|
304
|
+
|
|
305
|
+
# Create backup
|
|
306
|
+
backup_path = create_backup(self.config_path)
|
|
307
|
+
if backup_path:
|
|
308
|
+
print("Backup created: {}".format(os.path.basename(backup_path)))
|
|
309
|
+
else:
|
|
310
|
+
print("WARNING: Could not create backup")
|
|
311
|
+
|
|
312
|
+
# Save to file
|
|
313
|
+
success, error = save_config_atomic(self.config_path, self.config)
|
|
314
|
+
if not success:
|
|
315
|
+
print("ERROR: {}".format(error))
|
|
316
|
+
return False
|
|
317
|
+
|
|
318
|
+
# Clear cache
|
|
319
|
+
cache_success, cache_error = clear_config_cache()
|
|
320
|
+
if not cache_success:
|
|
321
|
+
print("WARNING: {}".format(cache_error))
|
|
322
|
+
|
|
323
|
+
print("Configuration saved successfully!")
|
|
324
|
+
return True
|
|
325
|
+
|
|
326
|
+
def process_command(self, command):
|
|
327
|
+
"""Process a user command."""
|
|
328
|
+
command = command.strip().upper()
|
|
329
|
+
|
|
330
|
+
if command == 'EXIT' or command == 'Q':
|
|
331
|
+
if self.staged_changes:
|
|
332
|
+
print("")
|
|
333
|
+
print("You have unsaved changes.")
|
|
334
|
+
self.show_staged_changes()
|
|
335
|
+
print("")
|
|
336
|
+
save_choice = input("Save changes before exiting? (Y/N): ").strip().upper()
|
|
337
|
+
if save_choice in ['Y', 'YES']:
|
|
338
|
+
if self.save_changes():
|
|
339
|
+
self.running = False
|
|
340
|
+
else:
|
|
341
|
+
print("Changes discarded.")
|
|
342
|
+
self.running = False
|
|
343
|
+
else:
|
|
344
|
+
self.running = False
|
|
345
|
+
|
|
346
|
+
elif command == 'HELP':
|
|
347
|
+
self.print_help()
|
|
348
|
+
|
|
349
|
+
elif command == 'DIR':
|
|
350
|
+
pass # Will refresh display
|
|
351
|
+
|
|
352
|
+
elif command == 'ADD':
|
|
353
|
+
self.add_new_key()
|
|
354
|
+
|
|
355
|
+
elif command.startswith('EDIT '):
|
|
356
|
+
try:
|
|
357
|
+
item_num = int(command[5:].strip())
|
|
358
|
+
self.navigate_to_item(item_num)
|
|
359
|
+
except ValueError:
|
|
360
|
+
print("ERROR: Invalid EDIT command. Use EDIT followed by a number.")
|
|
361
|
+
|
|
362
|
+
elif command == '0' or command == '..':
|
|
363
|
+
if self.current_path:
|
|
364
|
+
self.current_path.pop()
|
|
365
|
+
else:
|
|
366
|
+
print("Already at root level.")
|
|
367
|
+
|
|
368
|
+
elif command.isdigit():
|
|
369
|
+
item_num = int(command)
|
|
370
|
+
self.navigate_to_item(item_num)
|
|
371
|
+
|
|
372
|
+
else:
|
|
373
|
+
print("ERROR: Unknown command '{}'. Type HELP for available commands.".format(command))
|
|
374
|
+
|
|
375
|
+
def run(self):
|
|
376
|
+
"""Main editor loop."""
|
|
377
|
+
while self.running:
|
|
378
|
+
self.clear_screen()
|
|
379
|
+
self.print_header()
|
|
380
|
+
self.print_breadcrumb()
|
|
381
|
+
self.print_current_level()
|
|
382
|
+
self.print_commands()
|
|
383
|
+
|
|
384
|
+
if self.staged_changes:
|
|
385
|
+
print("")
|
|
386
|
+
print("You have {} unsaved changes.".format(len(self.staged_changes)))
|
|
387
|
+
|
|
388
|
+
try:
|
|
389
|
+
command = input("Config> ").strip()
|
|
390
|
+
if command:
|
|
391
|
+
self.process_command(command)
|
|
392
|
+
except KeyboardInterrupt:
|
|
393
|
+
print("\n")
|
|
394
|
+
print("Interrupted by user.")
|
|
395
|
+
if self.staged_changes:
|
|
396
|
+
save_choice = input("Save changes before exiting? (Y/N): ").strip().upper()
|
|
397
|
+
if save_choice in ['Y', 'YES']:
|
|
398
|
+
self.save_changes()
|
|
399
|
+
break
|
|
400
|
+
except EOFError:
|
|
401
|
+
break
|
|
402
|
+
|
|
403
|
+
print("Config editor closed.")
|
|
404
|
+
|
|
405
|
+
def main():
|
|
406
|
+
"""Main entry point."""
|
|
407
|
+
if len(sys.argv) != 2:
|
|
408
|
+
print("Usage: python config_editor.py <config_path>")
|
|
409
|
+
sys.exit(1)
|
|
410
|
+
|
|
411
|
+
# Resolve the config path using the same logic as MediLink_ConfigLoader
|
|
412
|
+
default_path = sys.argv[1]
|
|
413
|
+
config_path = resolve_config_path(default_path)
|
|
414
|
+
|
|
415
|
+
# Ensure the directory exists (create it if needed)
|
|
416
|
+
config_dir = os.path.dirname(config_path)
|
|
417
|
+
if not os.path.exists(config_dir):
|
|
418
|
+
try:
|
|
419
|
+
os.makedirs(config_dir)
|
|
420
|
+
except OSError as e:
|
|
421
|
+
# Ignore error if directory already exists (race condition)
|
|
422
|
+
if e.errno != 17: # EEXIST
|
|
423
|
+
print("ERROR: Could not create config directory '{}': {}".format(config_dir, str(e)))
|
|
424
|
+
sys.exit(1)
|
|
425
|
+
|
|
426
|
+
editor = ConfigEditor(config_path)
|
|
427
|
+
editor.run()
|
|
428
|
+
|
|
429
|
+
if __name__ == "__main__":
|
|
430
|
+
main()
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Config Editor Helper Functions
|
|
5
|
+
Provides JSON loading, validation, backup, and atomic write utilities for the config editor.
|
|
6
|
+
Compatible with Python 3.4.4 and Windows XP.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import json
|
|
11
|
+
import time
|
|
12
|
+
import tempfile
|
|
13
|
+
import shutil
|
|
14
|
+
import platform
|
|
15
|
+
from collections import OrderedDict
|
|
16
|
+
|
|
17
|
+
# Sensitive keys that should trigger extra confirmation
|
|
18
|
+
SENSITIVE_KEYS = ['password', 'token', 'secret', 'key', 'auth', 'credential', 'api_key', 'client_secret']
|
|
19
|
+
|
|
20
|
+
def resolve_config_path(default_path):
|
|
21
|
+
"""
|
|
22
|
+
Resolve the config file path using the same logic as MediLink_ConfigLoader.
|
|
23
|
+
This ensures the editor finds the config file in the same location as the main application.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
default_path: The default path to try first
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
str: The resolved config file path
|
|
30
|
+
"""
|
|
31
|
+
# If the default path exists, use it
|
|
32
|
+
if os.path.exists(default_path):
|
|
33
|
+
return default_path
|
|
34
|
+
|
|
35
|
+
# Try platform-specific fallbacks (same logic as MediLink_ConfigLoader)
|
|
36
|
+
if platform.system() == 'Windows' and platform.release() == 'XP':
|
|
37
|
+
# Use F: paths for Windows XP
|
|
38
|
+
xp_path = "F:\\Medibot\\json\\config.json"
|
|
39
|
+
if os.path.exists(xp_path):
|
|
40
|
+
return xp_path
|
|
41
|
+
elif platform.system() == 'Windows':
|
|
42
|
+
# Use current working directory for other versions of Windows
|
|
43
|
+
cwd_path = os.path.join(os.getcwd(), 'json', 'config.json')
|
|
44
|
+
if os.path.exists(cwd_path):
|
|
45
|
+
return cwd_path
|
|
46
|
+
|
|
47
|
+
# If no fallback paths exist, return the original default path
|
|
48
|
+
# (the editor will handle the missing file gracefully)
|
|
49
|
+
return default_path
|
|
50
|
+
|
|
51
|
+
def load_config_safe(config_path):
|
|
52
|
+
"""
|
|
53
|
+
Safely load config.json with fallback to empty structure.
|
|
54
|
+
Returns (config_dict, error_message)
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
if not os.path.exists(config_path):
|
|
58
|
+
return {}, "Config file not found: {}".format(config_path)
|
|
59
|
+
|
|
60
|
+
with open(config_path, 'r', encoding='utf-8') as f:
|
|
61
|
+
config = json.load(f, object_pairs_hook=OrderedDict)
|
|
62
|
+
|
|
63
|
+
if not isinstance(config, dict):
|
|
64
|
+
return {}, "Config file does not contain a valid JSON object"
|
|
65
|
+
|
|
66
|
+
return config, None
|
|
67
|
+
|
|
68
|
+
except json.JSONDecodeError as e:
|
|
69
|
+
return {}, "Invalid JSON in config file: {}".format(str(e))
|
|
70
|
+
except Exception as e:
|
|
71
|
+
return {}, "Error loading config file: {}".format(str(e))
|
|
72
|
+
|
|
73
|
+
def create_backup(config_path):
|
|
74
|
+
"""
|
|
75
|
+
Create a timestamped backup of the config file.
|
|
76
|
+
Returns backup_path or None if failed.
|
|
77
|
+
"""
|
|
78
|
+
try:
|
|
79
|
+
if not os.path.exists(config_path):
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
# Create timestamp in format YYYYMMDD_HHMMSS
|
|
83
|
+
timestamp = time.strftime("%Y%m%d_%H%M%S")
|
|
84
|
+
backup_path = "{}.bak.{}".format(config_path, timestamp)
|
|
85
|
+
|
|
86
|
+
shutil.copy2(config_path, backup_path)
|
|
87
|
+
return backup_path
|
|
88
|
+
|
|
89
|
+
except Exception as e:
|
|
90
|
+
print("Warning: Could not create backup: {}".format(str(e)))
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
def save_config_atomic(config_path, config_dict):
|
|
94
|
+
"""
|
|
95
|
+
Save config dictionary to file using atomic write (temp file + rename).
|
|
96
|
+
Compatible with Windows XP.
|
|
97
|
+
Returns (success, error_message)
|
|
98
|
+
"""
|
|
99
|
+
try:
|
|
100
|
+
# Create temp file in same directory as target
|
|
101
|
+
temp_dir = os.path.dirname(config_path)
|
|
102
|
+
temp_fd, temp_path = tempfile.mkstemp(suffix='.tmp', dir=temp_dir)
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
# Write to temp file
|
|
106
|
+
with os.fdopen(temp_fd, 'w', encoding='utf-8') as f:
|
|
107
|
+
json.dump(config_dict, f, ensure_ascii=False, indent=4, separators=(',', ': '))
|
|
108
|
+
|
|
109
|
+
# Atomic rename (works on Windows XP)
|
|
110
|
+
if os.name == 'nt': # Windows
|
|
111
|
+
if os.path.exists(config_path):
|
|
112
|
+
os.remove(config_path)
|
|
113
|
+
os.rename(temp_path, config_path)
|
|
114
|
+
else: # Unix-like
|
|
115
|
+
os.rename(temp_path, config_path)
|
|
116
|
+
|
|
117
|
+
return True, None
|
|
118
|
+
|
|
119
|
+
except Exception as e:
|
|
120
|
+
# Clean up temp file on error
|
|
121
|
+
try:
|
|
122
|
+
os.close(temp_fd)
|
|
123
|
+
if os.path.exists(temp_path):
|
|
124
|
+
os.remove(temp_path)
|
|
125
|
+
except:
|
|
126
|
+
pass
|
|
127
|
+
raise e
|
|
128
|
+
|
|
129
|
+
except Exception as e:
|
|
130
|
+
return False, "Error saving config: {}".format(str(e))
|
|
131
|
+
|
|
132
|
+
def validate_json_structure(data):
|
|
133
|
+
"""
|
|
134
|
+
Validate that data can be serialized to valid JSON.
|
|
135
|
+
Returns (is_valid, error_message)
|
|
136
|
+
"""
|
|
137
|
+
try:
|
|
138
|
+
json.dumps(data, ensure_ascii=False)
|
|
139
|
+
return True, None
|
|
140
|
+
except Exception as e:
|
|
141
|
+
return False, "Invalid JSON structure: {}".format(str(e))
|
|
142
|
+
|
|
143
|
+
def is_sensitive_key(key_name):
|
|
144
|
+
"""
|
|
145
|
+
Check if a key name appears to be sensitive.
|
|
146
|
+
"""
|
|
147
|
+
key_lower = key_name.lower()
|
|
148
|
+
return any(sensitive in key_lower for sensitive in SENSITIVE_KEYS)
|
|
149
|
+
|
|
150
|
+
def get_value_type(value):
|
|
151
|
+
"""
|
|
152
|
+
Determine the type of a JSON value for display purposes.
|
|
153
|
+
"""
|
|
154
|
+
if isinstance(value, str):
|
|
155
|
+
return "string"
|
|
156
|
+
elif isinstance(value, (int, float)):
|
|
157
|
+
return "number"
|
|
158
|
+
elif isinstance(value, bool):
|
|
159
|
+
return "boolean"
|
|
160
|
+
elif isinstance(value, list):
|
|
161
|
+
return "array"
|
|
162
|
+
elif isinstance(value, dict):
|
|
163
|
+
return "object"
|
|
164
|
+
else:
|
|
165
|
+
return "unknown"
|
|
166
|
+
|
|
167
|
+
def format_value_for_display(value, max_length=50):
|
|
168
|
+
"""
|
|
169
|
+
Format a value for display in the editor, truncating if too long.
|
|
170
|
+
"""
|
|
171
|
+
if isinstance(value, str):
|
|
172
|
+
if len(value) > max_length:
|
|
173
|
+
return '"{}..."'.format(value[:max_length-3])
|
|
174
|
+
else:
|
|
175
|
+
return '"{}"'.format(value)
|
|
176
|
+
elif isinstance(value, (int, float)):
|
|
177
|
+
return str(value)
|
|
178
|
+
elif isinstance(value, bool):
|
|
179
|
+
return "true" if value else "false"
|
|
180
|
+
elif isinstance(value, list):
|
|
181
|
+
return "[{} items]".format(len(value))
|
|
182
|
+
elif isinstance(value, dict):
|
|
183
|
+
return "{{{} keys}}".format(len(value))
|
|
184
|
+
else:
|
|
185
|
+
return str(value)
|
|
186
|
+
|
|
187
|
+
def clear_config_cache():
|
|
188
|
+
"""
|
|
189
|
+
Clear the MediLink_ConfigLoader cache after config changes.
|
|
190
|
+
"""
|
|
191
|
+
try:
|
|
192
|
+
# Import here to avoid issues if MediCafe not available
|
|
193
|
+
import sys
|
|
194
|
+
import os
|
|
195
|
+
|
|
196
|
+
# Add parent directory to path to find MediCafe
|
|
197
|
+
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
198
|
+
if parent_dir not in sys.path:
|
|
199
|
+
sys.path.insert(0, parent_dir)
|
|
200
|
+
|
|
201
|
+
from MediCafe.MediLink_ConfigLoader import clear_config_cache as clear_loader_cache
|
|
202
|
+
|
|
203
|
+
# Use the loader's own cache clearing function
|
|
204
|
+
clear_loader_cache()
|
|
205
|
+
|
|
206
|
+
return True, None
|
|
207
|
+
|
|
208
|
+
except Exception as e:
|
|
209
|
+
return False, "Warning: Could not clear config cache: {}".format(str(e))
|
|
210
|
+
|
|
211
|
+
def get_nested_value(config, path_list):
|
|
212
|
+
"""
|
|
213
|
+
Get a value from nested dictionary using path list.
|
|
214
|
+
Returns (value, found) where found is boolean.
|
|
215
|
+
"""
|
|
216
|
+
current = config
|
|
217
|
+
try:
|
|
218
|
+
for key in path_list:
|
|
219
|
+
current = current[key]
|
|
220
|
+
return current, True
|
|
221
|
+
except (KeyError, TypeError):
|
|
222
|
+
return None, False
|
|
223
|
+
|
|
224
|
+
def set_nested_value(config, path_list, value):
|
|
225
|
+
"""
|
|
226
|
+
Set a value in nested dictionary using path list.
|
|
227
|
+
Creates intermediate dictionaries if needed.
|
|
228
|
+
"""
|
|
229
|
+
current = config
|
|
230
|
+
for key in path_list[:-1]:
|
|
231
|
+
if key not in current or not isinstance(current[key], dict):
|
|
232
|
+
current[key] = OrderedDict()
|
|
233
|
+
current = current[key]
|
|
234
|
+
current[path_list[-1]] = value
|
|
235
|
+
|
|
236
|
+
def get_path_string(path_list):
|
|
237
|
+
"""
|
|
238
|
+
Convert path list to breadcrumb string.
|
|
239
|
+
"""
|
|
240
|
+
if not path_list:
|
|
241
|
+
return "config"
|
|
242
|
+
return "config > " + " > ".join(path_list)
|
|
243
|
+
|
|
244
|
+
def validate_key_name(key_name, existing_keys):
|
|
245
|
+
"""
|
|
246
|
+
Validate a new key name.
|
|
247
|
+
Returns (is_valid, error_message)
|
|
248
|
+
"""
|
|
249
|
+
if not key_name:
|
|
250
|
+
return False, "Key name cannot be empty"
|
|
251
|
+
|
|
252
|
+
if key_name in existing_keys:
|
|
253
|
+
return False, "Key '{}' already exists".format(key_name)
|
|
254
|
+
|
|
255
|
+
# Check for invalid characters that might break JSON
|
|
256
|
+
invalid_chars = ['"', '\\', '/', '\n', '\r', '\t']
|
|
257
|
+
for char in invalid_chars:
|
|
258
|
+
if char in key_name:
|
|
259
|
+
return False, "Key name contains invalid character: '{}'".format(char)
|
|
260
|
+
|
|
261
|
+
return True, None
|
|
262
|
+
|
|
263
|
+
def parse_value_input(value_str, value_type):
|
|
264
|
+
"""
|
|
265
|
+
Parse user input string into appropriate Python type.
|
|
266
|
+
Returns (parsed_value, error_message)
|
|
267
|
+
"""
|
|
268
|
+
try:
|
|
269
|
+
if value_type == "string":
|
|
270
|
+
# Remove surrounding quotes if present
|
|
271
|
+
if value_str.startswith('"') and value_str.endswith('"'):
|
|
272
|
+
value_str = value_str[1:-1]
|
|
273
|
+
return value_str, None
|
|
274
|
+
|
|
275
|
+
elif value_type == "number":
|
|
276
|
+
# Try int first, then float
|
|
277
|
+
if '.' in value_str:
|
|
278
|
+
return float(value_str), None
|
|
279
|
+
else:
|
|
280
|
+
return int(value_str), None
|
|
281
|
+
|
|
282
|
+
elif value_type == "boolean":
|
|
283
|
+
value_lower = value_str.lower()
|
|
284
|
+
if value_lower in ['true', 't', 'yes', 'y', '1']:
|
|
285
|
+
return True, None
|
|
286
|
+
elif value_lower in ['false', 'f', 'no', 'n', '0']:
|
|
287
|
+
return False, None
|
|
288
|
+
else:
|
|
289
|
+
return None, "Invalid boolean value. Use true/false, yes/no, or 1/0"
|
|
290
|
+
|
|
291
|
+
elif value_type == "array":
|
|
292
|
+
# Simple array parsing - comma separated values
|
|
293
|
+
if not value_str.strip():
|
|
294
|
+
return [], None
|
|
295
|
+
|
|
296
|
+
# Split by comma and strip whitespace
|
|
297
|
+
items = [item.strip() for item in value_str.split(',')]
|
|
298
|
+
return items, None
|
|
299
|
+
|
|
300
|
+
elif value_type == "object":
|
|
301
|
+
# For now, return empty object - user can add keys later
|
|
302
|
+
return OrderedDict(), None
|
|
303
|
+
|
|
304
|
+
else:
|
|
305
|
+
return None, "Unknown value type: {}".format(value_type)
|
|
306
|
+
|
|
307
|
+
except ValueError as e:
|
|
308
|
+
return None, "Invalid value format: {}".format(str(e))
|
|
309
|
+
except Exception as e:
|
|
310
|
+
return None, "Error parsing value: {}".format(str(e))
|
MediCafe/__init__.py
CHANGED
MediLink/MediLink_Gmail.py
CHANGED
|
@@ -458,10 +458,19 @@ def open_browser_with_executable(url, browser_path=None):
|
|
|
458
458
|
def initiate_link_retrieval(config):
|
|
459
459
|
log("Initiating browser via implicit GET.")
|
|
460
460
|
medi = extract_medilink_config(config)
|
|
461
|
-
|
|
461
|
+
dep_id = (medi.get('webapp_deployment_id', '') or '').strip()
|
|
462
|
+
if not dep_id:
|
|
463
|
+
log("webapp_deployment_id is empty. Please set it in config before continuing.")
|
|
464
|
+
shutdown_event.set()
|
|
465
|
+
return
|
|
466
|
+
url_get = "https://script.google.com/macros/s/{}/exec?action=get_link".format(dep_id)
|
|
467
|
+
try:
|
|
468
|
+
log("Opening GAS web app: {}".format(url_get))
|
|
469
|
+
except Exception:
|
|
470
|
+
pass
|
|
462
471
|
open_browser_with_executable(url_get)
|
|
463
472
|
log("Preparing POST call.")
|
|
464
|
-
url = "https://script.google.com/macros/s/{}/exec".format(
|
|
473
|
+
url = "https://script.google.com/macros/s/{}/exec".format(dep_id)
|
|
465
474
|
downloaded_emails = list(load_downloaded_emails())
|
|
466
475
|
payload = {"downloadedEmails": downloaded_emails}
|
|
467
476
|
access_token = get_access_token()
|
MediLink/__init__.py
CHANGED
MediLink/webapp.html
CHANGED
|
@@ -177,7 +177,13 @@
|
|
|
177
177
|
</main>
|
|
178
178
|
<script>
|
|
179
179
|
document.addEventListener("DOMContentLoaded", () => {
|
|
180
|
-
|
|
180
|
+
try {
|
|
181
|
+
var params = new URLSearchParams(window.location.search || '');
|
|
182
|
+
var actionParam = params.get('action');
|
|
183
|
+
console.log("Document ready, initializing flows... action=", actionParam);
|
|
184
|
+
} catch (e) {
|
|
185
|
+
console.log("Document ready, initializing flows...");
|
|
186
|
+
}
|
|
181
187
|
// initializeEmailListFlow();
|
|
182
188
|
initializeDocxEmailListFlow();
|
|
183
189
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
MediBot/MediBot.bat,sha256=
|
|
1
|
+
MediBot/MediBot.bat,sha256=z8LmLbqdi_CmpUSN5EBDFl29Xe9sgFBjCgewW4-Wq54,29380
|
|
2
2
|
MediBot/MediBot.py,sha256=ABSqWikb_c1VSuR4n8Vh5YfOqzDCi2jnnp3sg_xOYg0,51092
|
|
3
3
|
MediBot/MediBot_Charges.py,sha256=a28if_f_IoazIHiqlaFosFnfEgEoCwb9LQ6aOyk5-D0,10704
|
|
4
4
|
MediBot/MediBot_Crosswalk_Library.py,sha256=6LrpRx2UKVeH3TspS9LpR93iw5M7nTqN6IYpC-6PPGE,26060
|
|
@@ -12,8 +12,11 @@ MediBot/MediBot_dataformat_library.py,sha256=D46fdPtxcgfWTzaLBtSvjtozzZBNqNiODgu
|
|
|
12
12
|
MediBot/MediBot_debug.bat,sha256=F5Lfi3nFEEo4Ddx9EbX94u3fNAMgzMp3wsn-ULyASTM,6017
|
|
13
13
|
MediBot/MediBot_docx_decoder.py,sha256=9BSjV-kB90VHnqfL_5iX4zl5u0HcHvHuL7YNfx3gXpQ,33143
|
|
14
14
|
MediBot/MediBot_smart_import.py,sha256=Emvz7NwemHGCHvG5kZcUyXMcCheidbGKaPfOTg-YCEs,6684
|
|
15
|
-
MediBot/__init__.py,sha256=
|
|
15
|
+
MediBot/__init__.py,sha256=XbpIvNejZ4LpOh_pRbEyb46jxmqGfzyZAtyRTfAPK0k,3192
|
|
16
16
|
MediBot/clear_cache.bat,sha256=F6-VhETWw6xDdGWG2wUqvtXjCl3lY4sSUFqF90bM8-8,1860
|
|
17
|
+
MediBot/config_editor.bat,sha256=gwR_GP5a72vhrij6U8e1jH48xQDwLgLY3vEJI9KZtcs,2340
|
|
18
|
+
MediBot/config_editor.py,sha256=fwFMhHA7xmrHUW_V7XY7HfjWeY19BFje1Z-D0mK70zA,15788
|
|
19
|
+
MediBot/config_editor_helpers.py,sha256=7jsBg9NpDla09EuT0nvKgk4EmIEbyH8vma92lfk88uM,10510
|
|
17
20
|
MediBot/crash_diagnostic.bat,sha256=j8kUtyBg6NOWbXpeFuEqIRHOkVzgUrLOqO3FBMfNxTo,9268
|
|
18
21
|
MediBot/f_drive_diagnostic.bat,sha256=4572hZaiwZ5wVAarPcZJQxkOSTwAdDuT_X914noARak,6878
|
|
19
22
|
MediBot/full_debug_suite.bat,sha256=b3Euz-VbRaPE6tWmgpjApfv4_cB5-Rk8BLNlLrp2bpo,2223
|
|
@@ -22,7 +25,7 @@ MediBot/process_csvs.bat,sha256=3tI7h1z9eRj8rUUL4wJ7dy-Qrak20lRmpAPtGbUMbVQ,3489
|
|
|
22
25
|
MediBot/update_json.py,sha256=vvUF4mKCuaVly8MmoadDO59M231fCIInc0KI1EtDtPA,3704
|
|
23
26
|
MediBot/update_medicafe.py,sha256=G1lyvVOHYuho1d-TJQNN6qaB4HBWaJ2PpXqemBoPlRQ,17937
|
|
24
27
|
MediCafe/MediLink_ConfigLoader.py,sha256=heTbZ0ItwlpxqbAb0oV2dbTFRSTMnZyhBT9fS8nI0YU,12735
|
|
25
|
-
MediCafe/__init__.py,sha256=
|
|
28
|
+
MediCafe/__init__.py,sha256=R7IiwM-8skk7HPc2qpkE1U8pVyUt7IreovdKhSfYkMA,5721
|
|
26
29
|
MediCafe/__main__.py,sha256=NZMgsjuXYF39YS7mCWMjTO0vYf_0IAotQpNyUXfbahs,13233
|
|
27
30
|
MediCafe/api_core.py,sha256=wLAdRNZdmovKReXvzsmAgKrbYon4-wbJbGCyOm_C3AU,89896
|
|
28
31
|
MediCafe/api_factory.py,sha256=I5AeJoyu6m7oCrjc2OvVvO_4KSBRutTsR1riiWhTZV0,12086
|
|
@@ -51,7 +54,7 @@ MediLink/MediLink_Deductible.py,sha256=opGa5YQ6tfowurlf8xDWRtAtQMmoNYout0gYe3R5f
|
|
|
51
54
|
MediLink/MediLink_Deductible_Validator.py,sha256=x6tHJOi88TblUpDPSH6QhIdXXRgr3rXI7kYPVGZYCgU,24998
|
|
52
55
|
MediLink/MediLink_Display_Utils.py,sha256=MonsX6VPbdvqwY_V8sHUYrXCS0fMKc4toJvG0oyr-V4,24872
|
|
53
56
|
MediLink/MediLink_Down.py,sha256=s4_z-RaqHYanjwbQCl-OSkg4XIpcIQ2Q6jXa8-q_QXw,28111
|
|
54
|
-
MediLink/MediLink_Gmail.py,sha256=
|
|
57
|
+
MediLink/MediLink_Gmail.py,sha256=JuDyO8mckxwRtO8Jfty37lpbgTIA91x8LpEb6y9l_iU,31742
|
|
55
58
|
MediLink/MediLink_Mailer.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
56
59
|
MediLink/MediLink_Parser.py,sha256=eRVZ4ckZ5gDOrcvtCUZP3DOd3Djly66rCIk0aYXLz14,12567
|
|
57
60
|
MediLink/MediLink_PatientProcessor.py,sha256=9r2w4p45d30Tn0kbXL3j5574MYOehP83tDirNOw_Aek,19977
|
|
@@ -63,15 +66,15 @@ MediLink/MediLink_insurance_utils.py,sha256=g741Fj2K26cMy0JX5d_XavMw9LgkK6hjaUJY
|
|
|
63
66
|
MediLink/MediLink_main.py,sha256=1nmDtbJ9IfPwLnFhCZvDtBPSG_8gzj6LneuoV60l4IQ,26736
|
|
64
67
|
MediLink/MediLink_smart_import.py,sha256=ZUXvAkIA2Pk2uuyLZazKfKK8YGdkZt1VAeZo_ZSUyxk,9942
|
|
65
68
|
MediLink/Soumit_api.py,sha256=5JfOecK98ZC6NpZklZW2AkOzkjvrbYxpJpZNH3rFxDw,497
|
|
66
|
-
MediLink/__init__.py,sha256=
|
|
69
|
+
MediLink/__init__.py,sha256=OIl0UcNgWUpkZClnyh099Hq_sTH6TaddlcY7qpK66oM,3888
|
|
67
70
|
MediLink/gmail_http_utils.py,sha256=mYChIhkbA1oJaAJA-nY3XgHQY-H7zvZJUZPhUagomsI,4047
|
|
68
71
|
MediLink/gmail_oauth_utils.py,sha256=Ugr-DEqs4_RddRMSCJ_dbgA3TVeaxpbAor-dktcTIgY,3713
|
|
69
72
|
MediLink/openssl.cnf,sha256=76VdcGCykf0Typyiv8Wd1mMVKixrQ5RraG6HnfKFqTo,887
|
|
70
73
|
MediLink/test.py,sha256=DM_E8gEbhbVfTAm3wTMiNnK2GCD1e5eH6gwTk89QIc4,3116
|
|
71
|
-
MediLink/webapp.html,sha256=
|
|
72
|
-
medicafe-0.
|
|
73
|
-
medicafe-0.
|
|
74
|
-
medicafe-0.
|
|
75
|
-
medicafe-0.
|
|
76
|
-
medicafe-0.
|
|
77
|
-
medicafe-0.
|
|
74
|
+
MediLink/webapp.html,sha256=SSBuPCtdv7dWj_ZiMfkCj7LRIjma6YHVJMDHjoBIdyk,21547
|
|
75
|
+
medicafe-0.251030.0.dist-info/LICENSE,sha256=65lb-vVujdQK7uMH3RRJSMwUW-WMrMEsc5sOaUn2xUk,1096
|
|
76
|
+
medicafe-0.251030.0.dist-info/METADATA,sha256=oOPTKfF8Iw-0JSxfpB6tGqSJQpGuLTASjKi2k43ZQp4,3414
|
|
77
|
+
medicafe-0.251030.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
78
|
+
medicafe-0.251030.0.dist-info/entry_points.txt,sha256=m3RBUBjr-xRwEkKJ5W4a7NlqHZP_1rllGtjZnrRqKe8,52
|
|
79
|
+
medicafe-0.251030.0.dist-info/top_level.txt,sha256=U6-WBJ9RCEjyIs0BlzbQq_PwedCp_IV9n1616NNV5zA,26
|
|
80
|
+
medicafe-0.251030.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|