drunken-chunk 1.0.0

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.
@@ -0,0 +1,245 @@
1
+ import os
2
+ import sqlite3
3
+ from datetime import datetime
4
+
5
+ DB_DIR = os.path.expanduser("~/.drunken_chunk")
6
+ DB_PATH = os.environ.get("DRUNKEN_CHUNK_DB_PATH", os.path.join(DB_DIR, "drunken_chunk.db"))
7
+
8
+ def get_connection():
9
+ """Initializes the database directory and returns a sqlite3 connection."""
10
+ db_dir = os.path.dirname(DB_PATH)
11
+ if db_dir:
12
+ os.makedirs(db_dir, exist_ok=True)
13
+ conn = sqlite3.connect(DB_PATH)
14
+ conn.row_factory = sqlite3.Row
15
+ return conn
16
+
17
+ def init_db():
18
+ """Creates tables if they do not exist."""
19
+ conn = get_connection()
20
+ cursor = conn.cursor()
21
+
22
+ # Profile Table
23
+ cursor.execute("""
24
+ CREATE TABLE IF NOT EXISTS profile (
25
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
26
+ name TEXT NOT NULL,
27
+ weight REAL NOT NULL, -- In kg
28
+ height REAL NOT NULL, -- In cm
29
+ wake_time TEXT NOT NULL, -- Format: HH:MM
30
+ sleep_time TEXT NOT NULL, -- Format: HH:MM
31
+ reminder_interval INTEGER NOT NULL, -- In minutes
32
+ custom_target INTEGER, -- In ml (optional override)
33
+ joined_date TEXT NOT NULL
34
+ )
35
+ """)
36
+
37
+ # Water Intake Log Table
38
+ cursor.execute("""
39
+ CREATE TABLE IF NOT EXISTS water_log (
40
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
41
+ timestamp TEXT NOT NULL, -- Format: YYYY-MM-DD HH:MM:SS
42
+ amount INTEGER NOT NULL, -- In ml
43
+ type TEXT NOT NULL -- 'direct' or name of shortcut
44
+ )
45
+ """)
46
+
47
+ # Shortcuts Table
48
+ cursor.execute("""
49
+ CREATE TABLE IF NOT EXISTS shortcuts (
50
+ name TEXT PRIMARY KEY,
51
+ amount INTEGER NOT NULL -- In ml
52
+ )
53
+ """)
54
+
55
+ # Reminder Daemon State Table
56
+ cursor.execute("""
57
+ CREATE TABLE IF NOT EXISTS reminder_state (
58
+ key TEXT PRIMARY KEY,
59
+ value TEXT NOT NULL
60
+ )
61
+ """)
62
+
63
+ # Insert default active state for reminder if not present
64
+ cursor.execute("INSERT OR IGNORE INTO reminder_state (key, value) VALUES ('is_active', '0')")
65
+ cursor.execute("INSERT OR IGNORE INTO reminder_state (key, value) VALUES ('last_notified', '')")
66
+
67
+ # Add default shortcuts if table is empty
68
+ cursor.execute("SELECT COUNT(*) FROM shortcuts")
69
+ if cursor.fetchone()[0] == 0:
70
+ default_shortcuts = [
71
+ ("glass", 250),
72
+ ("cup", 150),
73
+ ("bottle", 750),
74
+ ("flask", 500)
75
+ ]
76
+ cursor.executemany("INSERT INTO shortcuts (name, amount) VALUES (?, ?)", default_shortcuts)
77
+
78
+ conn.commit()
79
+ conn.close()
80
+
81
+ # ---------------- PROFILE OPERATIONS ----------------
82
+
83
+ def get_profile():
84
+ """Gets the user profile. Returns dict or None."""
85
+ conn = get_connection()
86
+ cursor = conn.cursor()
87
+ cursor.execute("SELECT * FROM profile ORDER BY id DESC LIMIT 1")
88
+ row = cursor.fetchone()
89
+ conn.close()
90
+ return dict(row) if row else None
91
+
92
+ def save_profile(name, weight, height, wake_time, sleep_time, reminder_interval, custom_target=None):
93
+ """Saves or updates user profile."""
94
+ conn = get_connection()
95
+ cursor = conn.cursor()
96
+ # Check if there is already a profile
97
+ cursor.execute("SELECT id FROM profile LIMIT 1")
98
+ row = cursor.fetchone()
99
+
100
+ now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
101
+ if row:
102
+ profile_id = row['id']
103
+ cursor.execute("""
104
+ UPDATE profile
105
+ SET name = ?, weight = ?, height = ?, wake_time = ?, sleep_time = ?,
106
+ reminder_interval = ?, custom_target = ?
107
+ WHERE id = ?
108
+ """, (name, weight, height, wake_time, sleep_time, reminder_interval, custom_target, profile_id))
109
+ else:
110
+ cursor.execute("""
111
+ INSERT INTO profile (name, weight, height, wake_time, sleep_time, reminder_interval, custom_target, joined_date)
112
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
113
+ """, (name, weight, height, wake_time, sleep_time, reminder_interval, custom_target, now_str))
114
+
115
+ conn.commit()
116
+ conn.close()
117
+
118
+ # ---------------- LOGGING OPERATIONS ----------------
119
+
120
+ def log_water(amount, drink_type="direct", timestamp=None):
121
+ """Logs water consumption at a specific time (defaults to now)."""
122
+ if not timestamp:
123
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
124
+ conn = get_connection()
125
+ cursor = conn.cursor()
126
+ cursor.execute("""
127
+ INSERT INTO water_log (timestamp, amount, type)
128
+ VALUES (?, ?, ?)
129
+ """, (timestamp, amount, drink_type))
130
+ conn.commit()
131
+ conn.close()
132
+
133
+ def delete_log(log_id):
134
+ """Deletes a water log entry by its ID."""
135
+ conn = get_connection()
136
+ cursor = conn.cursor()
137
+ cursor.execute("DELETE FROM water_log WHERE id = ?", (log_id,))
138
+ conn.commit()
139
+ conn.close()
140
+
141
+ def get_today_logs():
142
+ """Retrieves all water logs for today."""
143
+ today = datetime.now().strftime("%Y-%m-%d")
144
+ conn = get_connection()
145
+ cursor = conn.cursor()
146
+ cursor.execute("""
147
+ SELECT * FROM water_log
148
+ WHERE timestamp LIKE ?
149
+ ORDER BY timestamp ASC
150
+ """, (f"{today}%",))
151
+ rows = cursor.fetchall()
152
+ conn.close()
153
+ return [dict(r) for r in rows]
154
+
155
+ def get_history_logs(days=7):
156
+ """Retrieves water logs for the last N days."""
157
+ conn = get_connection()
158
+ cursor = conn.cursor()
159
+ cursor.execute("""
160
+ SELECT * FROM water_log
161
+ WHERE timestamp >= datetime('now', ?)
162
+ ORDER BY timestamp DESC
163
+ """, (f"-{days} days",))
164
+ rows = cursor.fetchall()
165
+ conn.close()
166
+ return [dict(r) for r in rows]
167
+
168
+ def get_all_logs():
169
+ """Retrieves all logs in the database."""
170
+ conn = get_connection()
171
+ cursor = conn.cursor()
172
+ cursor.execute("SELECT * FROM water_log ORDER BY timestamp DESC")
173
+ rows = cursor.fetchall()
174
+ conn.close()
175
+ return [dict(r) for r in rows]
176
+
177
+ # ---------------- SHORTCUT OPERATIONS ----------------
178
+
179
+ def get_shortcuts():
180
+ """Retrieves all shortcuts."""
181
+ conn = get_connection()
182
+ cursor = conn.cursor()
183
+ cursor.execute("SELECT * FROM shortcuts ORDER BY amount ASC")
184
+ rows = cursor.fetchall()
185
+ conn.close()
186
+ return {r['name']: r['amount'] for r in rows}
187
+
188
+ def save_shortcut(name, amount):
189
+ """Saves or updates a shortcut."""
190
+ conn = get_connection()
191
+ cursor = conn.cursor()
192
+ cursor.execute("""
193
+ INSERT INTO shortcuts (name, amount)
194
+ VALUES (?, ?)
195
+ ON CONFLICT(name) DO UPDATE SET amount = excluded.amount
196
+ """, (name.lower(), amount))
197
+ conn.commit()
198
+ conn.close()
199
+
200
+ def delete_shortcut(name):
201
+ """Deletes a shortcut."""
202
+ conn = get_connection()
203
+ cursor = conn.cursor()
204
+ cursor.execute("DELETE FROM shortcuts WHERE name = ?", (name.lower(),))
205
+ conn.commit()
206
+ conn.close()
207
+
208
+ # ---------------- DAEMON STATE OPERATIONS ----------------
209
+
210
+ def set_daemon_state(key, value):
211
+ """Sets a configuration state for the daemon."""
212
+ conn = get_connection()
213
+ cursor = conn.cursor()
214
+ cursor.execute("""
215
+ INSERT INTO reminder_state (key, value)
216
+ VALUES (?, ?)
217
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value
218
+ """, (key, str(value)))
219
+ conn.commit()
220
+ conn.close()
221
+
222
+ def get_daemon_state(key, default=None):
223
+ """Retrieves a configuration state for the daemon."""
224
+ conn = get_connection()
225
+ cursor = conn.cursor()
226
+ cursor.execute("SELECT value FROM reminder_state WHERE key = ?", (key,))
227
+ row = cursor.fetchone()
228
+ conn.close()
229
+ return row['value'] if row else default
230
+
231
+ def undo_last_log():
232
+ """Deletes the most recent water log entry. Returns dict of deleted entry or None."""
233
+ conn = get_connection()
234
+ cursor = conn.cursor()
235
+ cursor.execute("SELECT * FROM water_log ORDER BY id DESC LIMIT 1")
236
+ row = cursor.fetchone()
237
+ if row:
238
+ log_dict = dict(row)
239
+ cursor.execute("DELETE FROM water_log WHERE id = ?", (log_dict['id'],))
240
+ conn.commit()
241
+ conn.close()
242
+ return log_dict
243
+ conn.close()
244
+ return None
245
+
@@ -0,0 +1,232 @@
1
+ import math
2
+ import random
3
+ from .utils import Colors, colorize
4
+
5
+ def calculate_bmi(weight_kg, height_cm):
6
+ """Calculates BMI (Body Mass Index)."""
7
+ if height_cm <= 0:
8
+ return 0
9
+ height_m = height_cm / 100.0
10
+ return weight_kg / (height_m * height_m)
11
+
12
+ def calculate_recommended_intake(weight_kg, height_cm):
13
+ """
14
+ Calculates recommended daily water intake in ml.
15
+ Standard baseline: 35 ml per kg of body weight.
16
+ Adjustments: High BMI bodies require slightly more water for metabolic activity.
17
+ """
18
+ if weight_kg <= 0 or height_cm <= 0:
19
+ return 2000 # default fallback
20
+
21
+ baseline = weight_kg * 35 # in ml
22
+
23
+ # BMI adjustment
24
+ bmi = calculate_bmi(weight_kg, height_cm)
25
+ if bmi > 25:
26
+ # Increase target by 1% per BMI unit above 25, capped at 15%
27
+ adjustment_factor = min(1.15, 1.0 + (bmi - 25) * 0.01)
28
+ recommended = baseline * adjustment_factor
29
+ elif bmi < 18.5 and bmi > 0:
30
+ # Decrease slightly but not below baseline * 0.9
31
+ adjustment_factor = max(0.9, 1.0 - (18.5 - bmi) * 0.01)
32
+ recommended = baseline * adjustment_factor
33
+ else:
34
+ recommended = baseline
35
+
36
+ # Round to nearest 50ml
37
+ return int(math.ceil(recommended / 50.0)) * 50
38
+
39
+ def get_bmi_category(bmi):
40
+ if bmi < 18.5:
41
+ return "Underweight", Colors.YELLOW
42
+ elif 18.5 <= bmi < 25:
43
+ return "Normal weight", Colors.GREEN
44
+ elif 25 <= bmi < 30:
45
+ return "Overweight", Colors.YELLOW
46
+ else:
47
+ return "Obese", Colors.RED
48
+
49
+ def get_hydration_level(consumed_ml, target_ml):
50
+ """
51
+ Determines hydration level and gives a status string with emojis.
52
+ Returns: (level_name, color, description_text)
53
+ """
54
+ if target_ml <= 0:
55
+ return "Unknown", Colors.GRAY, "Set up your target first."
56
+
57
+ ratio = consumed_ml / target_ml
58
+
59
+ if ratio == 0:
60
+ return "Dry Soil [DRY]", Colors.RED, "No water recorded yet today. Drink up!"
61
+ elif ratio < 0.25:
62
+ return "Dehydrated Cactus [CRITICAL]", Colors.RED, "Very low hydration! Your body is in emergency drought mode."
63
+ elif ratio < 0.50:
64
+ return "Thirsty Camel [LOW]", Colors.YELLOW, "Below average hydration. Time to fill up your hump."
65
+ elif ratio < 0.75:
66
+ return "Wandering Nomad [MODERATE]", Colors.CYAN, "Decent progress, but you are still wandering in the hydration desert."
67
+ elif ratio < 1.00:
68
+ return "Hydrated Dolphin [HYDRATED]", Colors.GREEN, "Excellent hydration! You are swimming smoothly and feeling alert."
69
+ else:
70
+ return "Poseidon / Water God [OPTIMAL]", Colors.MAGENTA, "Perfect hydration! You have achieved peak fluid flow."
71
+
72
+ def get_profile_insights(profile, consumed_today):
73
+ """Generates detailed body and hydration insights."""
74
+ weight = profile['weight']
75
+ height = profile['height']
76
+ bmi = calculate_bmi(weight, height)
77
+ bmi_cat, bmi_color = get_bmi_category(bmi)
78
+
79
+ target = profile['custom_target'] if profile['custom_target'] else calculate_recommended_intake(weight, height)
80
+ ratio = consumed_today / target
81
+
82
+ level_name, level_color, level_desc = get_hydration_level(consumed_today, target)
83
+
84
+ insights = []
85
+ insights.append(f"• Your BMI is {colorize(f'{bmi:.1f}', Colors.BOLD)} ({colorize(bmi_cat, bmi_color)}).")
86
+
87
+ # Calculate metabolic demand info
88
+ if bmi > 25:
89
+ insights.append("• Higher body mass increases metabolic demands; your target has been scaled up accordingly.")
90
+ elif bmi < 18.5:
91
+ insights.append("• Lower body mass reduces base water requirement slightly, but don't compromise on regular sips.")
92
+ else:
93
+ insights.append("• Your body mass indexes in the normal range. Standard metabolic demand applies.")
94
+
95
+ # Hydration level specific recommendations
96
+ if ratio < 0.5:
97
+ insights.append(f"• {colorize('WARNING:', Colors.RED + Colors.BOLD)} Low hydration leads to fatigue, headaches, and low focus.")
98
+ insights.append("• Action item: Set a shorter reminder interval or make sure your shortcuts are logged.")
99
+ elif ratio < 1.0:
100
+ insights.append("• You are on the right track! Keeping a steady flow prevents afternoon crashes.")
101
+ else:
102
+ insights.append(f"• {colorize('CONGRATS!', Colors.GREEN + Colors.BOLD)} You've met your daily target. Extra consumption will be flushed out, keep listening to your body.")
103
+
104
+ return {
105
+ "target": target,
106
+ "bmi": bmi,
107
+ "bmi_category": bmi_cat,
108
+ "bmi_color": bmi_color,
109
+ "level_name": level_name,
110
+ "level_color": level_color,
111
+ "level_desc": level_desc,
112
+ "ratio": ratio,
113
+ "insights": insights
114
+ }
115
+
116
+ def get_situational_art_and_quote(ratio):
117
+ """Returns a tuple of (ascii_art_str, funny_quote_str) based on hydration ratio."""
118
+ if ratio == 0:
119
+ art = r"""
120
+ ╔═════════════════╗
121
+ ║ :( SYSTEM ║
122
+ ║ ║
123
+ ║ thirst.sys ║
124
+ ║ has crashed ║
125
+ ║ ║
126
+ ║ ERR: 0x00H2O ║
127
+ ╚═════════════════╝
128
+ """
129
+ quotes = [
130
+ "Warning: You are literally 1HP bro XD. Drink some water immediately before you despawn!",
131
+ "Your kidneys are crying: 'Lag! Lag! I am at 1HP!' Go drink some water before you disconnect.",
132
+ "You are at 1HP, out of mana, and your organs are throwing 500 Internal Server Errors. DRINK WATER!",
133
+ "Keyboard warrior down! You are literally at 1HP bro. Throw a health potion down your throat.",
134
+ "System check: Dehydration level 100%. Please plug in your water cable.",
135
+ "Your kidney is running a garbage collection loop. Give it some water to clear the heap."
136
+ ]
137
+ quote = random.choice(quotes)
138
+ elif ratio < 0.25:
139
+ art = r"""
140
+ _______
141
+ .'_______'.
142
+ / X X \
143
+ | ^ |
144
+ | ----- |
145
+ '._________.'
146
+ [ GAME OVER ]
147
+ [ 0 CREDITS ]
148
+ """
149
+ quotes = [
150
+ "Low health alert! 15HP remaining. Spiky, dry, and lagging hard. Recharge your shield cell!",
151
+ "Danger: Shield down, health critical. If a bug attacks you now, you're dead. Sip some water.",
152
+ "Your cells are begging for a heal. Current status: lagging in the lobby with 15HP.",
153
+ "You are drier than a poorly documented legacy codebase. Drink up to restore some frames.",
154
+ "Organ performance degraded. Latency rising. Sip some water to avoid thermal throttling.",
155
+ "Your skin cells are looking like dried raisins. Help them out, drink a glass."
156
+ ]
157
+ quote = random.choice(quotes)
158
+ elif ratio < 0.50:
159
+ art = r"""
160
+ (\(\
161
+ (>_<) "pls send h2o"
162
+ ( )(")(")
163
+ ─────────────
164
+ // TODO: drink
165
+ // FIXME: NOW
166
+ """
167
+ quotes = [
168
+ "Half health! Camel mode activated. Storing water in your hump? biological storage failing!",
169
+ "40HP remaining. Your compilation times are starting to slow down. Lubricate the gears!",
170
+ "A wild dehydration monster appeared. You are holding your ground at 40HP, but barely.",
171
+ "Your humps are getting flat. Storing code snippets instead of water? Go grab a flask.",
172
+ "Halfway to the safe zone. Your brain is running on low power mode. Take a gulp.",
173
+ "Biological buffer is half empty. Grab a glass to keep the pipeline flowing."
174
+ ]
175
+ quote = random.choice(quotes)
176
+ elif ratio < 0.75:
177
+ art = r"""
178
+ (\(\
179
+ (-.-) "still alive..."
180
+ ( )(")(")
181
+ ─────────────
182
+ > running...
183
+ H2O : MEDIUM
184
+ """
185
+ quotes = [
186
+ "60% HP. Desert nomad status. You've escaped absolute drought, but you're not safe yet. Take a gulp.",
187
+ "60HP. Decent performance, but your brain buffer is half empty. Clear the cache with a glass.",
188
+ "You are in the mid-game. Neither dehydrated nor fully hydrated. Take a sip to secure the win.",
189
+ "Organ systems running at 60% capacity. You're writing code, but it's probably missing tests. Drink water!",
190
+ "Consistency check: Passing. Keep this steady flow to prevent the afternoon crash."
191
+ ]
192
+ quote = random.choice(quotes)
193
+ elif ratio < 1.00:
194
+ art = r"""
195
+ .──────────────────.
196
+ │ $ make hydrate │
197
+ │ [█████████░░] │
198
+ │ ✓ BUILD: OK │
199
+ │ ✓ 0 warnings │
200
+ │ ✓ still alive │
201
+ '──────────────────'
202
+ """
203
+ quotes = [
204
+ "Smooth swimming! 85HP. High focus, fast reflexes. You are gliding through code like a dolphin.",
205
+ "85HP. Excellent latency. Your brain-computer interface is fully lubricated. Keep it going!",
206
+ "Organ performance: Optimal. You're close to achieving the God Buff. One more sip!",
207
+ "Gliding through issues and pull requests. Hydration ratio: Excellent. You're styling on them.",
208
+ "Your organs are running in liquid-cooled mode. Peak performance detected.",
209
+ "You are in the zone. Clean focus, hydrated joints. Keep it up and hit the goal."
210
+ ]
211
+ quote = random.choice(quotes)
212
+ else:
213
+ art = r"""
214
+ ⚡ ⚡ ⚡
215
+ .───────.
216
+ / [MAX] \
217
+ | BUFFED!! |
218
+ \_________/
219
+ >> GOD MODE ON
220
+ """
221
+ quotes = [
222
+ "MAX HEALTH achieved! Transcended. Water deity status. The fluid gods bow to you!",
223
+ "100HP. Full shield. God Mode activated. You can compile C++ with your bare hands now.",
224
+ "You are literally composed of 99% pure hydration. Even your code is running in liquid cooling.",
225
+ "Water God status. Your kidneys have reached Nirvana. Go code something impossible!",
226
+ "Target unlocked. Hydration database fully synced. You are peak human right now."
227
+ ]
228
+ quote = random.choice(quotes)
229
+
230
+ return art.strip("\n"), quote
231
+
232
+