mingle 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +28 -0
- data/app/assets/javascripts/mingle/application.js +13 -0
- data/app/assets/stylesheets/mingle/application.css +15 -0
- data/app/controllers/mingle/application_controller.rb +4 -0
- data/app/helpers/mingle/application_helper.rb +4 -0
- data/app/jobs/mingle/facebook/fetch.rb +9 -0
- data/app/jobs/mingle/instagram/fetch.rb +10 -0
- data/app/jobs/mingle/twitter/fetch.rb +10 -0
- data/app/models/mingle.rb +5 -0
- data/app/models/mingle/facebook.rb +82 -0
- data/app/models/mingle/facebook/configuration.rb +8 -0
- data/app/models/mingle/facebook/post.rb +28 -0
- data/app/models/mingle/hashtag.rb +31 -0
- data/app/models/mingle/hashtagging.rb +4 -0
- data/app/models/mingle/instagram.rb +36 -0
- data/app/models/mingle/instagram/photo.rb +20 -0
- data/app/models/mingle/twitter.rb +48 -0
- data/app/models/mingle/twitter/tweet.rb +18 -0
- data/app/views/layouts/mingle/application.html.erb +14 -0
- data/config/initializers/facebook.rb +3 -0
- data/config/initializers/instagram.rb +3 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20140222105907_create_mingle_hashtags.rb +9 -0
- data/db/migrate/20140222105956_create_mingle_hashtaggings.rb +13 -0
- data/db/migrate/20140222110925_create_mingle_twitter_tweets.rb +15 -0
- data/db/migrate/20140222112623_create_mingle_facebook_posts.rb +19 -0
- data/db/migrate/20140222112741_create_mingle_instagram_photos.rb +15 -0
- data/db/migrate/20140222125715_create_mingle_feed_items.rb +11 -0
- data/db/migrate/20140224155358_drop_mingle_feed_items.rb +5 -0
- data/lib/mingle.rb +8 -0
- data/lib/mingle/engine.rb +21 -0
- data/lib/mingle/version.rb +3 -0
- data/lib/tasks/mingle_tasks.rake +33 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +23 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +30 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +83 -0
- data/spec/dummy/config/environments/test.rb +39 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/schema.rb +75 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +12964 -0
- data/spec/dummy/log/test.log +12630 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories/mingle/facebook/posts.rb +15 -0
- data/spec/factories/mingle/hashtaggings.rb +8 -0
- data/spec/factories/mingle/hashtags.rb +7 -0
- data/spec/factories/mingle/instagram/photos.rb +12 -0
- data/spec/factories/mingle/twitter/tweets.rb +12 -0
- data/spec/fixtures/mingle/facebook/page_posts.json +133 -0
- data/spec/fixtures/mingle/facebook/search_posts.json +119 -0
- data/spec/fixtures/mingle/instagram/photos.json +155 -0
- data/spec/fixtures/mingle/twitter/tweets.json +253 -0
- data/spec/jobs/mingle/facebook/fetch_spec.rb +9 -0
- data/spec/jobs/mingle/instagram/fetch_spec.rb +9 -0
- data/spec/jobs/mingle/twitter/fetch_spec.rb +9 -0
- data/spec/models/mingle/facebook/post_spec.rb +43 -0
- data/spec/models/mingle/facebook_spec.rb +105 -0
- data/spec/models/mingle/hashtag_spec.rb +41 -0
- data/spec/models/mingle/hashtagging_spec.rb +4 -0
- data/spec/models/mingle/instagram/photo_spec.rb +4 -0
- data/spec/models/mingle/instagram_spec.rb +58 -0
- data/spec/models/mingle/twitter/tweet_spec.rb +4 -0
- data/spec/models/mingle/twitter_spec.rb +69 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/fixture.rb +8 -0
- metadata +340 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f3bd646003531be5b3a770b16b012585460ec5ee
|
4
|
+
data.tar.gz: 205cab3c862ddb5c6ca37e6494844e500cde95a8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 86510a0a55dd1d1145a8ad1ce446015f930f9dc0f28638f7622e0d054a1b4b83784ff4b546ba5e03f402ce745f657d83f4167ba7b388c6934c1ec057c96f4468
|
7
|
+
data.tar.gz: fef31dda840faf088f5a92316a043176d0a364e12a41ab033fe5027148335f8080b4dc0687208d316c37062cfc29da2f0e831f71575b145ec5b67d77e829cbc2
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2014 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Mingle'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
Bundler::GemHelper.install_tasks
|
21
|
+
|
22
|
+
require 'rspec/core'
|
23
|
+
require 'rspec/core/rake_task'
|
24
|
+
|
25
|
+
desc 'Run all specs in spec directory (excluding plugin specs)'
|
26
|
+
RSpec::Core::RakeTask.new spec: 'app:db:test:prepare'
|
27
|
+
|
28
|
+
task default: :spec
|
@@ -0,0 +1,13 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require_tree .
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any styles
|
10
|
+
* defined in the other CSS/SCSS files in this directory. It is generally better to create a new
|
11
|
+
* file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Mingle::Facebook
|
2
|
+
|
3
|
+
class << self
|
4
|
+
attr_writer :config
|
5
|
+
|
6
|
+
def table_name_prefix
|
7
|
+
"#{Mingle.table_name_prefix}facebook_"
|
8
|
+
end
|
9
|
+
|
10
|
+
# Fetch posts since given date/timestamp, last post's creation date or beginning of time
|
11
|
+
def fetch(hashtags = Mingle::Hashtag.all, since = Mingle::Facebook::Post.ordered.last.try(:created_at))
|
12
|
+
hashtags = Array(hashtags)
|
13
|
+
|
14
|
+
posts = []
|
15
|
+
hashtags.each do |hashtag|
|
16
|
+
posts += posts_through_search hashtag, since
|
17
|
+
end
|
18
|
+
|
19
|
+
posts.collect do |data|
|
20
|
+
create_post_from_data(data, hashtags)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Retrieves posts through given search query
|
25
|
+
def posts_through_search hashtag, since
|
26
|
+
posts = FbGraph::Post.search(hashtag.tag_name_with_hash, since: since.to_i, return_ssl_resources: 1, access_token: config.access_token)
|
27
|
+
|
28
|
+
# Facebook search is fairly unpredictable, so ensure the query is actually mentioned
|
29
|
+
posts.select { |post| matches? post, hashtag.tag_name }
|
30
|
+
end
|
31
|
+
|
32
|
+
# Retrieves posts for given page
|
33
|
+
def posts_by_page page_id, since
|
34
|
+
page = FbGraph::Page.new(page_id, access_token: config.access_token)
|
35
|
+
page.posts(limit: 100, since: since.to_i, return_ssl_resources: 1)
|
36
|
+
end
|
37
|
+
|
38
|
+
def configure
|
39
|
+
yield config
|
40
|
+
end
|
41
|
+
|
42
|
+
def config
|
43
|
+
@config ||= Mingle::Facebook::Configuration.new
|
44
|
+
end
|
45
|
+
|
46
|
+
def create_post_from_data data, hashtags
|
47
|
+
post = Post.find_or_initialize_by post_id: data.identifier
|
48
|
+
|
49
|
+
post.attributes = {
|
50
|
+
caption: data.caption,
|
51
|
+
created_at: data.created_time,
|
52
|
+
description: data.description,
|
53
|
+
link: data.link,
|
54
|
+
message: data.message,
|
55
|
+
name: data.name,
|
56
|
+
picture: data.picture,
|
57
|
+
type: data.type,
|
58
|
+
user_id: data.from.identifier,
|
59
|
+
user_name: data.from.name
|
60
|
+
}
|
61
|
+
|
62
|
+
hashtags.each do |hashtag|
|
63
|
+
if matches? post, hashtag.tag_name
|
64
|
+
post.hashtags << hashtag unless post.hashtags.include? hashtag
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
post.save!
|
69
|
+
post
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def matches?(post, query)
|
75
|
+
post.message.try(:match, /#{query}/i) ||
|
76
|
+
post.description.try(:match, /#{query}/i) ||
|
77
|
+
post.caption.try(:match, /#{query}/i)
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Mingle::Facebook::Post < ActiveRecord::Base
|
2
|
+
has_many :hashtaggings, class_name: 'Mingle::Hashtagging', as: :hashtaggable,
|
3
|
+
dependent: :destroy
|
4
|
+
|
5
|
+
has_many :hashtags, through: :hashtaggings
|
6
|
+
|
7
|
+
validates :post_id, :user_id, :user_name, presence: true
|
8
|
+
|
9
|
+
scope :ordered, lambda { order('created_at ASC') }
|
10
|
+
|
11
|
+
# Prevent Rails from assuming :type is STI-related
|
12
|
+
# See: http://stackoverflow.com/questions/7134559/rails-use-type-column-without-sti
|
13
|
+
self.inheritance_column = nil
|
14
|
+
|
15
|
+
# Large pictures are not directly provided through the Graph API
|
16
|
+
# Note: Not a documented Facebook feature and may potentially break in the future
|
17
|
+
def large_picture
|
18
|
+
picture.gsub(/_(?:s|t)\.(jpg|jpeg|png|gif)$/, '_b.\1') if picture.present?
|
19
|
+
end
|
20
|
+
|
21
|
+
def profile_url
|
22
|
+
"https://www.facebook.com/#{user_id}" if user_id.present?
|
23
|
+
end
|
24
|
+
|
25
|
+
def profile_image_url
|
26
|
+
"https://graph.facebook.com/#{user_id}/picture" if user_id.present?
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Mingle::Hashtag < ActiveRecord::Base
|
2
|
+
|
3
|
+
has_many :hashtaggings, class_name: 'Mingle::Hashtagging', dependent: :destroy
|
4
|
+
|
5
|
+
has_many :facebook_posts, through: :hashtaggings, source: :hashtaggable,
|
6
|
+
source_type: 'Mingle::Facebook::Post'
|
7
|
+
|
8
|
+
has_many :twitter_tweets, through: :hashtaggings, source: :hashtaggable,
|
9
|
+
source_type: 'Mingle::Twitter::Tweet'
|
10
|
+
|
11
|
+
has_many :instagram_photos, through: :hashtaggings, source: :hashtaggable,
|
12
|
+
source_type: 'Mingle::Instagram::Photo'
|
13
|
+
|
14
|
+
validates :tag_name, uniqueness: true, presence: true
|
15
|
+
|
16
|
+
def tag_name_with_hash
|
17
|
+
return tag_name if tag_name.start_with? "#"
|
18
|
+
|
19
|
+
"##{tag_name}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def tag_name_without_hash
|
23
|
+
return tag_name unless tag_name.start_with? "#"
|
24
|
+
|
25
|
+
tag_name[1..-1]
|
26
|
+
end
|
27
|
+
|
28
|
+
def usage_count
|
29
|
+
hashtaggings.count
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Mingle::Instagram
|
2
|
+
|
3
|
+
def self.table_name_prefix
|
4
|
+
"#{Mingle.table_name_prefix}instagram_"
|
5
|
+
end
|
6
|
+
|
7
|
+
# Fetch recent photos tagged with 'klhd'
|
8
|
+
def self.fetch hashtags = Mingle::Hashtag.all
|
9
|
+
hashtags = Array(hashtags)
|
10
|
+
|
11
|
+
hashtags.each.map do |hashtag|
|
12
|
+
Instagram.tag_recent_media(hashtag.tag_name_without_hash).collect do |media|
|
13
|
+
|
14
|
+
case media.type
|
15
|
+
when 'image'
|
16
|
+
photo = Photo.find_or_initialize_by photo_id: media.id
|
17
|
+
photo.attributes = {
|
18
|
+
created_at: Time.at(media.created_time.to_i),
|
19
|
+
link: media.link,
|
20
|
+
message: media.try(:caption).try(:text),
|
21
|
+
url: media.images.standard_resolution.url,
|
22
|
+
user_handle: media.user.username,
|
23
|
+
user_id: media.user.id,
|
24
|
+
user_image_url: media.user.profile_picture
|
25
|
+
}
|
26
|
+
|
27
|
+
photo.hashtags << hashtag unless photo.hashtags.include? hashtag
|
28
|
+
|
29
|
+
photo.save!
|
30
|
+
photo
|
31
|
+
end
|
32
|
+
end.compact
|
33
|
+
end.flatten
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Mingle::Instagram::Photo < ActiveRecord::Base
|
2
|
+
has_many :hashtaggings, class_name: 'Mingle::Hashtagging', as: :hashtaggable, dependent: :destroy
|
3
|
+
has_many :hashtags, through: :hashtaggings
|
4
|
+
|
5
|
+
validates :link, :photo_id, :url, :user_handle, :user_image_url, presence: true
|
6
|
+
|
7
|
+
scope :ordered, lambda { order('created_at ASC') }
|
8
|
+
|
9
|
+
before_save :ensure_https_urls
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def ensure_https_urls
|
14
|
+
self.link = link.sub(/http:/, 'https:')
|
15
|
+
self.url = url.sub(/http:/, 'https:')
|
16
|
+
self.user_image_url = user_image_url.sub(%r(http://images\.ak\.instagram\.com),
|
17
|
+
'https://distillery.s3.amazonaws.com')
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Mingle::Twitter
|
2
|
+
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def table_name_prefix
|
6
|
+
"#{Mingle.table_name_prefix}twitter_"
|
7
|
+
end
|
8
|
+
|
9
|
+
def fetch hashtags = Mingle::Hashtag.all, since_id = Mingle::Twitter::Tweet.ordered.last.try(:tweet_id)
|
10
|
+
hashtags = Array(hashtags)
|
11
|
+
|
12
|
+
hashtags.map do |hashtag|
|
13
|
+
client.search(hashtag.tag_name_with_hash, since_id: since_id).collect do |data|
|
14
|
+
tweet = Tweet.find_or_initialize_by tweet_id: data.id.to_s
|
15
|
+
|
16
|
+
tweet.attributes = {
|
17
|
+
created_at: data.created_at,
|
18
|
+
image_url: data.media.any? ? data.media.first.media_url_https.to_s : nil,
|
19
|
+
text: data.text,
|
20
|
+
user_id: data.user.id.to_s,
|
21
|
+
user_handle: data.user.screen_name,
|
22
|
+
user_image_url: data.user.profile_image_url.to_s,
|
23
|
+
user_name: data.user.name
|
24
|
+
}
|
25
|
+
|
26
|
+
tweet.hashtags << hashtag unless tweet.hashtags.include? hashtag
|
27
|
+
|
28
|
+
tweet.save!
|
29
|
+
tweet
|
30
|
+
end
|
31
|
+
end.flatten
|
32
|
+
end
|
33
|
+
|
34
|
+
# Initialize the Twitter client.
|
35
|
+
#
|
36
|
+
# Returns a new Twitter::REST::Client instance.
|
37
|
+
def client
|
38
|
+
Twitter::REST::Client.new do |config|
|
39
|
+
config.consumer_key = ENV['TWITTER_API_KEY']
|
40
|
+
config.consumer_secret = ENV['TWITTER_API_SECRET']
|
41
|
+
config.access_token = ENV['TWITTER_ACCESS_TOKEN']
|
42
|
+
config.access_token_secret = ENV['TWITTER_ACCESS_TOKEN_SECRET']
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Mingle::Twitter::Tweet < ActiveRecord::Base
|
2
|
+
has_many :hashtaggings, class_name: 'Mingle::Hashtagging', as: :hashtaggable,
|
3
|
+
dependent: :destroy
|
4
|
+
|
5
|
+
has_many :hashtags, through: :hashtaggings
|
6
|
+
|
7
|
+
validates :text, :tweet_id, :user_handle, :user_image_url, :user_name, presence: true
|
8
|
+
|
9
|
+
scope :ordered, lambda { order('created_at ASC') }
|
10
|
+
|
11
|
+
before_save :ensure_https_urls
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def ensure_https_urls
|
16
|
+
self.user_image_url = user_image_url.sub(/http:/, 'https:')
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Mingle</title>
|
5
|
+
<%= stylesheet_link_tag "mingle/application", media: "all" %>
|
6
|
+
<%= javascript_include_tag "mingle/application" %>
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<%= yield %>
|
12
|
+
|
13
|
+
</body>
|
14
|
+
</html>
|