Ruben Laguna’s blog

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 you have to use the script below to load the bookmars into del.icio.us but first make sure that you have ruby or jruby, rubygems, json-jruby or json-ruby, jruby-openssl and rubilicious installed.

If you use jruby you can install everything in the following way:

jruby -S gem install json-jruby  jruby-openssl rubilicious-0.2.0.gem

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
<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 &lt; 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
<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  =~ /&lt;a href="(.*)">(.*)&lt; \/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 =~ /&lt;!--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 =~ /&lt;!--/
</span><span class='line'>        if line =~ /^&lt;!--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 =~ /^&lt;!--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 =~ /&lt;!--/
</span><span class='line'>        if line =~ /&lt;!--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 =~ /&lt;!--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 =~ /&lt;!--mdata=\[\w+\]\[([0-9A-F]+)\]\[([0-9A-F]+)\]\[([0-9A-F]+)\]/
</span><span class='line'>      @context.date = Time.now.to_i if @context.date &lt; 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).

Comments

Copyright © 2015 - Ruben Laguna - Powered by Octopress