initial commit
This commit is contained in:
commit
ff8091fe98
6 changed files with 3895 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
2256
Cargo.lock
generated
Normal file
2256
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
18
Cargo.toml
Normal file
18
Cargo.toml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "readlater-bot"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
env_logger = "0.11"
|
||||
log = "0.4"
|
||||
rand = "0.8"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tempfile = "3"
|
||||
teloxide = { version = "0.12", features = ["macros", "auto-send"] }
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] }
|
||||
toml = "0.8"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
78
SPEC.md
Normal file
78
SPEC.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
Scope
|
||||
- Build a Telegram bot (Rust) packaged via Nix flake with a NixOS module and a package.
|
||||
- Manage two Obsidian markdown files: /Users/thegeneralist/obsidian/10 Read Later.md and /Users/thegeneralist/obsidian/20 Finished Reading.md.
|
||||
- Single-user bot (Telegram user ID 5311210922).
|
||||
- No tags, no URL parsing, treat all inputs as raw text.
|
||||
|
||||
Files and entry boundaries
|
||||
- Entries are defined by lines beginning with - at column 1.
|
||||
- An entry starts at a - line and continues until the next - line or EOF.
|
||||
- Any text before the first - line is a preamble header and must be preserved unchanged.
|
||||
- New entries must be written with - prefix.
|
||||
- Multi-line entries are allowed; subsequent lines are written as-is (no indentation).
|
||||
- Line endings normalized to LF (\n), ensure a trailing newline at EOF.
|
||||
- UTF-8 encoding.
|
||||
|
||||
Read Later behavior
|
||||
- New items are prepended (inserted immediately after any preamble).
|
||||
- Deduping: exact full-block match (entire entry text). If identical block exists, skip add and inform user.
|
||||
- Add acknowledgment: send Saved. and auto-delete after 5s.
|
||||
|
||||
Finished Reading behavior
|
||||
- Mark Finished moves an entry: remove from Read Later, prepend to Finished (no separators).
|
||||
- Acknowledge with Moved and auto-delete after 5s.
|
||||
- Undo window: 30 minutes, persists across restarts.
|
||||
|
||||
Delete behavior
|
||||
- Delete requires two sequential confirmations via inline buttons.
|
||||
- Confirmation buttons expire after 5 minutes.
|
||||
- After delete, offer Undo for 30 minutes.
|
||||
|
||||
Listing /list UX
|
||||
- /list command required; also provide /start and /help.
|
||||
- Initial chooser shows Top/Bottom/Random with counts (Top/Bottom buttons include count).
|
||||
- Top/Bottom peek shows 3 entries at a time with previews (two-line preview).
|
||||
- Under peek: buttons 1, 2, 3 to select an item, plus Prev, Next, Back, Random.
|
||||
- Selecting an item shows full entry text with actions: Mark Finished, Delete, Back.
|
||||
- Back from selected item returns to the same peek view.
|
||||
- Random picks from all Read Later entries, avoids repeats per /list session.
|
||||
|
||||
Multi-item messages
|
||||
- If the incoming message contains the delimiter token ---, treat it as a multi-item input.
|
||||
- Split by the token wherever it appears (not just on a line by itself).
|
||||
- Show an interactive picker that lets you choose which items to add; include Add selected and Cancel.
|
||||
|
||||
Errors and retries
|
||||
- On write failure: retry 3 times immediately.
|
||||
- If still failing: enqueue the operation on disk and notify user.
|
||||
- Background retry interval: 30 seconds.
|
||||
- Error messages are not auto-deleted.
|
||||
|
||||
State storage
|
||||
- No hidden markers and no sidecar index.
|
||||
- A configurable data_dir stores queue and undo logs.
|
||||
|
||||
Security
|
||||
- Ignore messages from unauthorized users silently.
|
||||
- Auth by Telegram user ID.
|
||||
|
||||
Config
|
||||
- TOML config file, path provided via NixOS module option.
|
||||
- Required TOML fields:
|
||||
- token (Telegram bot token)
|
||||
- user_id (Telegram user ID)
|
||||
- read_later_path (absolute path)
|
||||
- finished_path (absolute path)
|
||||
- data_dir (absolute path)
|
||||
- retry_interval_seconds (default 30, configurable)
|
||||
|
||||
Implementation
|
||||
- Rust bot (teloxide or equivalent).
|
||||
- Full-file rewrite on changes, with atomic write (write temp + rename).
|
||||
- Serialize writes (single queue).
|
||||
- Minimal logging: errors and counters only.
|
||||
|
||||
Nix flake outputs
|
||||
- packages.<system>.default builds the bot binary.
|
||||
- nixosModules.default provides the NixOS service with configFile option.
|
||||
- Service runs as a systemd unit; uses the TOML config path.
|
||||
71
flake.nix
Normal file
71
flake.nix
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
{
|
||||
description = "Read Later Telegram bot";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
flake-utils,
|
||||
}:
|
||||
let
|
||||
packageFor =
|
||||
system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
in
|
||||
pkgs.rustPlatform.buildRustPackage {
|
||||
pname = "readlater-bot";
|
||||
version = "0.1.0";
|
||||
src = self;
|
||||
cargoLock.lockFile = ./Cargo.lock;
|
||||
};
|
||||
in
|
||||
flake-utils.lib.eachDefaultSystem (system: {
|
||||
packages.default = packageFor system;
|
||||
})
|
||||
// {
|
||||
nixosModules.default =
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.services.readlater-bot;
|
||||
in
|
||||
{
|
||||
options.services.readlater-bot = {
|
||||
enable = lib.mkEnableOption "Read Later Telegram bot";
|
||||
package = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
default = self.packages.${pkgs.system}.default;
|
||||
description = "Package providing the bot binary.";
|
||||
};
|
||||
configFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = "Path to TOML config file.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
systemd.services.readlater-bot = {
|
||||
description = "Read Later Telegram bot";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/readlater-bot --config ${cfg.configFile}";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 5;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
1471
src/main.rs
Normal file
1471
src/main.rs
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue