StatsCollect 0.1.1.20101220 → 0.2.0.20110830
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +98 -0
- data/LICENSE +1 -1
- data/ReleaseInfo +8 -8
- data/StatsCollect.conf.rb.example +43 -2
- data/bin/StatsCollect.rb +1 -1
- data/lib/StatsCollect/Backends/MySQL.rb +253 -41
- data/lib/StatsCollect/Backends/Terminal.rb +39 -27
- data/lib/StatsCollect/Locations/AddThis.rb +2 -2
- data/lib/StatsCollect/Locations/CSV.rb +106 -0
- data/lib/StatsCollect/Locations/Facebook.rb +9 -5
- data/lib/StatsCollect/Locations/FacebookArtist.rb +4 -2
- data/lib/StatsCollect/Locations/FacebookLike.rb +1 -1
- data/lib/StatsCollect/Locations/GoogleGroup.rb +103 -0
- data/lib/StatsCollect/Locations/GoogleSearch.rb +1 -1
- data/lib/StatsCollect/Locations/MySpace.rb +63 -12
- data/lib/StatsCollect/Locations/RB.rb +64 -0
- data/lib/StatsCollect/Locations/ReverbNation.rb +34 -12
- data/lib/StatsCollect/Locations/Tweets.rb +1 -1
- data/lib/StatsCollect/Locations/Twitter.rb +1 -1
- data/lib/StatsCollect/Locations/Youtube.rb +12 -8
- data/lib/StatsCollect/Notifiers/Custom.rb +25 -0
- data/lib/StatsCollect/Notifiers/LogFile.rb +27 -0
- data/lib/StatsCollect/Notifiers/None.rb +1 -1
- data/lib/StatsCollect/Notifiers/SendMail.rb +1 -1
- data/lib/StatsCollect/Stats.rb +115 -74
- data/lib/StatsCollect/StatsOrdersProxy.rb +54 -0
- data/lib/StatsCollect/StatsProxy.rb +47 -6
- metadata +16 -10
- data/TODO +0 -2
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c)
|
2
|
+
# Copyright (c) 2010 - 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
3
|
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
4
|
#++
|
5
5
|
|
@@ -15,34 +15,26 @@ module StatsCollect
|
|
15
15
|
# * *iConf* (<em>map<Symbol,Object></em>): Configuration of this backend
|
16
16
|
def initSession(iConf)
|
17
17
|
@IdxID = 0
|
18
|
+
logMsg 'Session initialized.'
|
18
19
|
end
|
19
20
|
|
20
|
-
# Get the next stats
|
21
|
-
# Code to begin a new transaction can be set in this method too.
|
21
|
+
# Get the next stats orders.
|
22
22
|
#
|
23
|
-
#
|
24
|
-
# *
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
[ DateTime.now, [], [], [], STATS_ORDER_STATUS_TOBEPROCESSED ]
|
39
|
-
]
|
40
|
-
end
|
41
|
-
if (!@LstStatsOrders.empty?)
|
42
|
-
rTimeStamp, rLstLocations, rLstObjects, rLstCategories, rStatus = @LstStatsOrders.pop
|
43
|
-
end
|
44
|
-
|
45
|
-
return rTimeStamp, rLstLocations, rLstObjects, rLstCategories, rStatus
|
23
|
+
# Parameters:
|
24
|
+
# * *oStatsOrdersProxy* (_StatsOrdersProxy_): The stats orders proxy to be used to give stats orders
|
25
|
+
def getStatsOrders(oStatsOrdersProxy)
|
26
|
+
oStatsOrdersProxy.addStatsOrder(0, DateTime.now, [], [], [], STATS_ORDER_STATUS_TOBEPROCESSED)
|
27
|
+
# oStatsOrdersProxy.addStatsOrder(0, DateTime.now, ['MySpace'], [], ['Friends list'], STATS_ORDER_STATUS_TOBEPROCESSED)
|
28
|
+
logMsg 'Added stats order 0.'
|
29
|
+
end
|
30
|
+
|
31
|
+
# Dequeue the given stat orders IDs.
|
32
|
+
# Code to begin a new transaction can be set in this method too. In this case, the dequeue should be part of the transaction, or it will have to be re-enqueued during rollback method call (otherwise orders will be lost).
|
33
|
+
#
|
34
|
+
# Parameters:
|
35
|
+
# * *iLstStatsOrderIDs* (<em>list<Integer></em>): The list of stats order IDs to dequeue
|
36
|
+
def dequeueStatsOrders(iLstStatsOrderIDs)
|
37
|
+
logMsg "Transaction started and stats orders dequeued: #{iLstStatsOrderIDs.join(', ')}"
|
46
38
|
end
|
47
39
|
|
48
40
|
# Get the list of known locations
|
@@ -125,7 +117,23 @@ module StatsCollect
|
|
125
117
|
# * *iValue* (_Object_): The value to store
|
126
118
|
# * *iValueType* (_Integer_): The value type
|
127
119
|
def addStat(iTimeStamp, iLocationID, iObjectID, iCategoryID, iValue, iValueType)
|
128
|
-
logMsg "Added stat: #{iTimeStamp} | Location: #{iLocationID} | Object: #{iObjectID} | Category: #{iCategoryID} | Value: #{iValue}"
|
120
|
+
logMsg "Added stat: #{iTimeStamp} | Location: #{iLocationID} | Object: #{iObjectID} | Category: #{iCategoryID} (value type: #{iValueType}) | Value: #{iValue}"
|
121
|
+
end
|
122
|
+
|
123
|
+
# Get an existing stat value
|
124
|
+
#
|
125
|
+
# Parameters:
|
126
|
+
# * *iTimeStamp* (_DateTime_): The timestamp
|
127
|
+
# * *iLocationID* (_Integer_): The location ID
|
128
|
+
# * *iObjectID* (_Integer_): The object ID
|
129
|
+
# * *iCategoryID* (_Integer_): The category ID
|
130
|
+
# * *iValueType* (_Integer_): The value type
|
131
|
+
# Return:
|
132
|
+
# * _Object_: The corresponding value, or nil if none
|
133
|
+
def getStat(iTimeStamp, iLocationID, iObjectID, iCategoryID, iValueType)
|
134
|
+
logMsg "Get stat: #{iTimeStamp} | Location: #{iLocationID} | Object: #{iObjectID} | Category: #{iCategoryID} (value type: #{iValueType})"
|
135
|
+
|
136
|
+
return nil
|
129
137
|
end
|
130
138
|
|
131
139
|
# Add a new stats order
|
@@ -150,6 +158,10 @@ module StatsCollect
|
|
150
158
|
logMsg 'Transaction rollbacked'
|
151
159
|
end
|
152
160
|
|
161
|
+
# Close a session of this backend
|
162
|
+
def closeSession
|
163
|
+
end
|
164
|
+
|
153
165
|
end
|
154
166
|
|
155
167
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c)
|
2
|
+
# Copyright (c) 2010 - 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
3
|
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
4
|
#++
|
5
5
|
|
@@ -22,7 +22,7 @@ module StatsCollect
|
|
22
22
|
def execute(oStatsProxy, iConf, iLstObjects, iLstCategories)
|
23
23
|
require 'mechanize'
|
24
24
|
lMechanizeAgent = Mechanize.new
|
25
|
-
# Get the number of
|
25
|
+
# Get the number of shares
|
26
26
|
if (oStatsProxy.isCategoryIncluded?('Monthly shares'))
|
27
27
|
getDomains(oStatsProxy, lMechanizeAgent, iConf, 'month', 'Monthly shares')
|
28
28
|
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2010 - 2011 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 CSV
|
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
|
+
if (!iConf[:Files].empty?)
|
24
|
+
# Get the list of categories, locations and objects
|
25
|
+
lCategories = oStatsProxy.getCategories
|
26
|
+
lObjects = oStatsProxy.getObjects
|
27
|
+
lLocations = oStatsProxy.getLocations
|
28
|
+
lCSVLocations = []
|
29
|
+
lCSVObjects = []
|
30
|
+
lCSVCategories = []
|
31
|
+
require 'csv'
|
32
|
+
iConf[:Files].each do |iFileName|
|
33
|
+
lIdxLine = 0
|
34
|
+
lMissingIDs = false
|
35
|
+
::CSV::open(iFileName, 'r', :col_sep => iConf[:ColumnSeparator], :quote_char => iConf[:QuoteChar], :row_sep => iConf[:RowSeparator]).each do |iRow|
|
36
|
+
case lIdxLine
|
37
|
+
when 0
|
38
|
+
# We have locations in this line
|
39
|
+
iRow[1..-1].each do |iLocationName|
|
40
|
+
if (lLocations[iLocationName] == nil)
|
41
|
+
logWarn "Unknown location from CSV file #{iFileName}: #{iLocationName}"
|
42
|
+
lMissingIDs = true
|
43
|
+
end
|
44
|
+
lCSVLocations << iLocationName
|
45
|
+
end
|
46
|
+
when 1
|
47
|
+
# We have objects in this line
|
48
|
+
iRow[1..-1].each do |iObjectName|
|
49
|
+
if (lObjects[iObjectName] == nil)
|
50
|
+
logWarn "Unknown object from CSV file #{iFileName}: #{iObjectName}"
|
51
|
+
lMissingIDs = true
|
52
|
+
end
|
53
|
+
lCSVObjects << iObjectName
|
54
|
+
end
|
55
|
+
when 2
|
56
|
+
# We have categories in this line
|
57
|
+
iRow[1..-1].each do |iCategoryName|
|
58
|
+
if (lCategories[iCategoryName] == nil)
|
59
|
+
logWarn "Unknown category from CSV file #{iFileName}: #{iCategoryName}"
|
60
|
+
lMissingIDs = true
|
61
|
+
end
|
62
|
+
lCSVCategories << iCategoryName
|
63
|
+
end
|
64
|
+
else
|
65
|
+
if (lMissingIDs and
|
66
|
+
(iConf[:IDsMustExist]))
|
67
|
+
raise RuntimeError.new("Missing some IDs from CSV file #{iFileName}.")
|
68
|
+
end
|
69
|
+
# A line of data
|
70
|
+
lTimestamp = DateTime.strptime(iRow[0], iConf[:DateTimeFormat])
|
71
|
+
iRow[1..-1].each_with_index do |iStrValue, iIdx|
|
72
|
+
if (iStrValue != nil)
|
73
|
+
# Interpret the CSV value based on the value type of this column
|
74
|
+
lValueType = (lCategories[lCSVCategories[iIdx]] || STATS_VALUE_TYPE_UNKNOWN)
|
75
|
+
lValue = nil
|
76
|
+
case lValueType
|
77
|
+
when STATS_VALUE_TYPE_INTEGER
|
78
|
+
lValue = Integer(iStrValue)
|
79
|
+
when STATS_VALUE_TYPE_FLOAT
|
80
|
+
lValue = Float(iStrValue)
|
81
|
+
when STATS_VALUE_TYPE_PERCENTAGE
|
82
|
+
lValue = Float(iStrValue)
|
83
|
+
when STATS_VALUE_TYPE_UNKNOWN
|
84
|
+
lValue = iStrValue
|
85
|
+
when STATS_VALUE_TYPE_MAP
|
86
|
+
lValue = eval(iStrValue)
|
87
|
+
when STATS_VALUE_TYPE_STRING
|
88
|
+
lValue = iStrValue
|
89
|
+
else
|
90
|
+
raise RuntimeError.new("Unknown value type for category #{lCSVCategories[iIdx]}: #{lValueType}")
|
91
|
+
end
|
92
|
+
oStatsProxy.addStat(lCSVObjects[iIdx], lCSVCategories[iIdx], lValue, :Timestamp => lTimestamp, :Location => lCSVLocations[iIdx])
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
lIdxLine += 1
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c)
|
2
|
+
# Copyright (c) 2010 - 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
3
|
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
4
|
#++
|
5
5
|
|
@@ -22,6 +22,8 @@ module StatsCollect
|
|
22
22
|
def execute(oStatsProxy, iConf, iLstObjects, iLstCategories)
|
23
23
|
require 'mechanize'
|
24
24
|
lMechanizeAgent = Mechanize.new
|
25
|
+
# Set a specific user agent, as Facebook 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'
|
25
27
|
lLoginForm = lMechanizeAgent.get('http://www.facebook.com').forms[0]
|
26
28
|
lLoginForm.email = iConf[:LoginEMail]
|
27
29
|
lLoginForm.pass = iConf[:LoginPassword]
|
@@ -29,7 +31,7 @@ module StatsCollect
|
|
29
31
|
lMechanizeAgent.submit(lLoginForm, lLoginForm.buttons.first)
|
30
32
|
if ((oStatsProxy.isObjectIncluded?('Global')) and
|
31
33
|
(oStatsProxy.isCategoryIncluded?('Friends')))
|
32
|
-
getProfile(oStatsProxy, lMechanizeAgent)
|
34
|
+
getProfile(oStatsProxy, lMechanizeAgent, iConf)
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
@@ -38,11 +40,13 @@ module StatsCollect
|
|
38
40
|
# Parameters:
|
39
41
|
# * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
|
40
42
|
# * *iMechanizeAgent* (_Mechanize_): The agent reading pages
|
41
|
-
|
42
|
-
|
43
|
+
# * *iConf* (<em>map<Symbol,Object></em>): The configuration associated to this plugin
|
44
|
+
def getProfile(oStatsProxy, iMechanizeAgent, iConf)
|
45
|
+
lProfilePage = iMechanizeAgent.get("http://www.facebook.com/#{iConf[:URLID]}")
|
43
46
|
lNbrFriends = nil
|
44
47
|
lProfilePage.root.css('script').each do |iScriptNode|
|
45
|
-
lMatch = iScriptNode.content.match(
|
48
|
+
lMatch = iScriptNode.content.match(/sk=friends&v=friends\\">Friends \((\d*)\)/)
|
49
|
+
#lMatch = iScriptNode.content.match(/>Friends \((\d*)\)/)
|
46
50
|
# The following line is valid for old profiles only
|
47
51
|
#lMatch = iScriptNode.content.match(/>(\d*) friends<\\\/a><\\\/span>/)
|
48
52
|
if (lMatch != nil)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c)
|
2
|
+
# Copyright (c) 2010 - 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
3
|
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
4
|
#++
|
5
5
|
|
@@ -22,6 +22,8 @@ module StatsCollect
|
|
22
22
|
def execute(oStatsProxy, iConf, iLstObjects, iLstCategories)
|
23
23
|
require 'mechanize'
|
24
24
|
lMechanizeAgent = Mechanize.new
|
25
|
+
# Set a specific user agent, as Facebook 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'
|
25
27
|
lLoginForm = lMechanizeAgent.get('http://www.facebook.com').forms[0]
|
26
28
|
lLoginForm.email = iConf[:LoginEMail]
|
27
29
|
lLoginForm.pass = iConf[:LoginPassword]
|
@@ -43,7 +45,7 @@ module StatsCollect
|
|
43
45
|
lProfilePage = iMechanizeAgent.get("http://www.facebook.com/pages/#{iConf[:PageID]}")
|
44
46
|
lNbrLikes = nil
|
45
47
|
lProfilePage.root.css('script').each do |iScriptNode|
|
46
|
-
lMatch = iScriptNode.content.match(
|
48
|
+
lMatch = iScriptNode.content.match(/>\\u003cspan class=\\"uiNumberGiant fsxxl fwb\\">(\d*)\\u003c\\\/span>/)
|
47
49
|
if (lMatch != nil)
|
48
50
|
lNbrLikes = Integer(lMatch[1])
|
49
51
|
break
|
@@ -0,0 +1,103 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2010 - 2011 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 GoogleGroup
|
11
|
+
|
12
|
+
MEMBERSTATUS_INVITED = 0
|
13
|
+
MEMBERSTATUS_MEMBER = 1
|
14
|
+
MEMBERSTATUS_OWNER = 2
|
15
|
+
|
16
|
+
# Execute the plugin.
|
17
|
+
# This method has to add the stats and errors to the proxy.
|
18
|
+
# It can filter only objects and categories given.
|
19
|
+
# It has access to its configuration.
|
20
|
+
#
|
21
|
+
# Parameters:
|
22
|
+
# * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
|
23
|
+
# * *iConf* (<em>map<Symbol,Object></em>): The configuration associated to this plugin
|
24
|
+
# * *iLstObjects* (<em>list<String></em>): List of objects to filter (can be empty for all)
|
25
|
+
# * *iLstCategories* (<em>list<String></em>): List of categories to filter (can be empty for all)
|
26
|
+
def execute(oStatsProxy, iConf, iLstObjects, iLstCategories)
|
27
|
+
require 'mechanize'
|
28
|
+
lMechanizeAgent = Mechanize.new
|
29
|
+
lLoginForm = lMechanizeAgent.get('http://groups.google.com/').link_with(:text => 'Sign in').click.forms[0]
|
30
|
+
lLoginForm.Email = iConf[:LoginEMail]
|
31
|
+
lLoginForm.Passwd = iConf[:LoginPassword]
|
32
|
+
if (Mechanize::VERSION > '1.0.0')
|
33
|
+
lMechanizeAgent.submit(lLoginForm, lLoginForm.buttons.first).meta_refresh.first.click
|
34
|
+
else
|
35
|
+
lMechanizeAgent.submit(lLoginForm, lLoginForm.buttons.first).meta.first.click
|
36
|
+
end
|
37
|
+
iConf[:Objects].each do |iGroupName|
|
38
|
+
if (oStatsProxy.isCategoryIncluded?('Friends'))
|
39
|
+
getMembers(oStatsProxy, lMechanizeAgent, iGroupName)
|
40
|
+
end
|
41
|
+
if (oStatsProxy.isCategoryIncluded?('Friends list'))
|
42
|
+
getMembersList(oStatsProxy, lMechanizeAgent, iGroupName)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Get the members statistics
|
48
|
+
#
|
49
|
+
# Parameters:
|
50
|
+
# * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
|
51
|
+
# * *iMechanizeAgent* (_Mechanize_): The agent reading pages
|
52
|
+
# * *iGroupName* (_String_): Name of the group to retrieve members from
|
53
|
+
def getMembers(oStatsProxy, iMechanizeAgent, iGroupName)
|
54
|
+
lMembersPage = iMechanizeAgent.get("http://groups.google.com/group/#{iGroupName}/manage_members?hl=en")
|
55
|
+
lNbrFriends = Integer(lMembersPage.root.css('div.mngcontentbox table.membertabs tr td.st b').first.content.match(/All members \((\d*)\)/)[1])
|
56
|
+
oStatsProxy.addStat(iGroupName, 'Friends', lNbrFriends)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Get the members list
|
60
|
+
#
|
61
|
+
# Parameters:
|
62
|
+
# * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
|
63
|
+
# * *iMechanizeAgent* (_Mechanize_): The agent reading pages
|
64
|
+
# * *iGroupName* (_String_): Name of the group to retrieve members from
|
65
|
+
def getMembersList(oStatsProxy, iMechanizeAgent, iGroupName)
|
66
|
+
lExportForm = iMechanizeAgent.get("http://groups.google.com/group/#{iGroupName}/manage_members?hl=en").forms[4]
|
67
|
+
lLstMembers = iMechanizeAgent.submit(lExportForm, lExportForm.buttons.first).content.split("\n")[2..-1]
|
68
|
+
# The map of members
|
69
|
+
# map< Name, [ MemberStatus, JoinDateTime ] >
|
70
|
+
lMapMembers = {}
|
71
|
+
lLstMembers.each do |iStrMemberInfo|
|
72
|
+
lEmail, _, lStrStatus, _, _, _, lStrYear, lStrMonth, lStrDay, lStrHour, lStrMinute, lStrSecond = iStrMemberInfo.split(',')
|
73
|
+
lStatus = nil
|
74
|
+
case lStrStatus
|
75
|
+
when 'invited'
|
76
|
+
lStatus = MEMBERSTATUS_INVITED
|
77
|
+
when 'member'
|
78
|
+
lStatus = MEMBERSTATUS_MEMBER
|
79
|
+
when 'owner'
|
80
|
+
lStatus = MEMBERSTATUS_OWNER
|
81
|
+
else
|
82
|
+
logErr "Unknown member status (#{lStrStatus}) for email #{lEmail}. Will be counted as a member."
|
83
|
+
lStatus = MEMBERSTATS_MEMBER
|
84
|
+
end
|
85
|
+
lMapMembers[lEmail] = [
|
86
|
+
lStatus,
|
87
|
+
DateTime.civil(
|
88
|
+
lStrYear.to_i,
|
89
|
+
lStrMonth.to_i,
|
90
|
+
lStrDay.to_i,
|
91
|
+
lStrHour.to_i,
|
92
|
+
lStrMinute.to_i,
|
93
|
+
lStrSecond.to_i)
|
94
|
+
]
|
95
|
+
end
|
96
|
+
oStatsProxy.addStat(iGroupName, 'Friends list', lMapMembers)
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c)
|
2
|
+
# Copyright (c) 2010 - 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
3
|
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
4
|
#++
|
5
5
|
|
@@ -30,14 +30,17 @@ module StatsCollect
|
|
30
30
|
lLoginForm.Password = iConf[:LoginPassword]
|
31
31
|
# Submit to get to the home page
|
32
32
|
lMechanizeAgent.submit(lLoginForm, lLoginForm.buttons.first)
|
33
|
-
if (
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
33
|
+
if (oStatsProxy.isObjectIncluded?('Global'))
|
34
|
+
if (oStatsProxy.isCategoryIncluded?('Comments'))
|
35
|
+
getProfile(oStatsProxy, lMechanizeAgent)
|
36
|
+
end
|
37
|
+
if ((oStatsProxy.isCategoryIncluded?('Friends')) or
|
38
|
+
(oStatsProxy.isCategoryIncluded?('Visits')))
|
39
|
+
getDashboard(oStatsProxy, lMechanizeAgent)
|
40
|
+
end
|
41
|
+
if (oStatsProxy.isCategoryIncluded?('Friends list'))
|
42
|
+
getFriendsList(oStatsProxy, lMechanizeAgent)
|
43
|
+
end
|
41
44
|
end
|
42
45
|
if (oStatsProxy.isCategoryIncluded?('Song plays'))
|
43
46
|
getSongs(oStatsProxy, lMechanizeAgent)
|
@@ -63,7 +66,8 @@ module StatsCollect
|
|
63
66
|
# Click on the Profile link from the home page
|
64
67
|
lProfilePage = iMechanizeAgent.get('http://www.myspace.com/home').link_with(:text => 'Profile').click
|
65
68
|
# Screen scrap it
|
66
|
-
lNbrComments = Integer(lProfilePage.root.css('div.
|
69
|
+
lNbrComments = Integer(lProfilePage.root.css('article#module18 div.wrapper section.content div.commentContainer a.moreComments span.cnt').first.content.match(/of (\d*)/)[1])
|
70
|
+
|
67
71
|
oStatsProxy.addStat('Global', 'Comments', lNbrComments)
|
68
72
|
end
|
69
73
|
|
@@ -75,8 +79,8 @@ module StatsCollect
|
|
75
79
|
def getDashboard(oStatsProxy, iMechanizeAgent)
|
76
80
|
# Get the dashboard page
|
77
81
|
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]
|
82
|
+
lNbrVisits = Integer(lJSonData['data'].select { |iItem| next (iItem[0] == 'myspace_views') }.first[-1].gsub(',',''))
|
83
|
+
lNbrFriends = Integer(lJSonData['data'].select { |iItem| next (iItem[0] == 'myspace_friends') }.first[-1].gsub(',',''))
|
80
84
|
oStatsProxy.addStat('Global', 'Visits', lNbrVisits)
|
81
85
|
oStatsProxy.addStat('Global', 'Friends', lNbrFriends)
|
82
86
|
|
@@ -217,6 +221,53 @@ module StatsCollect
|
|
217
221
|
logDebug "#{lLstBlogsRead.size} blogs read: #{lLstBlogsRead.join(', ')}"
|
218
222
|
end
|
219
223
|
|
224
|
+
# Get the friends list
|
225
|
+
#
|
226
|
+
# Parameters:
|
227
|
+
# * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
|
228
|
+
# * *iMechanizeAgent* (_Mechanize_): The agent reading pages
|
229
|
+
def getFriendsList(oStatsProxy, iMechanizeAgent)
|
230
|
+
lFriendsPage = iMechanizeAgent.get('http://www.myspace.com/my/friends/grid/page/1')
|
231
|
+
# Keep track of the last first friend of the page, as we will detect ending page thanks to it.
|
232
|
+
lLastFirstFriend = nil
|
233
|
+
lFriendsMap = {}
|
234
|
+
lIdxPage = 2
|
235
|
+
while (lFriendsPage != nil)
|
236
|
+
lFirstFriend = nil
|
237
|
+
lFriendsPage.root.css('ul.myDataList li').each do |iFriendNode|
|
238
|
+
if (iFriendNode['data-id'] != nil)
|
239
|
+
# We have a friend node
|
240
|
+
lFriendID = iFriendNode['data-id']
|
241
|
+
lFriendName = nil
|
242
|
+
iFriendNode.css('div div.vcard span.hcard a.nickname').each do |iFriendLinkNode|
|
243
|
+
lFriendName = iFriendLinkNode['href'][1..-1]
|
244
|
+
if (lFriendName == nil)
|
245
|
+
logErr "Could not get friend's name for ID #{lFriendID}: #{iFriendLinkNode}"
|
246
|
+
end
|
247
|
+
lFriendsMap[lFriendID] = lFriendName
|
248
|
+
end
|
249
|
+
if (lFirstFriend == nil)
|
250
|
+
# Check if the page has not changed
|
251
|
+
if (lLastFirstFriend == lFriendID)
|
252
|
+
# Finished
|
253
|
+
break
|
254
|
+
end
|
255
|
+
lFirstFriend = lFriendID
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
lLastFirstFriend = lFirstFriend
|
260
|
+
# Get next page if we did not reach the end
|
261
|
+
if (lLastFirstFriend == nil)
|
262
|
+
lFriendsPage = nil
|
263
|
+
else
|
264
|
+
lFriendsPage = iMechanizeAgent.get("http://www.myspace.com/my/friends/grid/page/#{lIdxPage}")
|
265
|
+
lIdxPage += 1
|
266
|
+
end
|
267
|
+
end
|
268
|
+
oStatsProxy.addStat('Global', 'Friends list', lFriendsMap)
|
269
|
+
end
|
270
|
+
|
220
271
|
end
|
221
272
|
|
222
273
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2010 - 2011 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 RB
|
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
|
+
if (!iConf[:Files].empty?)
|
24
|
+
# Get the list of categories, locations and objects
|
25
|
+
lCategories = oStatsProxy.getCategories
|
26
|
+
lObjects = oStatsProxy.getObjects
|
27
|
+
lLocations = oStatsProxy.getLocations
|
28
|
+
iConf[:Files].each do |iFileName|
|
29
|
+
lLstStats = nil
|
30
|
+
File.open(iFileName, 'r') do |iFile|
|
31
|
+
lLstStats = eval(iFile.read)
|
32
|
+
end
|
33
|
+
lMissingIDs = false
|
34
|
+
logInfo "Read #{lLstStats.size} stats from RB file."
|
35
|
+
lLstStats.each do |ioStatInfo|
|
36
|
+
iCheckExistenceBeforeAdd, iTimeStamp, iLocationName, iObjectName, iCategoryName, iValue = ioStatInfo
|
37
|
+
ioStatInfo[1] = DateTime.strptime(iTimeStamp, iConf[:DateTimeFormat])
|
38
|
+
if (iConf[:IDsMustExist])
|
39
|
+
if (lLocations[iLocationName] == nil)
|
40
|
+
logWarn "Unknown location from RB file #{iFileName}: #{iLocationName}"
|
41
|
+
lMissingIDs = true
|
42
|
+
end
|
43
|
+
if (lObjects[iObjectName] == nil)
|
44
|
+
logWarn "Unknown object from RB file #{iFileName}: #{iObjectName}"
|
45
|
+
lMissingIDs = true
|
46
|
+
end
|
47
|
+
if (lCategories[iCategoryName] == nil)
|
48
|
+
logWarn "Unknown category from RB file #{iFileName}: #{iCategoryName}"
|
49
|
+
lMissingIDs = true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
if (!lMissingIDs)
|
54
|
+
oStatsProxy.addStatsList(lLstStats)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|