Migrating BlinkList Bookmarks and Powermarks Bookmarks to del.icio.us
UPDATE: It seems that Blinklist’s JSON export doesn’t work any more, and delicious now requires OAuth authentication for new accounts (which rubilicious doesn’t support). So I created another script to transform Blinklist’s CSV format to HTML bookmark format which you can import to delicious.
It’s done. I was suffering constant problems with BlinkList and I decided to move to del.icio.us. I also decided to rescue the old powermarks 3.5 bookmarks from the oblivion and import them to del.icio.us too.
BlinkList gives you the option of exporting your bookmarks in JSON format via the Options →Export links. (here is the link)
So grab the json file and save it somewhere in your disk.
Then use the following script to load all the bookmarks in the json file to del.icio.us. Just change the filename and username and password to suit your needs.
#!/usr/bin/ruby
require "rubygems"
require "rubilicious"
require "json"
require "date"
require "time"
def getTime(item)
dateadd = item['dateadd']
return Time.at(dateadd) unless dateadd == false
return Time.now
end
def getIsPrivate(item)
isprivate = item['private']
return "checked"==isprivate
end
def getTags(item)
item['tag'].gsub(' ', '_').gsub(',',' ')
end
json_string = File.new("blinklist20080710.json").read
result = JSON.parse(json_string)
r = Rubilicious.new('your_delicious_username','your_delicious_password')
i=0
for item in result do
i += 1
puts "#{i}: #{item['url']}"
#next if i < 3229
r.add(item['url'],item['name'],item['description'], getTags(item), getTime(item), true, getIsPrivate(item))
end
puts "ended"
If the script fails in the middle of the import don’t worry. just uncomment the “#next if i < 3229” and change the 3229 to the last bookmark id that was loaded. Rerun the script and it will skip all bookmarks up to the one you write there.
Loading the old powermark file into del.icio.us is a little more complex. You will need two files:
1) state_pattern.rb (from maurice codik’s blog). I’m copying it here for completeness sake
<span class='line'>#!/usr/bin/ruby
</span><span class='line'>
</span><span class='line'># Copyright (C) 2006 Maurice Codik - maurice.codik@gmail.com
</span><span class='line'>#
</span><span class='line'># Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
</span><span class='line'># associated documentation files (the "Software"), to deal in the Software without restriction,
</span><span class='line'># including without limitation the rights to use, copy, modify, merge, publish, distribute,
</span><span class='line'># sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
</span><span class='line'># furnished to do so, subject to the following conditions:
</span><span class='line'>#
</span><span class='line'># The above copyright notice and this permission notice shall be included in all copies or substantial
</span><span class='line'># portions of the Software.
</span><span class='line'>#
</span><span class='line'># THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
</span><span class='line'># LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
</span><span class='line'># IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
</span><span class='line'># WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
</span><span class='line'># SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
</span><span class='line'>
</span><span class='line'>
</span><span class='line'># an example:
</span><span class='line'>#
</span><span class='line'># class Connection
</span><span class='line'># include StatePattern
</span><span class='line'># state :initial do # you always need a state named initial. this is where you begin.
</span><span class='line'># def connect
</span><span class='line'># puts "connected"
</span><span class='line'># # move to state :connected. all other args to transition_to are passed to the new state's constructor
</span><span class='line'># transition_to :connected, "hello from initial state"
</span><span class='line'># end
</span><span class='line'># def disconnect
</span><span class='line'># puts "not connected yet"
</span><span class='line'># end
</span><span class='line'># end
</span><span class='line'># state :connected do
</span><span class='line'># def initialize(msg)
</span><span class='line'># puts "initialize got msg: #{msg}"
</span><span class='line'># end
</span><span class='line'># def connect
</span><span class='line'># puts "already connected"
</span><span class='line'># end
</span><span class='line'># def disconnect
</span><span class='line'># puts "disconnecting"
</span><span class='line'># transition_to :initial
</span><span class='line'># end
</span><span class='line'># end
</span><span class='line'># def reset
</span><span class='line'># puts "reseting outside a state"
</span><span class='line'># # you can also change the state from outside of the state objects
</span><span class='line'># transition_to :initial
</span><span class='line'># end
</span><span class='line'># end
</span><span class='line'>
</span><span class='line'># how's it work:
</span><span class='line'># Each call to state defines a new subclass of Connection that is stored in a hash.
</span><span class='line'># Then, a call to transition_to instantiates one of these subclasses and sets it to the be the active state.
</span><span class='line'># Method calls to Connection are delegated to the active state object via method_missing.
</span><span class='line'>
</span><span class='line'>module StatePattern
</span><span class='line'> class UnknownStateException < Exception
</span><span class='line'> end
</span><span class='line'>
</span><span class='line'> def StatePattern.included(mod)
</span><span class='line'> mod.extend StatePattern::ClassMethods
</span><span class='line'> end
</span><span class='line'>
</span><span class='line'> module ClassMethods
</span><span class='line'> attr_reader :state_classes
</span><span class='line'> def state(state_name, &block)
</span><span class='line'> @state_classes ||= {}
</span><span class='line'>
</span><span class='line'> new_klass = Class.new(self, &block)
</span><span class='line'> new_klass.class_eval do
</span><span class='line'> alias_method :__old_init, :initialize
</span><span class='line'> def initialize(context, *args, &block)
</span><span class='line'> @context = context
</span><span class='line'> __old_init(*args, &block)
</span><span class='line'> end
</span><span class='line'> end
</span><span class='line'>
</span><span class='line'> @state_classes[state_name] = new_klass
</span><span class='line'> end
</span><span class='line'> end
</span><span class='line'>
</span><span class='line'> attr_accessor :current_state, :current_state_obj
</span><span class='line'>
</span><span class='line'> def transition_to(state_name, *args, &block)
</span><span class='line'> new_context = @context || self
</span><span class='line'>
</span><span class='line'> klass = new_context.class.state_classes[state_name]
</span><span class='line'> if klass
</span><span class='line'> new_context.current_state = state_name
</span><span class='line'> new_context.current_state_obj = klass.new(new_context, *args, &block)
</span><span class='line'> else
</span><span class='line'> raise UnknownStateException, "tried to transition to unknown state, #{state_name}"
</span><span class='line'> end
</span><span class='line'> end
</span><span class='line'>
</span><span class='line'> def method_missing(method, *args, &block)
</span><span class='line'> unless @current_state_obj
</span><span class='line'> transition_to :initial
</span><span class='line'> end
</span><span class='line'> if @current_state_obj
</span><span class='line'> @current_state_obj.send(method, *args, &block)
</span><span class='line'> else
</span><span class='line'> super
</span><span class='line'> end
</span><span class='line'> end
</span><span class='line'>
</span><span class='line'>end</span>
2) The script that parses the powermarks file and load it to del.icio.us
<span class='line'>#!/usr/bin/ruby
</span><span class='line'>require "rubygems"
</span><span class='line'>require "rubilicious"
</span><span class='line'>require "json"
</span><span class='line'>require "date"
</span><span class='line'>require "time"
</span><span class='line'>require "state_pattern"
</span><span class='line'>
</span><span class='line'>class Parser
</span><span class='line'> include StatePattern
</span><span class='line'>
</span><span class='line'> attr_accessor :name, :url,:desc,:tags ,:date , :r
</span><span class='line'>
</span><span class='line'> state :initial do
</span><span class='line'> def parse(line)
</span><span class='line'> #puts "initial: #{line}"
</span><span class='line'> if line =~ /<a href="(.*)">(.*)< \/a>/
</span><span class='line'> @context.name = $2
</span><span class='line'> @context.url = $1
</span><span class='line'> transition_to :read_keywords
</span><span class='line'> end
</span><span class='line'> end
</span><span class='line'> end
</span><span class='line'> state :read_keywords do
</span><span class='line'> def parse(line)
</span><span class='line'> if line =~ /<!--keywords-->(.*)$/
</span><span class='line'> @context.tags = $1.chomp
</span><span class='line'> transition_to :read_keywords2
</span><span class='line'> end
</span><span class='line'> end
</span><span class='line'> end
</span><span class='line'>
</span><span class='line'> state :read_keywords2 do
</span><span class='line'> def parse(line)
</span><span class='line'> #puts "read_keywords2: #{line}"
</span><span class='line'> if line =~ /<!--/
</span><span class='line'> if line =~ /^<!--desc-->/
</span><span class='line'> transition_to :read_desc
</span><span class='line'> @context.current_state_obj.parse(line)
</span><span class='line'> end
</span><span class='line'> if line =~ /^<!--mdata/
</span><span class='line'> transition_to :read_metadata
</span><span class='line'> @context.current_state_obj.parse(line)
</span><span class='line'> end
</span><span class='line'> return
</span><span class='line'> end
</span><span class='line'> @context.tags += " " + line.chomp
</span><span class='line'> end
</span><span class='line'> end
</span><span class='line'>
</span><span class='line'> state :read_desc do
</span><span class='line'> def parse(line)
</span><span class='line'> if line =~ /<!--/
</span><span class='line'> if line =~ /<!--desc-->(.*)/
</span><span class='line'> @context.desc = $1.chomp
</span><span class='line'> else
</span><span class='line'> #puts "not desc"
</span><span class='line'> if line =~ /<!--mdata/
</span><span class='line'> transition_to :read_metadata
</span><span class='line'> @context.current_state_obj.parse(line)
</span><span class='line'> else
</span><span class='line'> raise "dont know how to parse this in this state #{line}"
</span><span class='line'> end
</span><span class='line'> return
</span><span class='line'> end
</span><span class='line'> else
</span><span class='line'> @context.desc += " " + line.chomp
</span><span class='line'> end
</span><span class='line'> end
</span><span class='line'> end
</span><span class='line'>
</span><span class='line'> state :read_metadata do
</span><span class='line'> def parse(line)
</span><span class='line'> @context.date = $1.hex if line =~ /<!--mdata=\[\w+\]\[([0-9A-F]+)\]\[([0-9A-F]+)\]\[([0-9A-F]+)\]/
</span><span class='line'> @context.date = Time.now.to_i if @context.date < 0
</span><span class='line'>
</span><span class='line'>
</span><span class='line'> puts "=============================="
</span><span class='line'> puts "name: #{@context.name}"
</span><span class='line'> puts "url: #{@context.url}"
</span><span class='line'> puts "tags: #{@context.tags}"
</span><span class='line'> puts "date: #{Time.at(@context.date)}"
</span><span class='line'> puts "desc: #{@context.desc}" unless @context.desc.nil?
</span><span class='line'> puts "=============================="
</span><span class='line'> @context.r.add(@context.url,@context.name,@context.desc, @context.tags, Time.at(@context.date), false, true)
</span><span class='line'> @context.name = @context.url = @context.tags = @context.date = @context.desc = nil
</span><span class='line'>
</span><span class='line'> transition_to :initial
</span><span class='line'> end
</span><span class='line'> end
</span><span class='line'>
</span><span class='line'>end
</span><span class='line'>
</span><span class='line'>
</span><span class='line'>r = Rubilicious.new('your_delicious_username','your_password')
</span><span class='line'>p = Parser.new
</span><span class='line'>p.r = r;
</span><span class='line'>i = 0
</span><span class='line'>File.new("pm3520070703.htm").each { |line|
</span><span class='line'> puts i;
</span><span class='line'> i += 1
</span><span class='line'> #next unless i >2261
</span><span class='line'> p.parse(line);
</span><span class='line'>}
</span><span class='line'>
</span><span class='line'>puts "ended"</span>
(This script will add all the links as private. If you don’t want that behaviour just modify the last parameter in “@context.r.add(@context.url,@context.name,@context.desc, @context.tags, Time.at(@context.date), false, true)” to “false”.)
Again, if the script fails in the middle of the import don’t worry. just uncomment the “#next unless i > 2261” and change the 2261 to the line number where you want to resume parsing the powermarks file. Rerun the script and it will skip all previous lines.
Hope it helps anybody that it’s trying to escape from Blinklist and/or Powermarks. I successfully imported 3299 blinklist bookmarks and 4000 powermarks bookmarks (a lot of dupes though). By the way, the first script will replace any previous bookmark with the same url and the second script will not. That’s the way I wanted it but of course you can change it. The parameter before the last one in the call to add is the one that control the “replace”. (see add documentation).