StatsCollect 0.1.0.20101220
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/AUTHORS +2 -0
- data/ChangeLog +5 -0
- data/Credits +31 -0
- data/LICENSE +31 -0
- data/README +18 -0
- data/ReleaseInfo +8 -0
- data/StatsCollect.conf.rb.example +91 -0
- data/TODO +1 -0
- data/bin/StatsCollect.rb +30 -0
- data/lib/StatsCollect/Backends/MySQL.rb +190 -0
- data/lib/StatsCollect/Backends/Terminal.rb +157 -0
- data/lib/StatsCollect/Locations/AddThis.rb +65 -0
- data/lib/StatsCollect/Locations/Facebook.rb +62 -0
- data/lib/StatsCollect/Locations/FacebookArtist.rb +63 -0
- data/lib/StatsCollect/Locations/FacebookLike.rb +51 -0
- data/lib/StatsCollect/Locations/GoogleSearch.rb +39 -0
- data/lib/StatsCollect/Locations/MySpace.rb +224 -0
- data/lib/StatsCollect/Locations/ReverbNation.rb +143 -0
- data/lib/StatsCollect/Locations/Tweets.rb +51 -0
- data/lib/StatsCollect/Locations/Twitter.rb +46 -0
- data/lib/StatsCollect/Locations/Youtube.rb +69 -0
- data/lib/StatsCollect/Notifiers/None.rb +24 -0
- data/lib/StatsCollect/Notifiers/SendMail.rb +34 -0
- data/lib/StatsCollect/Stats.rb +447 -0
- data/lib/StatsCollect/StatsProxy.rb +106 -0
- metadata +89 -0
@@ -0,0 +1,62 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
|
+
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
|
+
#++
|
5
|
+
|
6
|
+
module StatsCollect
|
7
|
+
|
8
|
+
module Locations
|
9
|
+
|
10
|
+
class Facebook
|
11
|
+
|
12
|
+
# Execute the plugin.
|
13
|
+
# This method has to add the stats and errors to the proxy.
|
14
|
+
# It can filter only objects and categories given.
|
15
|
+
# It has access to its configuration.
|
16
|
+
#
|
17
|
+
# Parameters:
|
18
|
+
# * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
|
19
|
+
# * *iConf* (<em>map<Symbol,Object></em>): The configuration associated to this plugin
|
20
|
+
# * *iLstObjects* (<em>list<String></em>): List of objects to filter (can be empty for all)
|
21
|
+
# * *iLstCategories* (<em>list<String></em>): List of categories to filter (can be empty for all)
|
22
|
+
def execute(oStatsProxy, iConf, iLstObjects, iLstCategories)
|
23
|
+
require 'mechanize'
|
24
|
+
lMechanizeAgent = Mechanize.new
|
25
|
+
lLoginForm = lMechanizeAgent.get('http://www.facebook.com').forms[0]
|
26
|
+
lLoginForm.email = iConf[:LoginEMail]
|
27
|
+
lLoginForm.pass = iConf[:LoginPassword]
|
28
|
+
# Submit to get to the home page
|
29
|
+
lMechanizeAgent.submit(lLoginForm, lLoginForm.buttons.first)
|
30
|
+
if ((oStatsProxy.isObjectIncluded?('Global')) and
|
31
|
+
(oStatsProxy.isCategoryIncluded?('Friends')))
|
32
|
+
getProfile(oStatsProxy, lMechanizeAgent)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Get the profile statistics
|
37
|
+
#
|
38
|
+
# Parameters:
|
39
|
+
# * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
|
40
|
+
# * *iMechanizeAgent* (_Mechanize_): The agent reading pages
|
41
|
+
def getProfile(oStatsProxy, iMechanizeAgent)
|
42
|
+
lProfilePage = iMechanizeAgent.get('http://www.facebook.com/profile.php')
|
43
|
+
lNbrFriends = nil
|
44
|
+
lProfilePage.root.css('script').each do |iScriptNode|
|
45
|
+
lMatch = iScriptNode.content.match(/>(\d*) friends<\\\/a><\\\/span>/)
|
46
|
+
if (lMatch != nil)
|
47
|
+
lNbrFriends = Integer(lMatch[1])
|
48
|
+
break
|
49
|
+
end
|
50
|
+
end
|
51
|
+
if (lNbrFriends == nil)
|
52
|
+
logErr "Unable to get number of friends: #{lProfilePage.root}"
|
53
|
+
else
|
54
|
+
oStatsProxy.addStat('Global', 'Friends', lNbrFriends)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
|
+
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
|
+
#++
|
5
|
+
|
6
|
+
module StatsCollect
|
7
|
+
|
8
|
+
module Locations
|
9
|
+
|
10
|
+
class FacebookArtist
|
11
|
+
|
12
|
+
# Execute the plugin.
|
13
|
+
# This method has to add the stats and errors to the proxy.
|
14
|
+
# It can filter only objects and categories given.
|
15
|
+
# It has access to its configuration.
|
16
|
+
#
|
17
|
+
# Parameters:
|
18
|
+
# * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
|
19
|
+
# * *iConf* (<em>map<Symbol,Object></em>): The configuration associated to this plugin
|
20
|
+
# * *iLstObjects* (<em>list<String></em>): List of objects to filter (can be empty for all)
|
21
|
+
# * *iLstCategories* (<em>list<String></em>): List of categories to filter (can be empty for all)
|
22
|
+
def execute(oStatsProxy, iConf, iLstObjects, iLstCategories)
|
23
|
+
require 'mechanize'
|
24
|
+
lMechanizeAgent = Mechanize.new
|
25
|
+
lLoginForm = lMechanizeAgent.get('http://www.facebook.com').forms[0]
|
26
|
+
lLoginForm.email = iConf[:LoginEMail]
|
27
|
+
lLoginForm.pass = iConf[:LoginPassword]
|
28
|
+
# Submit to get to the home page
|
29
|
+
lMechanizeAgent.submit(lLoginForm, lLoginForm.buttons.first)
|
30
|
+
if ((oStatsProxy.isObjectIncluded?('Global')) and
|
31
|
+
(oStatsProxy.isCategoryIncluded?('Likes')))
|
32
|
+
getArtistProfile(oStatsProxy, lMechanizeAgent, iConf)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Get the artist profile statistics
|
37
|
+
#
|
38
|
+
# Parameters:
|
39
|
+
# * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
|
40
|
+
# * *iMechanizeAgent* (_Mechanize_): The agent reading pages
|
41
|
+
# * *iConf* (<em>map<Symbol,Object></em>): The conf
|
42
|
+
def getArtistProfile(oStatsProxy, iMechanizeAgent, iConf)
|
43
|
+
lProfilePage = iMechanizeAgent.get("http://www.facebook.com/pages/#{iConf[:PageID]}")
|
44
|
+
lNbrLikes = nil
|
45
|
+
lProfilePage.root.css('script').each do |iScriptNode|
|
46
|
+
lMatch = iScriptNode.content.match(/>(\d*) People Like This/)
|
47
|
+
if (lMatch != nil)
|
48
|
+
lNbrLikes = Integer(lMatch[1])
|
49
|
+
break
|
50
|
+
end
|
51
|
+
end
|
52
|
+
if (lNbrLikes == nil)
|
53
|
+
logErr "Unable to get number of likes: #{lProfilePage.root}"
|
54
|
+
else
|
55
|
+
oStatsProxy.addStat('Global', 'Likes', lNbrLikes)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
|
+
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
|
+
#++
|
5
|
+
|
6
|
+
module StatsCollect
|
7
|
+
|
8
|
+
module Locations
|
9
|
+
|
10
|
+
class FacebookLike
|
11
|
+
|
12
|
+
# Execute the plugin.
|
13
|
+
# This method has to add the stats and errors to the proxy.
|
14
|
+
# It can filter only objects and categories given.
|
15
|
+
# It has access to its configuration.
|
16
|
+
#
|
17
|
+
# Parameters:
|
18
|
+
# * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
|
19
|
+
# * *iConf* (<em>map<Symbol,Object></em>): The configuration associated to this plugin
|
20
|
+
# * *iLstObjects* (<em>list<String></em>): List of objects to filter (can be empty for all)
|
21
|
+
# * *iLstCategories* (<em>list<String></em>): List of categories to filter (can be empty for all)
|
22
|
+
def execute(oStatsProxy, iConf, iLstObjects, iLstCategories)
|
23
|
+
require 'mechanize'
|
24
|
+
lMechanizeAgent = Mechanize.new
|
25
|
+
# Get the number of likes from Facebook
|
26
|
+
lErrorObjects = []
|
27
|
+
if (oStatsProxy.isCategoryIncluded?('Likes'))
|
28
|
+
iConf[:Objects].each do |iObject|
|
29
|
+
if (oStatsProxy.isObjectIncluded?(iObject))
|
30
|
+
lLikesContent = lMechanizeAgent.get("http://www.facebook.com/plugins/like.php?href=#{iObject}").root.css('span.connect_widget_not_connected_text').first.content.delete(',')
|
31
|
+
lMatch = lLikesContent.match(/^(\d*) likes./)
|
32
|
+
if (lMatch == nil)
|
33
|
+
logErr "Unable to parse FacebookLike output for object #{iObject}: #{lLikesContent}"
|
34
|
+
lErrorObjects << iObject
|
35
|
+
else
|
36
|
+
lNbrLikes = Integer(lMatch[1])
|
37
|
+
oStatsProxy.addStat(iObject, 'Likes', lNbrLikes)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
if (!lErrorObjects.empty?)
|
43
|
+
oStatsProxy.addUnrecoverableOrder(lErrorObjects, ['Likes'])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
|
+
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
|
+
#++
|
5
|
+
|
6
|
+
module StatsCollect
|
7
|
+
|
8
|
+
module Locations
|
9
|
+
|
10
|
+
class GoogleSearch
|
11
|
+
|
12
|
+
# Execute the plugin.
|
13
|
+
# This method has to add the stats and errors to the proxy.
|
14
|
+
# It can filter only objects and categories given.
|
15
|
+
# It has access to its configuration.
|
16
|
+
#
|
17
|
+
# Parameters:
|
18
|
+
# * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
|
19
|
+
# * *iConf* (<em>map<Symbol,Object></em>): The configuration associated to this plugin
|
20
|
+
# * *iLstObjects* (<em>list<String></em>): List of objects to filter (can be empty for all)
|
21
|
+
# * *iLstCategories* (<em>list<String></em>): List of categories to filter (can be empty for all)
|
22
|
+
def execute(oStatsProxy, iConf, iLstObjects, iLstCategories)
|
23
|
+
iConf[:Objects].each do |iObject|
|
24
|
+
if ((oStatsProxy.isObjectIncluded?(iObject)) and
|
25
|
+
(oStatsProxy.isCategoryIncluded?('Search results')))
|
26
|
+
require 'mechanize'
|
27
|
+
lMechanizeAgent = Mechanize.new
|
28
|
+
lProfilePage = lMechanizeAgent.get("http://www.google.com/search?q=#{URI.escape(iObject)}")
|
29
|
+
lNbrSearchResults = Integer(lProfilePage.root.css('div#resultStats').first.content.delete(',').strip.match(/ (\d*) /)[1])
|
30
|
+
oStatsProxy.addStat(iObject, 'Search results', lNbrSearchResults)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
|
+
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
|
+
#++
|
5
|
+
|
6
|
+
module StatsCollect
|
7
|
+
|
8
|
+
module Locations
|
9
|
+
|
10
|
+
class MySpace
|
11
|
+
|
12
|
+
# Execute the plugin.
|
13
|
+
# This method has to add the stats and errors to the proxy.
|
14
|
+
# It can filter only objects and categories given.
|
15
|
+
# It has access to its configuration.
|
16
|
+
#
|
17
|
+
# Parameters:
|
18
|
+
# * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
|
19
|
+
# * *iConf* (<em>map<Symbol,Object></em>): The configuration associated to this plugin
|
20
|
+
# * *iLstObjects* (<em>list<String></em>): List of objects to filter (can be empty for all)
|
21
|
+
# * *iLstCategories* (<em>list<String></em>): List of categories to filter (can be empty for all)
|
22
|
+
def execute(oStatsProxy, iConf, iLstObjects, iLstCategories)
|
23
|
+
require 'mechanize'
|
24
|
+
lMechanizeAgent = Mechanize.new
|
25
|
+
# Set a specific user agent, as myspace will treat our agent as a mobile one
|
26
|
+
lMechanizeAgent.user_agent = 'Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13'
|
27
|
+
# Get the login page
|
28
|
+
lLoginForm = lMechanizeAgent.get('http://www.myspace.com/redirector?dest=/home').forms[2]
|
29
|
+
lLoginForm.Email = iConf[:LoginEMail]
|
30
|
+
lLoginForm.Password = iConf[:LoginPassword]
|
31
|
+
# Submit to get to the home page
|
32
|
+
lMechanizeAgent.submit(lLoginForm, lLoginForm.buttons.first)
|
33
|
+
if ((oStatsProxy.isObjectIncluded?('Global')) and
|
34
|
+
(oStatsProxy.isCategoryIncluded?('Comments')))
|
35
|
+
getProfile(oStatsProxy, lMechanizeAgent)
|
36
|
+
end
|
37
|
+
if ((oStatsProxy.isObjectIncluded?('Global')) and
|
38
|
+
((oStatsProxy.isCategoryIncluded?('Friends')) or
|
39
|
+
(oStatsProxy.isCategoryIncluded?('Visits'))))
|
40
|
+
getDashboard(oStatsProxy, lMechanizeAgent)
|
41
|
+
end
|
42
|
+
if (oStatsProxy.isCategoryIncluded?('Song plays'))
|
43
|
+
getSongs(oStatsProxy, lMechanizeAgent)
|
44
|
+
end
|
45
|
+
if ((oStatsProxy.isCategoryIncluded?('Video plays')) or
|
46
|
+
(oStatsProxy.isCategoryIncluded?('Video comments')) or
|
47
|
+
(oStatsProxy.isCategoryIncluded?('Video likes')) or
|
48
|
+
(oStatsProxy.isCategoryIncluded?('Video rating')))
|
49
|
+
getVideos(oStatsProxy, lMechanizeAgent)
|
50
|
+
end
|
51
|
+
if ((oStatsProxy.isCategoryIncluded?('Blog reads')) or
|
52
|
+
(oStatsProxy.isCategoryIncluded?('Blog likes')))
|
53
|
+
getBlogs(oStatsProxy, lMechanizeAgent, iConf)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Get the profile statistics
|
58
|
+
#
|
59
|
+
# Parameters:
|
60
|
+
# * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
|
61
|
+
# * *iMechanizeAgent* (_Mechanize_): The agent reading pages
|
62
|
+
def getProfile(oStatsProxy, iMechanizeAgent)
|
63
|
+
# Click on the Profile link from the home page
|
64
|
+
lProfilePage = iMechanizeAgent.get('http://www.myspace.com/home').link_with(:text => 'Profile').click
|
65
|
+
# Screen scrap it
|
66
|
+
lNbrComments = Integer(lProfilePage.root.css('div.commentsModule > div > div > div > div.moduleBody > div.genericComments > a.moreComments > span.cnt').first.content.match(/of (\d*)/)[1])
|
67
|
+
oStatsProxy.addStat('Global', 'Comments', lNbrComments)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Get the dashboard statistics
|
71
|
+
#
|
72
|
+
# Parameters:
|
73
|
+
# * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
|
74
|
+
# * *iMechanizeAgent* (_Mechanize_): The agent reading pages
|
75
|
+
def getDashboard(oStatsProxy, iMechanizeAgent)
|
76
|
+
# Get the dashboard page
|
77
|
+
lJSonData = eval(iMechanizeAgent.get_file('http://www.myspace.com/stats/fans_json/profile_stats/en-US/x=0').gsub(':','=>'))
|
78
|
+
lNbrVisits = lJSonData['data'].select { |iItem| next (iItem[0] == 'myspace_views') }.first[-1]
|
79
|
+
lNbrFriends = lJSonData['data'].select { |iItem| next (iItem[0] == 'myspace_friends') }.first[-1]
|
80
|
+
oStatsProxy.addStat('Global', 'Visits', lNbrVisits)
|
81
|
+
oStatsProxy.addStat('Global', 'Friends', lNbrFriends)
|
82
|
+
|
83
|
+
# OLD VERSION (keeping it as Myspace changes all the time
|
84
|
+
# lDashboardPage = iMechanizeAgent.get('http://www.myspace.com/music/dashboard')
|
85
|
+
# # Get the variables used by the Ajax script
|
86
|
+
# lMatch = lDashboardPage.root.css('section.moduleBody script').first.content.split("\n").join.match(/var appID = "([^"]*)".*var pkey = "([^"]*)";/)
|
87
|
+
# lAppID, lPKey = lMatch[1..2]
|
88
|
+
# lCoreUserID = nil
|
89
|
+
# lDashboardPage.root.css('body script').each do |iScriptNode|
|
90
|
+
# lMatch = iScriptNode.content.split("\n").join.match(/var coreUserId =(\d*)/)
|
91
|
+
# if (lMatch != nil)
|
92
|
+
# # Found it
|
93
|
+
# lCoreUserID = lMatch[1]
|
94
|
+
# break
|
95
|
+
# end
|
96
|
+
# end
|
97
|
+
# if (lCoreUserID == nil)
|
98
|
+
# logErr "Unable to find the core user ID: #{lDashboardPage.root}"
|
99
|
+
# else
|
100
|
+
# # Call the Ajax script
|
101
|
+
# lStatsAjaxContent = iMechanizeAgent.get_file("http://www.myspace.com/Modules/Music/Handlers/Dashboard.ashx?sourceApplication=#{lAppID}&pkey=#{lPKey}&action=GETCORESTATS&userID=#{lCoreUserID}")
|
102
|
+
# lStrVisits, lStrFriends = lStatsAjaxContent.match(/^\{'totalprofileviews':'([^']*)','totalfriends':'([^']*)'/)[1..2]
|
103
|
+
# lNbrVisits = Integer(lStrVisits.delete(','))
|
104
|
+
# lNbrFriends = Integer(lStrFriends.delete(','))
|
105
|
+
# oStatsProxy.addStat('Global', 'Visits', lNbrVisits)
|
106
|
+
# oStatsProxy.addStat('Global', 'Friends', lNbrFriends)
|
107
|
+
# end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Get the songs statistics
|
111
|
+
#
|
112
|
+
# Parameters:
|
113
|
+
# * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
|
114
|
+
# * *iMechanizeAgent* (_Mechanize_): The agent reading pages
|
115
|
+
def getSongs(oStatsProxy, iMechanizeAgent)
|
116
|
+
# Get the songs page
|
117
|
+
lSongsPage = iMechanizeAgent.get('http://www.myspace.com/my/songs')
|
118
|
+
# Screen scrap it
|
119
|
+
# List of objects that can be tried again for the song plays category
|
120
|
+
lLstRecoverableObjectsForSongPlays = []
|
121
|
+
# List of songs read (used for display)
|
122
|
+
lLstSongsPlayRead = []
|
123
|
+
lSongsPage.root.css('div.UploadedSong').each do |iSongNode|
|
124
|
+
lSongTitle = nil
|
125
|
+
lNbrPlays = nil
|
126
|
+
iSongNode.css('div#songTitle').each do |iSongTitleNode|
|
127
|
+
lSongTitle = iSongTitleNode.content
|
128
|
+
end
|
129
|
+
lPlaysNode = iSongNode.children[11]
|
130
|
+
if (lPlaysNode == nil)
|
131
|
+
logErr "Unable to find plays node: #{iSongNode}"
|
132
|
+
else
|
133
|
+
begin
|
134
|
+
lNbrPlays = Integer(lPlaysNode.content)
|
135
|
+
rescue Exception
|
136
|
+
logErr "Invalid number of plays content: #{lPlaysNode}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
if (lSongTitle == nil)
|
140
|
+
logErr "Unable to get the song title: #{iSongNode}"
|
141
|
+
end
|
142
|
+
if (lNbrPlays == nil)
|
143
|
+
logErr "Unable to get the song number of plays: #{iSongNode}"
|
144
|
+
if (lSongTitle != nil)
|
145
|
+
# We can try this one again
|
146
|
+
lLstRecoverableObjectsForSongPlays << lSongTitle
|
147
|
+
end
|
148
|
+
end
|
149
|
+
if ((lSongTitle != nil) and
|
150
|
+
(lNbrPlays != nil))
|
151
|
+
oStatsProxy.addStat(lSongTitle, 'Song plays', lNbrPlays)
|
152
|
+
end
|
153
|
+
lLstSongsPlayRead << lSongTitle
|
154
|
+
end
|
155
|
+
logDebug "#{lLstSongsPlayRead.size} songs read for songs plays: #{lLstSongsPlayRead.join(', ')}"
|
156
|
+
if (!lLstRecoverableObjectsForSongPlays.empty?)
|
157
|
+
oStatsProxy.addRecoverableOrder(lLstRecoverableObjectsForSongPlays, ['Song plays'])
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Get the videos statistics
|
162
|
+
#
|
163
|
+
# Parameters:
|
164
|
+
# * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
|
165
|
+
# * *iMechanizeAgent* (_Mechanize_): The agent reading pages
|
166
|
+
def getVideos(oStatsProxy, iMechanizeAgent)
|
167
|
+
# Get the videos page
|
168
|
+
lVideosPage = iMechanizeAgent.get('http://www.myspace.com/my/videos')
|
169
|
+
# Screen scrap it
|
170
|
+
# List of videos read (used for display)
|
171
|
+
lLstVideosRead = []
|
172
|
+
lVideosPage.root.css('table.myUploadsList tr').each do |iVideoNode|
|
173
|
+
lVideoTitle = iVideoNode.css('td.summary h2 a').first.content
|
174
|
+
lStatsNodes = iVideoNode.css('td.controls div.text span')
|
175
|
+
lNbrPlays = Integer(lStatsNodes[1].content)
|
176
|
+
lNbrComments = Integer(lStatsNodes[2].content)
|
177
|
+
lMatch = lStatsNodes[3].content.match(/^(\d*)% \((\d*) vote/)
|
178
|
+
lRating = Integer(lMatch[1])
|
179
|
+
lNbrLikes = Integer(lMatch[2])
|
180
|
+
oStatsProxy.addStat(lVideoTitle, 'Video plays', lNbrPlays)
|
181
|
+
oStatsProxy.addStat(lVideoTitle, 'Video comments', lNbrComments)
|
182
|
+
oStatsProxy.addStat(lVideoTitle, 'Video likes', lNbrLikes)
|
183
|
+
oStatsProxy.addStat(lVideoTitle, 'Video rating', lRating)
|
184
|
+
lLstVideosRead << lVideoTitle
|
185
|
+
end
|
186
|
+
logDebug "#{lLstVideosRead.size} videos read: #{lLstVideosRead.join(', ')}"
|
187
|
+
end
|
188
|
+
|
189
|
+
# Get the blogs statistics
|
190
|
+
#
|
191
|
+
# Parameters:
|
192
|
+
# * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
|
193
|
+
# * *iMechanizeAgent* (_Mechanize_): The agent reading pages
|
194
|
+
# * *iConf* (<em>map<Symbol,Object></em>): The configuration
|
195
|
+
def getBlogs(oStatsProxy, iMechanizeAgent, iConf)
|
196
|
+
# TODO: Be able to get the list of blogs using MySpace only, without config
|
197
|
+
# Parse each blog ID given from the conf.
|
198
|
+
lLstBlogsRead = []
|
199
|
+
iConf[:BlogsID].each do |iBlogID|
|
200
|
+
# Get the blog page
|
201
|
+
lBlogPage = iMechanizeAgent.get("http://www.myspace.com/#{iConf[:MySpaceName]}/blog/#{iBlogID}")
|
202
|
+
lBlogTitle = lBlogPage.root.css('h2.post-title').first.content
|
203
|
+
lNbrLikes = 0
|
204
|
+
lStrLikes = lBlogPage.root.css('span.like span.likeLabel').first.content
|
205
|
+
if (!lStrLikes.empty?)
|
206
|
+
lNbrLikes = Integer(lStrLikes.match(/\((\d*)\)/)[1])
|
207
|
+
end
|
208
|
+
lNbrReads = 0
|
209
|
+
lStrReads = lBlogPage.root.css('li.blogCommentCnt span').first.content
|
210
|
+
if (!lStrReads.empty?)
|
211
|
+
lNbrReads = Integer(lStrReads.match(/\((\d*)\)/)[1])
|
212
|
+
end
|
213
|
+
oStatsProxy.addStat(lBlogTitle, 'Blog likes', lNbrLikes)
|
214
|
+
oStatsProxy.addStat(lBlogTitle, 'Blog reads', lNbrReads)
|
215
|
+
lLstBlogsRead << lBlogTitle
|
216
|
+
end
|
217
|
+
logDebug "#{lLstBlogsRead.size} blogs read: #{lLstBlogsRead.join(', ')}"
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
|
+
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
|
+
#++
|
5
|
+
|
6
|
+
module StatsCollect
|
7
|
+
|
8
|
+
module Locations
|
9
|
+
|
10
|
+
class ReverbNation
|
11
|
+
|
12
|
+
# Execute the plugin.
|
13
|
+
# This method has to add the stats and errors to the proxy.
|
14
|
+
# It can filter only objects and categories given.
|
15
|
+
# It has access to its configuration.
|
16
|
+
#
|
17
|
+
# Parameters:
|
18
|
+
# * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
|
19
|
+
# * *iConf* (<em>map<Symbol,Object></em>): The configuration associated to this plugin
|
20
|
+
# * *iLstObjects* (<em>list<String></em>): List of objects to filter (can be empty for all)
|
21
|
+
# * *iLstCategories* (<em>list<String></em>): List of categories to filter (can be empty for all)
|
22
|
+
def execute(oStatsProxy, iConf, iLstObjects, iLstCategories)
|
23
|
+
require 'mechanize'
|
24
|
+
lMechanizeAgent = Mechanize.new
|
25
|
+
lLoginForm = lMechanizeAgent.get('http://www.reverbnation.com/user/login').forms[2]
|
26
|
+
lLoginForm.field_with(:name => 'user[login]').value = iConf[:LoginEMail]
|
27
|
+
lLoginForm.field_with(:name => 'user[password]').value = iConf[:LoginPassword]
|
28
|
+
# Submit to get to the home page
|
29
|
+
lHomePage = lMechanizeAgent.submit(lLoginForm, lLoginForm.buttons.first)
|
30
|
+
# Get the user id, as it will be necessary for further requests
|
31
|
+
lUserID = lHomePage.uri.to_s.match(/^http:\/\/www\.reverbnation\.com\/artist\/control_room\/(\d*)$/)[1]
|
32
|
+
if ((oStatsProxy.isCategoryIncluded?('Song plays')) or
|
33
|
+
(oStatsProxy.isCategoryIncluded?('Song downloads')) or
|
34
|
+
(oStatsProxy.isCategoryIncluded?('Video plays')) or
|
35
|
+
(oStatsProxy.isCategoryIncluded?('Song play ratio')) or
|
36
|
+
(oStatsProxy.isCategoryIncluded?('Song likes')) or
|
37
|
+
(oStatsProxy.isCategoryIncluded?('Song dislikes')))
|
38
|
+
getPlays(oStatsProxy, lMechanizeAgent, lUserID)
|
39
|
+
end
|
40
|
+
if ((oStatsProxy.isObjectIncluded?('Global')) and
|
41
|
+
((oStatsProxy.isCategoryIncluded?('Chart position genre')) or
|
42
|
+
(oStatsProxy.isCategoryIncluded?('Chart position global')) or
|
43
|
+
(oStatsProxy.isCategoryIncluded?('Band equity')) or
|
44
|
+
(oStatsProxy.isCategoryIncluded?('Friends'))))
|
45
|
+
getReport(oStatsProxy, lMechanizeAgent, lUserID)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Get the plays statistics
|
50
|
+
#
|
51
|
+
# Parameters:
|
52
|
+
# * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
|
53
|
+
# * *iMechanizeAgent* (_Mechanize_): The agent reading pages
|
54
|
+
# * *iUserID* (_String_): The ReverbNation user ID
|
55
|
+
def getPlays(oStatsProxy, iMechanizeAgent, iUserID)
|
56
|
+
# Get the Ajax stats table
|
57
|
+
lStatsTable = iMechanizeAgent.get("http://www.reverbnation.com/artist/new_stats_plays_table/#{iUserID}?all_time=true")
|
58
|
+
lStatsTableNode = Nokogiri::HTML(lStatsTable.content[31..-4].gsub(/\\"/,'"').gsub(/\\n/,"\n").gsub(/\\r/,''))
|
59
|
+
# Screen scrap it
|
60
|
+
lLstSongsRead = []
|
61
|
+
lStatsTableNode.css('table.statstable_full tr')[1..-3].each do |iSongNode|
|
62
|
+
lNodeContents = iSongNode.css('td')
|
63
|
+
lSongTitle = lNodeContents[1].content
|
64
|
+
lNbrSongPlays = Integer(lNodeContents[3].content)
|
65
|
+
lNbrSongDownloads = Integer(lNodeContents[4].content)
|
66
|
+
lNbrVideoPlays = Integer(lNodeContents[5].content)
|
67
|
+
lPlayRatio = Integer(lNodeContents[6].content.match(/^(\d*)%$/)[1])
|
68
|
+
lMatch = lNodeContents[7].content.match(/^(\d*)\/(\d*)$/)
|
69
|
+
lNbrLikes = Integer(lMatch[1])
|
70
|
+
lNbrDislikes = Integer(lMatch[2])
|
71
|
+
oStatsProxy.addStat(lSongTitle, 'Song plays', lNbrSongPlays)
|
72
|
+
oStatsProxy.addStat(lSongTitle, 'Song downloads', lNbrSongDownloads)
|
73
|
+
oStatsProxy.addStat(lSongTitle, 'Video plays', lNbrVideoPlays)
|
74
|
+
oStatsProxy.addStat(lSongTitle, 'Song play ratio', lPlayRatio)
|
75
|
+
oStatsProxy.addStat(lSongTitle, 'Song likes', lNbrLikes)
|
76
|
+
oStatsProxy.addStat(lSongTitle, 'Song dislikes', lNbrDislikes)
|
77
|
+
lLstSongsRead << lSongTitle
|
78
|
+
end
|
79
|
+
logDebug "#{lLstSongsRead.size} songs read: #{lLstSongsRead.join(', ')}"
|
80
|
+
end
|
81
|
+
|
82
|
+
# Get the report statistics
|
83
|
+
#
|
84
|
+
# Parameters:
|
85
|
+
# * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
|
86
|
+
# * *iMechanizeAgent* (_Mechanize_): The agent reading pages
|
87
|
+
# * *iUserID* (_String_): The ReverbNation user ID
|
88
|
+
def getReport(oStatsProxy, iMechanizeAgent, iUserID)
|
89
|
+
# Get the report page
|
90
|
+
lReportPage = iMechanizeAgent.get("http://www.reverbnation.com/artist/artist_report_printable/#{iUserID}")
|
91
|
+
lChartPositionGenre = nil
|
92
|
+
lChartPositionGlobal = nil
|
93
|
+
lReportPage.root.css('body div#mainwrap div div div.control_room_graph_column').each do |iStatsSectionNode|
|
94
|
+
lChildrenNodes = iStatsSectionNode.children
|
95
|
+
lChildrenNodes.each_with_index do |iNode, iIdx|
|
96
|
+
if (iNode.content == 'Genre:')
|
97
|
+
lChartPositionGenre = Integer(lChildrenNodes[iIdx+1].content.strip)
|
98
|
+
elsif (iNode.content == 'Global:')
|
99
|
+
lChartPositionGlobal = Integer(lChildrenNodes[iIdx+1].content.strip)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
if ((lChartPositionGenre != nil) and
|
103
|
+
(lChartPositionGlobal != nil))
|
104
|
+
break
|
105
|
+
end
|
106
|
+
end
|
107
|
+
if (lChartPositionGenre == nil)
|
108
|
+
logErr "Unable to get the chart positions: #{lReportPage.root}"
|
109
|
+
else
|
110
|
+
lBandEquityScore = nil
|
111
|
+
lNbrFriends = nil
|
112
|
+
lReportPage.root.css('body div#mainwrap div div div.printable_pane').each do |iPaneNode|
|
113
|
+
lChildrenNodes = iPaneNode.children
|
114
|
+
lChildrenNodes.each_with_index do |iNode, iIdx|
|
115
|
+
if (iNode.content == 'Band Equity Score: ')
|
116
|
+
lBandEquityScore = Integer(lChildrenNodes[iIdx+1].content.strip)
|
117
|
+
elsif (iNode.content == 'Total Fans: ')
|
118
|
+
lNbrFriends = Integer(lChildrenNodes[iIdx+1].content.strip)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
if ((lBandEquityScore != nil) and
|
122
|
+
(lNbrFriends != nil))
|
123
|
+
break
|
124
|
+
end
|
125
|
+
end
|
126
|
+
if (lBandEquityScore == nil)
|
127
|
+
logErr "Unable to get the band equity score: #{lReportPage.root}"
|
128
|
+
elsif (lNbrFriends == nil)
|
129
|
+
logErr "Unable to get the number of friends: #{lReportPage.root}"
|
130
|
+
else
|
131
|
+
oStatsProxy.addStat('Global', 'Chart position genre', lChartPositionGenre)
|
132
|
+
oStatsProxy.addStat('Global', 'Chart position global', lChartPositionGlobal)
|
133
|
+
oStatsProxy.addStat('Global', 'Band equity', lBandEquityScore)
|
134
|
+
oStatsProxy.addStat('Global', 'Friends', lNbrFriends)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009-2010 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
|
+
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
|
+
#++
|
5
|
+
|
6
|
+
module StatsCollect
|
7
|
+
|
8
|
+
module Locations
|
9
|
+
|
10
|
+
class Tweets
|
11
|
+
|
12
|
+
# Execute the plugin.
|
13
|
+
# This method has to add the stats and errors to the proxy.
|
14
|
+
# It can filter only objects and categories given.
|
15
|
+
# It has access to its configuration.
|
16
|
+
#
|
17
|
+
# Parameters:
|
18
|
+
# * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
|
19
|
+
# * *iConf* (<em>map<Symbol,Object></em>): The configuration associated to this plugin
|
20
|
+
# * *iLstObjects* (<em>list<String></em>): List of objects to filter (can be empty for all)
|
21
|
+
# * *iLstCategories* (<em>list<String></em>): List of categories to filter (can be empty for all)
|
22
|
+
def execute(oStatsProxy, iConf, iLstObjects, iLstCategories)
|
23
|
+
require 'mechanize'
|
24
|
+
lMechanizeAgent = Mechanize.new
|
25
|
+
# Get the number of likes from Facebook
|
26
|
+
if (oStatsProxy.isCategoryIncluded?('Tweets'))
|
27
|
+
lFailedObjects = []
|
28
|
+
iConf[:Objects].each do |iObject|
|
29
|
+
if (oStatsProxy.isObjectIncluded?(iObject))
|
30
|
+
lTweetsData = lMechanizeAgent.get_file("http://urls.api.twitter.com/1/urls/count.json?url=#{iObject}").gsub(/:/,'=>')
|
31
|
+
if (lTweetsData.match(/Unable to access URL counting services/) != nil)
|
32
|
+
# An error occurred: we can try again
|
33
|
+
logErr "Error while fetching Tweets for #{iObject}: #{lTweetsData}"
|
34
|
+
lFailedObjects << iObject
|
35
|
+
else
|
36
|
+
lNbrTweets = eval(lTweetsData)['count']
|
37
|
+
oStatsProxy.addStat(iObject, 'Tweets', lNbrTweets)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
if (!lFailedObjects.empty?)
|
42
|
+
oStatsProxy.addRecoverableOrder(lFailedObjects, ['Tweets'])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|