tweetable 0.1.10
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +8 -0
- data/History.txt +4 -0
- data/Manifest.txt +33 -0
- data/PostInstall.txt +7 -0
- data/README.rdoc +37 -0
- data/Rakefile +19 -0
- data/VERSION +1 -0
- data/lib/tweetable/authorization.rb +21 -0
- data/lib/tweetable/collection.rb +54 -0
- data/lib/tweetable/link.rb +30 -0
- data/lib/tweetable/message.rb +119 -0
- data/lib/tweetable/persistable.rb +32 -0
- data/lib/tweetable/photo.rb +6 -0
- data/lib/tweetable/queue.rb +121 -0
- data/lib/tweetable/search.rb +77 -0
- data/lib/tweetable/twitter_client.rb +36 -0
- data/lib/tweetable/twitter_streaming_client.rb +67 -0
- data/lib/tweetable/url.rb +39 -0
- data/lib/tweetable/user.rb +104 -0
- data/lib/tweetable.rb +82 -0
- data/pkg/tweetable-0.1.7.gem +0 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/collection_spec.rb +55 -0
- data/spec/fixtures/blank.json +1 -0
- data/spec/fixtures/flippyhead.json +1 -0
- data/spec/fixtures/follower_ids.json +1 -0
- data/spec/fixtures/friend_ids.json +1 -0
- data/spec/fixtures/friends_timeline.json +1 -0
- data/spec/fixtures/link_blank.json +1 -0
- data/spec/fixtures/link_exists.json +1 -0
- data/spec/fixtures/rate_limit_status.json +1 -0
- data/spec/fixtures/search.json +1 -0
- data/spec/fixtures/user_timeline.json +1 -0
- data/spec/fixtures/verify_credentials.json +1 -0
- data/spec/link_spec.rb +35 -0
- data/spec/message_spec.rb +148 -0
- data/spec/persistable_spec.rb +53 -0
- data/spec/queue_spec.rb +29 -0
- data/spec/search_spec.rb +60 -0
- data/spec/spec.opts +5 -0
- data/spec/spec_helper.rb +55 -0
- data/spec/tweetable_spec.rb +19 -0
- data/spec/twitter_client_spec.rb +41 -0
- data/spec/twitter_streaming_client_spec.rb +18 -0
- data/spec/user_spec.rb +143 -0
- data/tweetable.gemspec +106 -0
- metadata +165 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
# require 'yajl'
|
2
|
+
require 'crack'
|
3
|
+
require 'crack/json'
|
4
|
+
require 'net/http'
|
5
|
+
require 'cgi'
|
6
|
+
|
7
|
+
module Tweetable
|
8
|
+
class URL
|
9
|
+
LONG_URL_API_URL = 'http://www.longurlplease.com/api/v1.1'
|
10
|
+
LONG_URL_TIMEOUT = 5
|
11
|
+
|
12
|
+
def self.headers
|
13
|
+
{ "Content-Type" => 'application/json' }
|
14
|
+
end
|
15
|
+
|
16
|
+
# the api can handle multiple url queries and response, but this does just 1
|
17
|
+
def self.lookup_long_url(search)
|
18
|
+
url = URI.parse(LONG_URL_API_URL)
|
19
|
+
url.query = "q=#{CGI.escape(search)}"
|
20
|
+
|
21
|
+
http = Net::HTTP.new(url.host, url.port)
|
22
|
+
http.read_timeout = LONG_URL_TIMEOUT
|
23
|
+
|
24
|
+
long_url = nil
|
25
|
+
begin
|
26
|
+
json = http.get(url.to_s, headers).body
|
27
|
+
urls = Crack::JSON.parse(json)
|
28
|
+
long_url = urls.values[0]
|
29
|
+
rescue Crack::ParseError
|
30
|
+
raise TweetableError.new("Error parsing json trying to get long URL: #{json.to_s}")
|
31
|
+
rescue Timeout::Error => e
|
32
|
+
raise TweetableError.new("Timeout trying to get long URL: #{e}")
|
33
|
+
rescue Exception => e
|
34
|
+
raise TweetableError.new("Error trying to get long URL: #{e}")
|
35
|
+
end
|
36
|
+
long_url.nil? ? search : long_url
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Tweetable
|
2
|
+
class User < Persistable
|
3
|
+
attribute :created_at
|
4
|
+
attribute :updated_at
|
5
|
+
attribute :screen_name
|
6
|
+
attribute :profile_image_url
|
7
|
+
attribute :user_id
|
8
|
+
attribute :friends_count
|
9
|
+
attribute :followers_count
|
10
|
+
|
11
|
+
index :screen_name
|
12
|
+
index :user_id
|
13
|
+
|
14
|
+
list :messages, Message
|
15
|
+
list :friend_messages, Message
|
16
|
+
|
17
|
+
set :friend_ids
|
18
|
+
set :follower_ids
|
19
|
+
set :tags
|
20
|
+
|
21
|
+
def self.create_from_timeline(user)
|
22
|
+
u = User.find_or_create(:screen_name, user.screen_name.downcase)
|
23
|
+
u.update(
|
24
|
+
:user_id => user[:id],
|
25
|
+
:profile_image_url => user.profile_image_url,
|
26
|
+
:followers_count => user.followers_count,
|
27
|
+
:friends_count => user.friends_count)
|
28
|
+
u
|
29
|
+
end
|
30
|
+
|
31
|
+
def update_all(force = false)
|
32
|
+
return unless needs_update?(force)
|
33
|
+
update_info if self.config[:include_on_update].include?(:info)
|
34
|
+
update_friend_ids if self.config[:include_on_update].include?(:friend_ids)
|
35
|
+
update_follower_ids if self.config[:include_on_update].include?(:follower_ids)
|
36
|
+
update_friend_messages if self.config[:include_on_update].include?(:friend_messages)
|
37
|
+
self.update(:updated_at => Time.now.utc.to_s)
|
38
|
+
self.config[:include_on_update].include?(:messages) ? update_messages : [] # return newly found messages
|
39
|
+
end
|
40
|
+
|
41
|
+
def update_info
|
42
|
+
uid = self.user_id.blank? ? self.screen_name : self.user_id
|
43
|
+
info = self.client.user(uid)
|
44
|
+
|
45
|
+
self.user_id = info[:id]
|
46
|
+
self.screen_name = info[:screen_name].downcase
|
47
|
+
self.profile_image_url = info[:profile_image_url]
|
48
|
+
self.friends_count = info[:friends_count]
|
49
|
+
self.followers_count = info[:followers_count]
|
50
|
+
self.save
|
51
|
+
end
|
52
|
+
|
53
|
+
def update_friend_ids
|
54
|
+
fids = self.client.friend_ids(:screen_name => self.screen_name, :page => 1) # limit to 5000 friend ids
|
55
|
+
fids.each{|fid| self.friend_ids << fid}
|
56
|
+
end
|
57
|
+
|
58
|
+
def update_follower_ids
|
59
|
+
fids = self.client.follower_ids(:screen_name => self.screen_name, :page => 1) # limit to 5000 friend ids
|
60
|
+
fids.each{|fid| self.follower_ids << fid}
|
61
|
+
end
|
62
|
+
|
63
|
+
def update_messages(options = {})
|
64
|
+
most_recent_message = self.messages.first(:order => 'DESC', :by => :message_id)
|
65
|
+
options.merge!(:count => self.config[:max_message_count], :screen_name => self.screen_name)
|
66
|
+
options[:since_id] = most_recent_message.message_id if most_recent_message
|
67
|
+
|
68
|
+
timeline = self.client.user_timeline(options)
|
69
|
+
build_messages(timeline, self.messages)
|
70
|
+
end
|
71
|
+
|
72
|
+
def update_friend_messages(options = {})
|
73
|
+
most_recent_message = self.friend_messages.first(:order => 'DESC', :by => :message_id)
|
74
|
+
options.merge!(:count => self.config[:max_message_count], :screen_name => self.screen_name)
|
75
|
+
options[:since_id] = most_recent_message.message_id if most_recent_message
|
76
|
+
|
77
|
+
timeline = self.client.friends_timeline(options)
|
78
|
+
build_messages(timeline, self.friend_messages, :create_user => true)
|
79
|
+
end
|
80
|
+
|
81
|
+
def twitter_link
|
82
|
+
"http://twitter.com/#{screen_name}"
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
def build_messages(timeline, collection, options = {})
|
87
|
+
new_messages = []
|
88
|
+
|
89
|
+
timeline.each do |message|
|
90
|
+
m = Message.create_from_timeline(message, options[:create_user])
|
91
|
+
next if !m.valid?
|
92
|
+
collection << m
|
93
|
+
new_messages << m
|
94
|
+
end
|
95
|
+
|
96
|
+
new_messages # return just the newly created messages
|
97
|
+
end
|
98
|
+
|
99
|
+
def validate
|
100
|
+
super
|
101
|
+
assert_unique :screen_name
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/lib/tweetable.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'logging'
|
5
|
+
require 'twitter'
|
6
|
+
|
7
|
+
module Tweetable
|
8
|
+
DEFAULT_CONFIG = {
|
9
|
+
:max_message_count => 10,
|
10
|
+
:include_on_update => [:info, :friend_ids, :follower_ids, :messages], # skipping :friend_messages
|
11
|
+
:update_delay => 60*10
|
12
|
+
}
|
13
|
+
|
14
|
+
# Generic exception class.
|
15
|
+
class TweetableError < StandardError
|
16
|
+
end
|
17
|
+
|
18
|
+
class TweetableAuthError < StandardError
|
19
|
+
end
|
20
|
+
|
21
|
+
# Raised when there is a temporary problem like Twitter::Unavailable
|
22
|
+
class TemporaryPullFromQueueError < TweetableError
|
23
|
+
end
|
24
|
+
|
25
|
+
# Raised when there is a problem pulling from the queue
|
26
|
+
class PullFromQueueError < TweetableError
|
27
|
+
end
|
28
|
+
|
29
|
+
autoload :Persistable, File.join(File.dirname(__FILE__), *%w[tweetable persistable.rb])
|
30
|
+
autoload :TwitterClient, File.join(File.dirname(__FILE__), *%w[tweetable twitter_client.rb])
|
31
|
+
autoload :TwitterStreamingClient, File.join(File.dirname(__FILE__), *%w[tweetable twitter_streaming_client.rb])
|
32
|
+
autoload :Link, File.join(File.dirname(__FILE__), *%w[tweetable link.rb])
|
33
|
+
autoload :Photo, File.join(File.dirname(__FILE__), *%w[tweetable photo.rb])
|
34
|
+
autoload :Message, File.join(File.dirname(__FILE__), *%w[tweetable message.rb])
|
35
|
+
autoload :Search, File.join(File.dirname(__FILE__), *%w[tweetable search.rb])
|
36
|
+
autoload :User, File.join(File.dirname(__FILE__), *%w[tweetable user.rb])
|
37
|
+
autoload :Collection, File.join(File.dirname(__FILE__), *%w[tweetable collection.rb])
|
38
|
+
autoload :UserCollection, File.join(File.dirname(__FILE__), *%w[tweetable collection.rb])
|
39
|
+
autoload :MessageCollection, File.join(File.dirname(__FILE__), *%w[tweetable collection.rb])
|
40
|
+
autoload :SearchCollection, File.join(File.dirname(__FILE__), *%w[tweetable collection.rb])
|
41
|
+
autoload :LinkCollection, File.join(File.dirname(__FILE__), *%w[tweetable collection.rb])
|
42
|
+
autoload :Authorization, File.join(File.dirname(__FILE__), *%w[tweetable authorization.rb])
|
43
|
+
autoload :URL, File.join(File.dirname(__FILE__), *%w[tweetable url.rb])
|
44
|
+
|
45
|
+
def client
|
46
|
+
Thread.current[:twitter_client] ||= options.nil? ? TwitterClient.new.login(*credentials) : TwitterClient.new.authorize(*options)
|
47
|
+
end
|
48
|
+
|
49
|
+
def streaming_client
|
50
|
+
Thread.current[:twitter_streaming_client] ||= TwitterStreamingClient.new.authorize(*options)
|
51
|
+
end
|
52
|
+
|
53
|
+
def login(*credentials)
|
54
|
+
@credentials = credentials
|
55
|
+
end
|
56
|
+
|
57
|
+
def authorize(*options)
|
58
|
+
@options = options
|
59
|
+
end
|
60
|
+
|
61
|
+
def options
|
62
|
+
@options
|
63
|
+
end
|
64
|
+
|
65
|
+
def config(config = nil)
|
66
|
+
@config ||= DEFAULT_CONFIG
|
67
|
+
@config.merge!(config) unless config.nil?
|
68
|
+
@config
|
69
|
+
end
|
70
|
+
|
71
|
+
def credentials
|
72
|
+
@credentials
|
73
|
+
end
|
74
|
+
|
75
|
+
def log
|
76
|
+
@log = Logging.logger(STDOUT)
|
77
|
+
@log.level = :debug
|
78
|
+
@log
|
79
|
+
end
|
80
|
+
|
81
|
+
module_function :client, :options, :authorize, :login, :credentials, :log, :config
|
82
|
+
end
|
Binary file
|
data/script/console
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# File: script/console
|
3
|
+
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
|
4
|
+
|
5
|
+
libs = " -r irb/completion"
|
6
|
+
# Perhaps use a console_lib to store any extra methods I may want available in the cosole
|
7
|
+
# libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
|
8
|
+
libs << " -r #{File.dirname(__FILE__) + '/../lib/tweetable.rb'}"
|
9
|
+
puts "Loading tweetable gem"
|
10
|
+
exec "#{irb} #{libs} --simple-prompt"
|
data/script/destroy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/destroy'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Destroy.new.run(ARGV)
|
data/script/generate
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/generate'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Generate.new.run(ARGV)
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
describe Tweetable::Collection, 'when building lists' do
|
4
|
+
before do
|
5
|
+
RedisSpecHelper.reset
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should dissallow collections without a name" do
|
9
|
+
Tweetable::Collection.create.should_not be_valid
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should build new MessageCollection" do
|
13
|
+
Tweetable::MessageCollection.new.should_not be_nil
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should build new UserCollection" do
|
17
|
+
Tweetable::UserCollection.new.should_not be_nil
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should build new SearchCollection" do
|
21
|
+
Tweetable::SearchCollection.new.should_not be_nil
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should find or create a new SearchCollection" do
|
25
|
+
Tweetable::SearchCollection.find_or_create(:name, 'some-name').should be_instance_of Tweetable::SearchCollection
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should find an existing collection" do
|
29
|
+
Tweetable::SearchCollection.create(:name => 'some-name')
|
30
|
+
Tweetable::SearchCollection.find(:name => 'some-name').first.should be_instance_of Tweetable::SearchCollection
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe Tweetable::Collection, 'when building sets' do
|
35
|
+
before do
|
36
|
+
RedisSpecHelper.reset
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should add user to user set" do
|
40
|
+
collection = Tweetable::UserCollection.find_or_create(:name, 'some-name')
|
41
|
+
collection.user_set.add(Tweetable::User.create(:screen_name => 'this'))
|
42
|
+
collection.user_set.size.should == 1
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should only add unique users to set" do
|
46
|
+
collection = Tweetable::UserCollection.find_or_create(:name, 'some-name')
|
47
|
+
user = Tweetable::User.create(:screen_name => 'this', :created_at => Time.now.utc.to_s)
|
48
|
+
user2 = Tweetable::User.create(:screen_name => 'this2', :created_at => Time.now.utc.to_s)
|
49
|
+
collection.user_set.add(user)
|
50
|
+
collection.user_set.add(user2)
|
51
|
+
collection.user_set.add(user)
|
52
|
+
collection.user_set.size.should == 2
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
[]
|
@@ -0,0 +1 @@
|
|
1
|
+
{"profile_background_image_url":"http://s.twimg.com/a/1274144130/images/themes/theme12/bg.gif","profile_link_color":"FF0000","url":"http://wagglelabs.com","description":"Haha made ya read!","status":{"contributors":null,"coordinates":null,"in_reply_to_screen_name":null,"geo":null,"favorited":false,"truncated":false,"in_reply_to_status_id":null,"source":"<a href=\"http://twitterrific.com\" rel=\"nofollow\">Twitterrific</a>","in_reply_to_user_id":null,"created_at":"Sat May 29 03:21:34 +0000 2010","id":14951172829,"place":null,"text":"Watering mytwee !"},"profile_background_tile":false,"profile_sidebar_fill_color":"FFF7CC","location":"Capitol Hill, Washington","statuses_count":262,"notifications":null,"profile_image_url":"http://a3.twimg.com/profile_images/74792235/screen-capture-2_normal.png","profile_sidebar_border_color":"F2E195","followers_count":233,"contributors_enabled":false,"lang":"en","screen_name":"Flippyhead","friends_count":114,"favourites_count":2,"profile_background_color":"BADFCD","protected":false,"following":null,"geo_enabled":true,"time_zone":"Pacific Time (US & Canada)","created_at":"Wed Feb 20 04:05:51 +0000 2008","name":"Peter T. Brown","profile_text_color":"0C3E53","id":13705052,"verified":false,"utc_offset":-28800}
|
@@ -0,0 +1 @@
|
|
1
|
+
[103136961,12767492,38488854,43330332,117481827,114770454,87265151,7315022,14470427,15661935,118054255,116260600,58822253,31340126,17296974,32503163,108098467,60002553,79313634,20254889,82887533,12101862,39617233,15878021,95752657,97296470,22343218,94655742,91197034,15140257,14305034,87716554,87618379,85689666,56425680,16607043,1699751,45283949,3452941,84318041,82091551,16753165,59543723,36585916,79669240,78114832,15759497,32241975,76542156,16337900,14814522,16598422,14166535,64881807,46293477,41241684,17724900,40162052,34364391,25577085,31231869,67645971,19942432,46725587,62416583,3778581,2856031,16030970,15443687,16987565,33893844,43183984,24562033,15665281,11334,17304123,20104538,16388638,17663086,53480097,16230239,58598857,46236831,58579307,58587952,24718118,23096363,18348762,25846278,23047941,19990886,19159761,13656362,2656,5702262,15283419,51553499,17629947,18229081,25030269,16004418,17413507,46757793,41849734,18457047,2654,809512,54078161,15575798,46715253,21538391,14446704,7597362,14762678,17093876,32327090,43418805,25539943,24018995,9022272,38533218,20123484,31560075,27731367,22996821,20351687,30688103,44668855,17813503,16438680,18041530,21681207,29260057,44681603,43753948,43012556,16072309,17627869,23178979,20395561,51585617,15399031,15618016,18285296,29571801,14838820,14137771,24251591,18584722,19207519,19089507,16460227,17089852,14352556,49594418,14385806,10475492,49239945,23456819,15103961,15522944,47891060,1612021,37533608,42924484,14207966,41479415,26565471,37879230,983011,33337061,36267489,17181629,33389895,22900167,34986068,34904138,34686817,34423762,18811351,32664150,32649092,32630299,32607874,14069658,32235388,32227031,32222310,32216021,32211757,32204651,31294847,31237665,18810879,24166272,20255102,6726182,22071150,6140,8938362,16820481,18973843,24576385,16100658,21701416,1442541,19121958,22966270,15069767,17158244,17218814,21015551,780886,20402566,15555883,1173281,15841044,15552710,3093981,802799,19239032,14334195,19943055,10395332,2078,15909918,13357,3734051,3567281]
|
@@ -0,0 +1 @@
|
|
1
|
+
[17333183,14305100,12685,3452941,60493684,21547828,1442541,14305034,20608910,36082429,3778581,19035252,30495974,14032852,9580822,21066103,21944280,18789983,6170,25577085,13445732,14677720,9066762,3302921,1192,16004418,38975663,21159411,14278978,9420652,5832712,14166535,17242834,18457047,3244801,2729061,16393990,14939200,17304123,13895242,16316680,1235521,13035,11907952,24726388,7255652,10168952,15629200,7787522,14805814,16669075,30313925,35270768,51886564,5493,11263102,48381958,12691112,19159761,53256107,8071082,1916151,23832616,11397012,20734538,17093876,7597362,16735575,32241975,27339792,41478837,35395379,17065232,14446704,16129920,639643,12854372,14319981,16753165,24737795,33187261,16793850,15347711,8693202,14589771,22132568,42678765,50089613,20092241,29566363,16072309,17290254,18772339,19121773,30270211,41479415,3093981,17203250,649,14814522,7706092,2029851,43753948,6140,16112339,17663086,15126376,5651,47891060,9504712,15522944,6118342,12111,45384353,6583802,3585151,31136692,20123484,17086635,43012556,46725587,43916629,15618016,13134132,18492538,24258295,24251591,30903701,49258589,29327220,30688103,16426042,16055631,7017692,148373,14137771,31825923,15610682,45769183,20104538,8248752,17875633,3370001,44274687,20210157,15741618,25181690,25694758,31231869,17813503,37678947,29260057,14091396,45607634,46293477,13983,18492596,30340010,28286020,24827930,16230239,19089507,17089852,23519997,21361624,23642374,62903,20037216,16115979,29571801,32327090,17627869,14818663,17205623,18584722,23879834,19128879,14782042,14852457,14838820,16120957,18041530,19897797,22996563,21400763,20216202,18091654,41008177,16607043,23178979,18285296,12650182,18152775,20735406,19040380,25369931,31560075,24156110,6839892,21842077,34988327,17368984,20395561,22952101,12258422,24018995,19452490,21630899,22821231,22409250,16460227,18999188,24960841,32684446,9076552,23721204,41439789,6429402,27033107,44681603,22961177,25846278,18999061,21591962,29915826,19207519,19216123,15463366,31226467,19012200,3019081,7378312,19782239,26394794,7076432,19786525,17724900,21994180,15200655,22221963,26704718,17671951,17545756,21891183,17645701,32175337,34991746,49108215,25030269,10944522,16438680,22966270,16099148,18131862,15399031,14769689,756278,15846932,14470427,14385806,790368,4044361,6025032,5500632,1249191,809512,11334,1173281,9338012,14256894,14326765,14232711,820661,15876737,786818,9022272,16095572,10058162,21538391,19094626,28921689,36132718,1053901,15240977,25032607,50057976,23899200,15685878,16975038,763489,14324794,17464103,20254889,5702262,23456819,15103961,48160844,2185,18673,13357,12428572,14575000,23393638,17870251,22028135,33337061,9676672,586,32216021,32211757,428333,32649092,32664150,14069658,1338321,734493,15552710,97933,6726182,4557111,14170620,24576385,16100658,21701416,780886,8938362,18973843,11132462,15909918,2061681,10395332,14388729,2078,16359539,4644261,15841044,15069767,802799,3567281]
|