tweetable 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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]
|