├── README ├── test.rb └── OperaChain.rb /README: -------------------------------------------------------------------------------- 1 | Opera Chain 2 | =========== 3 | 4 | Ruby library to access Opera Link 5 | 6 | Example 7 | ======= 8 | 9 | Example 1: 10 | Connects to Opera Link, and displays a nicely formatted 11 | tree of bookmarks. 12 | 13 | require "OperaChain" 14 | 15 | chain = OperaChain.new("user","pass") 16 | puts chain 17 | 18 | Example 2: 19 | Adds a new bookmark. 20 | 21 | chain.root.add("Github", "http://github.com", "Github: Social Coding") 22 | 23 | Example 3: 24 | Edit a bookmark. 25 | 26 | chain.root.bookmarks[0].title = "Schmeabay.com" 27 | 28 | Example 4: 29 | Delete a bookmark. 30 | 31 | chain.root.bookmarks[0].delete 32 | -------------------------------------------------------------------------------- /test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | require 'rubygems' 4 | gem 'minitest' 5 | require 'minitest/unit' 6 | 7 | require 'OperaChain.rb' 8 | 9 | MiniTest::Unit.autorun 10 | 11 | class OperaChainTest < MiniTest::Unit::TestCase 12 | 13 | def setup 14 | unless $chain 15 | puts "Username: "; username = gets.strip 16 | puts "Password: "; password = gets.strip 17 | 18 | $chain = OperaChain.new(username, password) 19 | end 20 | 21 | @chain = $chain 22 | end 23 | 24 | def teardown 25 | end 26 | 27 | def test_login_fail 28 | end 29 | 30 | def test_not_nil 31 | refute_nil(@chain) 32 | end 33 | 34 | def test_add_bookmark_html 35 | end 36 | 37 | def test_add_bookmark 38 | title = "Test add_bookmark #{rand(1000)}" 39 | url = "http://opera.com" 40 | description = "Opera\nIt's a very nice browser, but needs more plugin support." 41 | 42 | directory = @chain.bookmarks[-1].parent 43 | sleep 3 44 | directory.add(title, url, description) 45 | 46 | # Check if the bookmark is added correctly 47 | bookmark = directory.bookmarks.find do |bookmark| 48 | bookmark.title == title 49 | end 50 | 51 | refute_nil(bookmark) 52 | assert_equal(url, bookmark.url) 53 | # This does not actually exist: bookmark.description 54 | # assert_equal(description, bookmark.description) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /OperaChain.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'mechanize' 3 | 4 | class OperaChain 5 | attr_reader :username, :password, :root 6 | def initialize(username, password) 7 | @username = username 8 | @password = password 9 | 10 | @agent = WWW::Mechanize.new do |agent| 11 | agent.redirect_ok = true 12 | agent.follow_meta_refresh = true 13 | agent.user_agent = "Opera Chain v0.0" 14 | end 15 | 16 | page = @agent.get('http://link.opera.com/') 17 | form = page.forms.first 18 | form.user = @username 19 | form.passwd = @password 20 | sleep 3 21 | page = @agent.submit form 22 | 23 | raise "login failed" if page.title =~ /Login Failed/i 24 | 25 | sleep 3 26 | page = @agent.click page.link_with(:text => 'Bookmarks') 27 | 28 | @root = OperaDirectory.new(@agent, page.search('//ul[@id="folders"]/li').first) # Root node, "Bookmarks" 29 | end 30 | 31 | def bookmarks # Copy of OperaDirectory.all_bookmarks. Maybe make a link? 32 | if block_given? 33 | @root.all_bookmarks do |bookmark| 34 | yield bookmark 35 | end 36 | else 37 | @root.all_bookmarks 38 | end 39 | end 40 | 41 | def to_s 42 | @root.to_s 43 | end 44 | end 45 | 46 | 47 | 48 | # BookmarkTag == Directory 49 | class OperaDirectory 50 | attr_reader :title, :parent, :children 51 | 52 | def initialize(agent, node, parent = nil) 53 | @agent = agent 54 | @parent = parent 55 | @link = node.search("./a").first 56 | @add_link = nil 57 | @title = @link.content.strip 58 | @children = [] 59 | @bookmarks = [] 60 | @cached = false 61 | 62 | # Generate subdirectories 63 | node.search('./ul/li').each do |sub_node| 64 | @children << OperaDirectory.new(@agent, sub_node, self) 65 | end 66 | end 67 | 68 | def refresh 69 | @cached = false 70 | end 71 | 72 | def bookmarks 73 | cache unless @cached 74 | 75 | @bookmarks 76 | end 77 | 78 | def all_bookmarks 79 | if block_given? 80 | bookmarks.each do |bookmark| 81 | yield bookmark 82 | end 83 | 84 | children.each do |subnode| 85 | subnode.all_bookmarks do |bookmark| 86 | yield bookmark 87 | end 88 | end 89 | else 90 | result = [] 91 | all_bookmarks do |bookmark| 92 | result << bookmark 93 | end 94 | 95 | result 96 | end 97 | end 98 | 99 | def add(title, link, description = "") 100 | cache unless @cached 101 | 102 | page = @agent.click @add_link 103 | form = page.forms.first 104 | form["name"] = title # Manual selector because name() is a function 105 | form.link = link 106 | form.desc = description 107 | sleep 3 108 | page = form.click_button 109 | cache(page) 110 | end 111 | 112 | def to_s(level = 0) 113 | result = "#{' ' * level}#{@title}\n" 114 | bookmarks.each do |bookmark| 115 | result << "#{' ' * (level)} - #{bookmark.title}\n" 116 | end 117 | @children.each do |subdir| 118 | result << subdir.to_s(level+1) 119 | end 120 | 121 | result 122 | end 123 | 124 | 125 | private 126 | 127 | # Loads the bookmarks in this directory 128 | def cache(page = nil) 129 | @cached = true 130 | @bookmarks.clear 131 | page ||= @agent.click @link 132 | 133 | unless page.search("//img[@src='http://my.opera.com/community/graphics/link/screens.jpg']").empty? 134 | # Opera burped and gave us the wrong page. Retry, with throttling. 135 | puts "Burp!" 136 | sleep 3 137 | cache 138 | else 139 | @add_link = page.link_with(:text => "Add a bookmark in this folder") 140 | page.search('//li[@class="xfolkentry"]').each do |node| 141 | @bookmarks << OperaBookmark.new(@agent, node, self) 142 | end 143 | end 144 | end 145 | 146 | end 147 | 148 | class OperaBookmark 149 | attr_reader :title, :url, :parent 150 | 151 | def initialize(agent, node, parent) 152 | @title = node.search("span").first.content 153 | @url = node.search("a[@class='taggedlink']").first["href"] 154 | @edit_link = node.search("a[@class='ed']").first 155 | @delete_link = node.search("a[@class='delete']").first 156 | 157 | @parent = parent 158 | @agent = agent 159 | end 160 | 161 | def title=(title) 162 | form = edit_form 163 | form["name"] = title 164 | form.click_button # Todo: check exceptions 165 | @title = title 166 | 167 | self 168 | end 169 | 170 | def url=(url) 171 | form = edit_form 172 | form.link = url 173 | form.click_button 174 | @url = url 175 | 176 | self 177 | end 178 | 179 | def delete 180 | @agent.click @delete_link 181 | @parent.children.delete(self) 182 | self 183 | end 184 | 185 | private 186 | 187 | def edit_form 188 | edit_page = @agent.click @edit_link 189 | edit_page.forms.first 190 | end 191 | end 192 | --------------------------------------------------------------------------------