Skip to content

Instantly share code, notes, and snippets.

@v9n
Created June 8, 2025 21:50
Show Gist options
  • Save v9n/7453a635acc2d7a84a28619b1e2ede7c to your computer and use it in GitHub Desktop.
Save v9n/7453a635acc2d7a84a28619b1e2ede7c to your computer and use it in GitHub Desktop.
Simple script to import Pocket Export to Linkding
  1. Export Pocket archive. Extract them into a directory. There will be a few csv file
  2. Install LINKDING
  3. Get the LINKDING API and TOKEN. Update script
  4. Get ruby dependencies with gem install httpx
  5. Run ruby import.rb
require 'csv'
require 'json'
require 'httpx' # Require the httpx gem
# --- Configuration ---
LINKDING_API_URL = '<linkding url>/api/bookmarks/' # Replace with your Linkding API URL
LINKDING_API_TOKEN = '<linkding api token>' # Replace with your Linkding API token
POCKET_CSV_DIR = File.expand_path('<path-to-pocket-scv-directory>') # Replace with the path to your Pocket CSV directory
# --- Script Logic ---
def import_bookmark(bookmark_data)
headers = {
'Content-Type' => 'application/json',
'Authorization' => "Token #{LINKDING_API_TOKEN}"
}
begin
response = HTTPX.post(LINKDING_API_URL, json: bookmark_data, headers: headers)
if response.status == 201
puts "Successfully imported: #{bookmark_data[:url]}"
else
puts "Failed to import #{bookmark_data[:url]}. Status: #{response.status}, Body: #{response.body.to_s}"
end
rescue HTTPX::Error => e
puts "HTTPX error importing #{bookmark_data[:url]}: #{e.message}"
rescue StandardError => e
puts "General error importing #{bookmark_data[:url]}: #{e.message}"
end
end
def process_pocket_csv(csv_file_path)
puts "Processing CSV file: #{csv_file_path}"
CSV.foreach(csv_file_path, headers: true) do |row|
title = row['title']
url = row['url']
time_added = row['time_added'] # This is a Unix timestamp
tags = row['tags']
status = row['status']
# Skip invalid rows (e.g., if URL is missing or malformed)
next if url.nil? || url.strip.empty? || !url.start_with?('http')
# Prepare data for Linkding API
bookmark = {
url: url,
title: title.to_s.empty? ? url : title, # Use URL as title if title is empty
description: '', # Pocket export doesn't seem to have descriptions, so leave empty
is_archived: status == 'read', # Linkding uses 'is_archived' for read status
tag_names: tags.to_s.split(',').map(&:strip).reject(&:empty?) # Split tags by comma and clean up
}
# Convert Unix timestamp to ISO 8601 format for Linkding's `added_at`
begin
bookmark[:added_at] = Time.at(time_added.to_i).iso8601
rescue ArgumentError => e
puts "Warning: Could not parse timestamp '#{time_added}' for URL: #{url}. Skipping 'added_at'. Error: #{e.message}"
bookmark.delete(:added_at) # Remove if invalid
end
import_bookmark(bookmark)
sleep(0.1) # Be kind to the API, add a small delay
end
end
# --- Main Execution ---
puts "Starting Pocket to Linkding import..."
Dir.glob(File.join(POCKET_CSV_DIR, 'part_*.csv')).sort.each do |csv_file|
process_pocket_csv(csv_file)
end
puts "Import process finished."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment