|
| 1 | +# SPDX-License-Identifier: PMPL-1.0-or-later |
| 2 | +# organize_edge_bookmarks.jl — Categorize bookmarks and write Edge Bookmarks JSON |
| 3 | +# Migrated from Python (organize_edge_bookmarks.py) |
| 4 | + |
| 5 | +using JSON3 |
| 6 | +using UUIDs |
| 7 | +using Dates |
| 8 | + |
| 9 | +const THEMES = Dict{String, Vector{String}}( |
| 10 | + "Tech & Programming" => ["github", "programming", "docker", "server", "code", "dev", "tech", "node", "python", "rust", "npm", "stack", "api", "web", "cloud", "aws", "google", "microsoft", "apple", "app", "software"], |
| 11 | + "Linux & OS" => ["fedora", "linux", "ubuntu", "debian", "kernel", "cli", "shell", "bash", "terminal", "desktop", "os", "system", "config"], |
| 12 | + "News & Finance" => ["news", "breaking", "msn", "trump", "politics", "economy", "market", "finance", "bank", "money", "stock", "crypto", "bitcoin", "business", "invest", "trading"], |
| 13 | + "Psychology & Growth" => ["psychology", "mental", "self", "growth", "happiness", "manipulation", "relationships", "advice", "life", "wellness", "mindset", "success", "productivity"], |
| 14 | + "Art, Culture & Design" => ["art", "artist", "design", "museum", "culture", "style", "creative", "photo", "music", "history", "philosophy", "books", "writing"], |
| 15 | + "Lifestyle & Health" => ["food", "cooking", "recipe", "health", "fitness", "yoga", "travel", "holiday", "home", "garden", "shopping", "amazon", "buy", "deal"], |
| 16 | + "Education & Career" => ["learn", "course", "study", "university", "school", "job", "career", "resume", "cv", "interview", "skills", "training"], |
| 17 | + "Media & Social" => ["youtube", "video", "tv", "movie", "entertainment", "facebook", "twitter", "x.com", "instagram", "reddit", "watch", "podcast"], |
| 18 | +) |
| 19 | + |
| 20 | +"""Chrome/Edge timestamp: microseconds since Jan 1, 1601.""" |
| 21 | +function chrome_timestamp(dt::DateTime)::String |
| 22 | + epoch_1601 = DateTime(1601, 1, 1) |
| 23 | + diff_seconds = Dates.value(dt - epoch_1601) / 1000 # ms → s |
| 24 | + return string(round(Int64, diff_seconds * 1_000_000)) |
| 25 | +end |
| 26 | + |
| 27 | +"""Categorize a bookmark line by matching keywords against themes.""" |
| 28 | +function categorize(line::AbstractString)::String |
| 29 | + line_lower = lowercase(line) |
| 30 | + for (theme, keywords) in THEMES |
| 31 | + if any(kw -> occursin(kw, line_lower), keywords) |
| 32 | + return theme |
| 33 | + end |
| 34 | + end |
| 35 | + return "Uncategorized" |
| 36 | +end |
| 37 | + |
| 38 | +function main() |
| 39 | + all_bookmarks = Dict{String, Vector{String}}() |
| 40 | + |
| 41 | + for path in ["/tmp/firefox_bookmarks.txt", "/tmp/edge_bookmarks.txt"] |
| 42 | + isfile(path) || continue |
| 43 | + for line in eachline(path) |
| 44 | + stripped = strip(line) |
| 45 | + isempty(stripped) && continue |
| 46 | + theme = categorize(stripped) |
| 47 | + push!(get!(all_bookmarks, theme, String[]), stripped) |
| 48 | + end |
| 49 | + end |
| 50 | + |
| 51 | + now_ts = chrome_timestamp(now()) |
| 52 | + current_id = Ref(4) |
| 53 | + |
| 54 | + function next_id() |
| 55 | + id = current_id[] |
| 56 | + current_id[] += 1 |
| 57 | + return string(id) |
| 58 | + end |
| 59 | + |
| 60 | + bookmark_bar_children = [] |
| 61 | + |
| 62 | + sorted_themes = sort(collect(filter(t -> t != "Uncategorized", keys(all_bookmarks)))) |
| 63 | + haskey(all_bookmarks, "Uncategorized") && push!(sorted_themes, "Uncategorized") |
| 64 | + |
| 65 | + for theme in sorted_themes |
| 66 | + items = sort(unique(all_bookmarks[theme])) |
| 67 | + children = [] |
| 68 | + for item in items |
| 69 | + parts = split(item, "|"; limit=2) |
| 70 | + if length(parts) == 2 |
| 71 | + push!(children, Dict( |
| 72 | + "date_added" => now_ts, |
| 73 | + "date_last_used" => "0", |
| 74 | + "guid" => string(uuid4()), |
| 75 | + "id" => next_id(), |
| 76 | + "name" => parts[1], |
| 77 | + "type" => "url", |
| 78 | + "url" => parts[2], |
| 79 | + )) |
| 80 | + end |
| 81 | + end |
| 82 | + push!(bookmark_bar_children, Dict( |
| 83 | + "date_added" => now_ts, |
| 84 | + "date_last_used" => "0", |
| 85 | + "guid" => string(uuid4()), |
| 86 | + "id" => next_id(), |
| 87 | + "name" => theme, |
| 88 | + "type" => "folder", |
| 89 | + "children" => children, |
| 90 | + )) |
| 91 | + end |
| 92 | + |
| 93 | + output = Dict( |
| 94 | + "checksum" => "", |
| 95 | + "roots" => Dict( |
| 96 | + "bookmark_bar" => Dict( |
| 97 | + "children" => bookmark_bar_children, |
| 98 | + "date_added" => now_ts, |
| 99 | + "date_last_used" => "0", |
| 100 | + "guid" => string(uuid4()), |
| 101 | + "id" => "1", |
| 102 | + "name" => "Favorites Bar", |
| 103 | + "type" => "folder", |
| 104 | + ), |
| 105 | + "other" => Dict("children" => [], "date_added" => "0", "date_last_used" => "0", "guid" => string(uuid4()), "id" => "2", "name" => "Other Favorites", "type" => "folder"), |
| 106 | + "synced" => Dict("children" => [], "date_added" => "0", "date_last_used" => "0", "guid" => string(uuid4()), "id" => "3", "name" => "Mobile Favorites", "type" => "folder"), |
| 107 | + ), |
| 108 | + "version" => 1, |
| 109 | + ) |
| 110 | + |
| 111 | + outpath = expanduser("~/.var/app/com.microsoft.EdgeDev/config/microsoft-edge-dev/Default/Bookmarks") |
| 112 | + open(outpath, "w") do f |
| 113 | + JSON3.pretty(f, output; allow_inf=true) |
| 114 | + end |
| 115 | + |
| 116 | + println("Success: Edge Bookmarks file overwritten.") |
| 117 | +end |
| 118 | + |
| 119 | +main() |
0 commit comments