miscreaders package

Supported readers:

Loop Habit Tracker

Loop Habit Tracker is an open-source Android app for tracking habits.

References

Note

Tested with version 2.2.1 of Loop Habit Tracker.

class miscreaders.loophabit.LoophabitDbReader(db_path: str | PathLike[Any])

Reader for SQLite database exported from Loop Habit Tracker

get_connection() Connection

Return read-only connection to the database

get_habit_df() DataFrame

Return dataframe with habit definition

See also

HabitType for meaning of the type column

Example

>>> from miscreaders.loophabit import LoophabitDbReader
>>> reader = LoophabitDbReader(path_to_db)
>>> reader.get_habit_df().head(2)
shape: (2, 18)
┌─────┬──────────┬───────┬─────────────┬───┬──────────────┬──────┬──────────┬──────────────────────┐
│ id  ┆ archived ┆ color ┆ description ┆ … ┆ target_value ┆ unit ┆ question ┆ uuid                 │
│ --- ┆ ---      ┆ ---   ┆ ---         ┆   ┆ ---          ┆ ---  ┆ ---      ┆ ---                  │
│ i64 ┆ i64      ┆ i64   ┆ str         ┆   ┆ f64          ┆ str  ┆ str      ┆ str                  │
╞═════╪══════════╪═══════╪═════════════╪═══╪══════════════╪══════╪══════════╪══════════════════════╡
│ 1   ┆ 0        ┆ 4     ┆             ┆ … ┆ 0.0          ┆      ┆          ┆ cwvnot6vdjvkwzu55s19 │
│     ┆          ┆       ┆             ┆   ┆              ┆      ┆          ┆ gbgdeer84i…          │
│ 2   ┆ 0        ┆ 0     ┆             ┆ … ┆ 0.0          ┆      ┆          ┆ owhf7kceexi8cghlbqks │
│     ┆          ┆       ┆             ┆   ┆              ┆      ┆          ┆ 687kj3w8j9…          │
└─────┴──────────┴───────┴─────────────┴───┴──────────────┴──────┴──────────┴──────────────────────┘
get_repetition_df() DataFrame

Return dataframe with daily data for habits

See also

EntryValue for meaning of the value column

Example

>>> from miscreaders.loophabit import LoophabitDbReader
>>> reader = LoophabitDbReader(path_to_db)
>>> reader.get_repetition_df()
shape: (10, 4)
┌──────┬────────────┬───────┬───────┐
│ name ┆ timestamp  ┆ value ┆ notes │
│ ---  ┆ ---        ┆ ---   ┆ ---   │
│ enum ┆ date       ┆ i64   ┆ str   │
╞══════╪════════════╪═══════╪═══════╡
│ a    ┆ 2014-02-21 ┆ 2     ┆       │
│ b    ┆ 2014-02-22 ┆ 2     ┆       │
│ c    ┆ 2014-02-23 ┆ 2     ┆       │
│ d    ┆ 2014-02-24 ┆ 2     ┆       │
│ a    ┆ 2014-02-25 ┆ 0     ┆       │
│ a    ┆ 2014-02-26 ┆ 2     ┆       │
│ a    ┆ 2014-02-27 ┆ 2     ┆       │
│ a    ┆ 2014-02-28 ┆ 2     ┆       │
│ a    ┆ 2014-03-01 ┆ 2     ┆       │
│ a    ┆ 2014-03-02 ┆ 2     ┆       │
└──────┴────────────┴───────┴───────┘
class miscreaders.loophabit.HabitType

Values in column type in habit table

YES_NO

Habit value has interpretation per EntryValue

NUMERICAL

Habit value has numerical interpretation

References

class miscreaders.loophabit.EntryValue

Values in column value in repetition table for habits of type HabitType.YES_NO

SKIP

Value indicating that the habit is not applicable for this timestamp.

YES_MANUAL

Value indicating that the user has performed the habit at this timestamp.

YES_AUTO

Value indicating that the user did not perform the habit, but they were not expected to, because of the frequency of the habit.

NO

Value indicating that the user did not perform the habit, even though they were expected to perform it.

UNKNOWN

Value indicating that no data is available for the given timestamp.

References

Moonwatch.rs

Moonwatch.rs is a privacy-oriented tool for tracking application usage on Windows and Linux.

References

Note

Tested with version 0.1.0 of Moonwatch.rs.

class miscreaders.moonwatch.MoonwatchLogReader(jsonl_path: str | PathLike[Any])

Reader for Moonwatch.rs log file

get_df() DataFrame

Return dataframe with log content

Example

>>> from miscreaders.moonwatch import MoonwatchLogReader
>>> reader = MoonwatchLogReader(path_to_jsonl)
>>> reader.get_df()
shape: (5, 8)
┌────────────┬────────────┬────────────┬───────────┬───────────┬───────────┬───────────┬───────────┐
│ type       ┆ time       ┆ duration   ┆ hostname  ┆ username  ┆ idle_for  ┆ process_p ┆ tags      │
│ ---        ┆ ---        ┆ ---        ┆ ---       ┆ ---       ┆ ---       ┆ ath       ┆ ---       │
│ enum       ┆ datetime[μ ┆ duration[μ ┆ str       ┆ str       ┆ duration[ ┆ ---       ┆ list[str] │
│            ┆ s]         ┆ s]         ┆           ┆           ┆ μs]       ┆ str       ┆           │
╞════════════╪════════════╪════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ ActiveWind ┆ 2025-04-02 ┆ 15s        ┆ test-host ┆ test-user ┆ 1m 22s    ┆ C:\test-p ┆ []        │
│ owEvent    ┆ 21:40:24.5 ┆            ┆ name      ┆           ┆           ┆ rogram.ex ┆           │
│            ┆ 43149      ┆            ┆           ┆           ┆           ┆ e         ┆           │
│ ActiveWind ┆ 2025-04-02 ┆ 15s        ┆ test-host ┆ test-user ┆ 3m 38s    ┆ C:\other- ┆ ["tag-a"] │
│ owEvent    ┆ 00:20:12.3 ┆            ┆ name      ┆           ┆           ┆ program.e ┆           │
│            ┆ 26975      ┆            ┆           ┆           ┆           ┆ xe        ┆           │
│ ActiveWind ┆ 2025-04-02 ┆ 15s        ┆ test-host ┆ test-user ┆ 3m 23s    ┆ C:\test-p ┆ []        │
│ owEvent    ┆ 00:19:57.3 ┆            ┆ name      ┆           ┆           ┆ rogram.ex ┆           │
│            ┆ 17296      ┆            ┆           ┆           ┆           ┆ e         ┆           │
│ ActiveWind ┆ 2025-04-02 ┆ 15s        ┆ test-host ┆ test-user ┆ 3m 8s     ┆ C:\test-p ┆ []        │
│ owEvent    ┆ 00:19:42.3 ┆            ┆ name      ┆           ┆           ┆ rogram.ex ┆           │
│            ┆ 14747      ┆            ┆           ┆           ┆           ┆ e         ┆           │
│ ActiveWind ┆ 2025-04-02 ┆ 15s        ┆ test-host ┆ test-user ┆ 2m 53s    ┆ C:\other- ┆ ["tag-a", │
│ owEvent    ┆ 00:19:27.3 ┆            ┆ name      ┆           ┆           ┆ program.e ┆ "tag-b"]  │
│            ┆ 06595      ┆            ┆           ┆           ┆           ┆ xe        ┆           │
└────────────┴────────────┴────────────┴───────────┴───────────┴───────────┴───────────┴───────────┘
class miscreaders.moonwatch.MoonwatchLogDirectoryReader(dir_path: str | PathLike[Any])

Helper class for batch reading of Moonwatch.rs logs

You can use this to read entire log directory (eg. ~/.moonwatch-rs/log) into one dataframe.

get_df(rechunk: bool = True) DataFrame

Return concatenated dataframe of all logs in directory

Parameters:

rechunk – Reallocate data into contiguous memory for better performance (on by default)

Example

>>> from miscreaders.moonwatch import MoonwatchLogDirectoryReader
>>> reader = MoonwatchLogDirectoryReader(path_to_dir)
>>> reader.get_df()
shape: (15, 8)
┌────────────┬────────────┬────────────┬───────────┬───────────┬───────────┬───────────┬───────────┐
│ type       ┆ time       ┆ duration   ┆ hostname  ┆ username  ┆ idle_for  ┆ process_p ┆ tags      │
│ ---        ┆ ---        ┆ ---        ┆ ---       ┆ ---       ┆ ---       ┆ ath       ┆ ---       │
│ enum       ┆ datetime[μ ┆ duration[μ ┆ str       ┆ str       ┆ duration[ ┆ ---       ┆ list[str] │
│            ┆ s]         ┆ s]         ┆           ┆           ┆ μs]       ┆ str       ┆           │
╞════════════╪════════════╪════════════╪═══════════╪═══════════╪═══════════╪═══════════╪═══════════╡
│ ActiveWind ┆ 2025-04-02 ┆ 15s        ┆ test-host ┆ test-user ┆ 57s       ┆ C:\test-p ┆ []        │
│ owEvent    ┆ 00:12:57.1 ┆            ┆ name      ┆           ┆           ┆ rogram.ex ┆           │
│            ┆ 11274      ┆            ┆           ┆           ┆           ┆ e         ┆           │
│ ActiveWind ┆ 2025-04-02 ┆ 15s        ┆ test-host ┆ test-user ┆ 42s       ┆ C:\test-p ┆ []        │
│ owEvent    ┆ 00:12:42.1 ┆            ┆ name      ┆           ┆           ┆ rogram.ex ┆           │
│            ┆ 10712      ┆            ┆           ┆           ┆           ┆ e         ┆           │
│ ActiveWind ┆ 2025-04-02 ┆ 15s        ┆ test-host ┆ test-user ┆ 27s       ┆ C:\test-p ┆ []        │
│ owEvent    ┆ 00:12:27.1 ┆            ┆ name      ┆           ┆           ┆ rogram.ex ┆           │
│            ┆ 08317      ┆            ┆           ┆           ┆           ┆ e         ┆           │
│ ActiveWind ┆ 2025-04-02 ┆ 15s        ┆ test-host ┆ test-user ┆ 12s       ┆ C:\test-p ┆ []        │
│ owEvent    ┆ 00:12:12.0 ┆            ┆ name      ┆           ┆           ┆ rogram.ex ┆           │
│            ┆ 97326      ┆            ┆           ┆           ┆           ┆ e         ┆           │
│ ActiveWind ┆ 2025-04-02 ┆ 15s        ┆ test-host ┆ test-user ┆ 0µs       ┆ C:\test-p ┆ []        │
│ owEvent    ┆ 00:11:57.0 ┆            ┆ name      ┆           ┆           ┆ rogram.ex ┆           │
│            ┆ 94459      ┆            ┆           ┆           ┆           ┆ e         ┆           │
│ …          ┆ …          ┆ …          ┆ …         ┆ …         ┆ …         ┆ …         ┆ …         │
│ ActiveWind ┆ 2025-04-02 ┆ 15s        ┆ test-host ┆ test-user ┆ 1m 22s    ┆ C:\test-p ┆ []        │
│ owEvent    ┆ 21:40:24.5 ┆            ┆ name      ┆           ┆           ┆ rogram.ex ┆           │
│            ┆ 43149      ┆            ┆           ┆           ┆           ┆ e         ┆           │
│ ActiveWind ┆ 2025-04-02 ┆ 15s        ┆ test-host ┆ test-user ┆ 3m 38s    ┆ C:\other- ┆ ["tag-a"] │
│ owEvent    ┆ 00:20:12.3 ┆            ┆ name      ┆           ┆           ┆ program.e ┆           │
│            ┆ 26975      ┆            ┆           ┆           ┆           ┆ xe        ┆           │
│ ActiveWind ┆ 2025-04-02 ┆ 15s        ┆ test-host ┆ test-user ┆ 3m 23s    ┆ C:\test-p ┆ []        │
│ owEvent    ┆ 00:19:57.3 ┆            ┆ name      ┆           ┆           ┆ rogram.ex ┆           │
│            ┆ 17296      ┆            ┆           ┆           ┆           ┆ e         ┆           │
│ ActiveWind ┆ 2025-04-02 ┆ 15s        ┆ test-host ┆ test-user ┆ 3m 8s     ┆ C:\test-p ┆ []        │
│ owEvent    ┆ 00:19:42.3 ┆            ┆ name      ┆           ┆           ┆ rogram.ex ┆           │
│            ┆ 14747      ┆            ┆           ┆           ┆           ┆ e         ┆           │
│ ActiveWind ┆ 2025-04-02 ┆ 15s        ┆ test-host ┆ test-user ┆ 2m 53s    ┆ C:\other- ┆ ["tag-a", │
│ owEvent    ┆ 00:19:27.3 ┆            ┆ name      ┆           ┆           ┆ program.e ┆ "tag-b"]  │
│            ┆ 06595      ┆            ┆           ┆           ┆           ┆ xe        ┆           │
└────────────┴────────────┴────────────┴───────────┴───────────┴───────────┴───────────┴───────────┘
iter_dfs() Iterator[DataFrame]

Iterate over dataframes from individual log files

StayFree

StayFree is a productivity app for mobile, desktop and web.

References

Note

Tested with version 18.8.4 of the Android app.

class miscreaders.stayfree.StayfreeXlsReader(xls_path: str | PathLike[Any])

Reader for StayFree XLS export file

This parser reads file that can be manually exported from the Android app (as of 18.8.4) by the slightly confusing way of: Settings > Storage > Export to CSV. Despite the menu mentioning CSV, the exported file is actually XLS (old-style Excel file).

See also

StayfreeBackupReader for a more readily automated way of getting the data.

get_device_unlocks_df() DataFrame

Return dataframe with device unlock counts

Example

>>> from miscreaders.stayfree import StayfreeXlsReader
>>> reader = StayfreeXlsReader(path_to_xls)
>>> reader.get_device_unlocks_df()
shape: (122, 2)
┌────────────┬───────┐
│ date       ┆ count │
│ ---        ┆ ---   │
│ date       ┆ i32   │
╞════════════╪═══════╡
│ 2023-11-10 ┆ 4     │
│ 2023-11-11 ┆ 26    │
│ 2023-11-12 ┆ 22    │
│ 2023-11-13 ┆ 19    │
│ 2023-11-14 ┆ 21    │
│ …          ┆ …     │
│ 2024-03-06 ┆ 39    │
│ 2024-03-07 ┆ 35    │
│ 2024-03-08 ┆ 26    │
│ 2024-03-09 ┆ 10    │
│ 2024-03-10 ┆ 0     │
└────────────┴───────┘
get_usage_count_df() DataFrame

Return dataframe with app open counts

Example

>>> from miscreaders.stayfree import StayfreeXlsReader
>>> reader = StayfreeXlsReader(path_to_xls)
>>> reader.get_usage_count_df()
shape: (6_222, 4)
┌────────────┬─────────────┬───────┬────────┐
│ date       ┆ app         ┆ count ┆ device │
│ ---        ┆ ---         ┆ ---   ┆ ---    │
│ date       ┆ str         ┆ i32   ┆ str    │
╞════════════╪═════════════╪═══════╪════════╡
│ 2023-11-10 ┆ addititious ┆ 0     ┆        │
│ 2023-11-11 ┆ addititious ┆ 0     ┆        │
│ 2023-11-12 ┆ addititious ┆ 0     ┆        │
│ 2023-11-13 ┆ addititious ┆ 0     ┆        │
│ 2023-11-14 ┆ addititious ┆ 1     ┆        │
│ …          ┆ …           ┆ …     ┆ …      │
│ 2024-03-06 ┆ xylometer   ┆ 3     ┆        │
│ 2024-03-07 ┆ xylometer   ┆ 0     ┆        │
│ 2024-03-08 ┆ xylometer   ┆ 2     ┆        │
│ 2024-03-09 ┆ xylometer   ┆ 0     ┆        │
│ 2024-03-10 ┆ xylometer   ┆ 0     ┆        │
└────────────┴─────────────┴───────┴────────┘
get_usage_time_df() DataFrame

Return dataframe with app usage time

Example

>>> from miscreaders.stayfree import StayfreeXlsReader
>>> reader = StayfreeXlsReader(path_to_xls)
>>> reader.get_usage_time_df()
shape: (6_222, 4)
┌────────────┬─────────────┬──────────────┬────────┐
│ date       ┆ app         ┆ duration     ┆ device │
│ ---        ┆ ---         ┆ ---          ┆ ---    │
│ date       ┆ str         ┆ duration[μs] ┆ str    │
╞════════════╪═════════════╪══════════════╪════════╡
│ 2023-11-10 ┆ addititious ┆ 0µs          ┆        │
│ 2023-11-11 ┆ addititious ┆ 0µs          ┆        │
│ 2023-11-12 ┆ addititious ┆ 0µs          ┆        │
│ 2023-11-13 ┆ addititious ┆ 0µs          ┆        │
│ 2023-11-14 ┆ addititious ┆ 6s           ┆        │
│ …          ┆ …           ┆ …            ┆ …      │
│ 2024-03-06 ┆ xylometer   ┆ 53m 8s       ┆        │
│ 2024-03-07 ┆ xylometer   ┆ 0µs          ┆        │
│ 2024-03-08 ┆ xylometer   ┆ 30m 54s      ┆        │
│ 2024-03-09 ┆ xylometer   ┆ 0µs          ┆        │
│ 2024-03-10 ┆ xylometer   ┆ 0µs          ┆        │
└────────────┴─────────────┴──────────────┴────────┘
static parse_duration(text: str) timedelta

Convert "1h 10m 26s" into datetime.timedelta

Example

>>> from miscreaders.stayfree import StayfreeXlsReader
>>> print(StayfreeXlsReader.parse_duration("1h 10m 26s"))
1:10:26
>>> print(StayfreeXlsReader.parse_duration("10m"))
0:10:00
>>> print(StayfreeXlsReader.parse_duration("10h 26s"))
10:00:26
class miscreaders.stayfree.StayfreeBackupReader(backup_path: str | PathLike[Any])

Reader for StayFree app data backup file on Google Drive

This parser can extract data from StayFree internal data backup that can be scheduled or triggered manually at Settings > Storage > Backup & Restore. The backup gets stored as application-specific data on your Google Drive.

The problem with this is that there is no blessed way of accessing such data (other than deleting them) for the user; it is only meant to be accessed by the app. For a slightly cursed way of getting access, see https://github.com/albertored/google-drive-app-data.

See also

StayfreeXlsReader for reader of the official file export format.

get_connection(db_name: str) Generator[Connection, None, None]

Return read-only connection to the database extracted from the backup archive to temporary directory

get_usage_time_df(lookup_app_name: bool = True) DataFrame

Return dataframe with app usage time

Parameters:

lookup_app_name – Translate package names (eg. "com.google.android.youtube") into human-readable app names (eg. "YouTube").

Example

>>> from miscreaders.stayfree import StayfreeBackupReader
>>> reader = StayfreeBackupReader("stayfree-dailybackup_PhoneName.backup-xxxxxx")
>>> reader.get_usage_time_df()
shape: (6_222, 4)
┌────────────┬─────────────┬──────────────┐
│ date       ┆ app         ┆ duration     │
│ ---        ┆ ---         ┆ ---          │
│ date       ┆ str         ┆ duration[μs] │
╞════════════╪═════════════╪══════════════╡
│ 2023-11-10 ┆ addititious ┆ 0µs          │
│ 2023-11-11 ┆ addititious ┆ 0µs          │
│ 2023-11-12 ┆ addititious ┆ 0µs          │
│ 2023-11-13 ┆ addititious ┆ 0µs          │
│ 2023-11-14 ┆ addititious ┆ 6s           │
│ …          ┆ …           ┆ …            │
│ 2024-03-06 ┆ xylometer   ┆ 53m 8s       │
│ 2024-03-07 ┆ xylometer   ┆ 0µs          │
│ 2024-03-08 ┆ xylometer   ┆ 30m 54s      │
│ 2024-03-09 ┆ xylometer   ┆ 0µs          │
│ 2024-03-10 ┆ xylometer   ┆ 0µs          │
└────────────┴─────────────┴──────────────┘