Compare commits
186 Commits
detach-rec
...
master
Author | SHA1 | Date | |
---|---|---|---|
ac5061e40d | |||
abc2752292 | |||
27a8a66dd3 | |||
8843a31d5d | |||
d6824d0c97 | |||
ae92892504 | |||
fd1de5971b | |||
03355de4f4 | |||
6e69fa5b3a | |||
635dc71ab5 | |||
57186f444f | |||
6d5caf6d6d | |||
43662d3fce | |||
7d77d26691 | |||
5336e8d224 | |||
69375d2683 | |||
3148f61eb9 | |||
625e66f006 | |||
f82728d159 | |||
5c82dd485a | |||
9348faaa91 | |||
1c551148fa | |||
4c8d269712 | |||
7b7fbceedc | |||
6b70551210 | |||
ff457ddbe7 | |||
ff72e94bb5 | |||
7bb4d66d57 | |||
482834e205 | |||
7c546c32b8 | |||
fa694f99a7 | |||
485572a81e | |||
ff38d34dc4 | |||
4d439283b1 | |||
7da6f87daf | |||
2cec4816a3 | |||
c91c793683 | |||
0af6e835b2 | |||
29b758c58a | |||
fdeecc5fde | |||
215e83d66e | |||
6548c70291 | |||
61eb15f8e3 | |||
6f821dfe53 | |||
5333b1c8d8 | |||
0b172e6f29 | |||
d4432bae7a | |||
fb00701e9e | |||
d2025d8aa9 | |||
1dec7081e4 | |||
33dccbc851 | |||
c8e7509e6a | |||
40d47caf1c | |||
7ddbed96bc | |||
3d7cc8110c | |||
68333ff0c0 | |||
dcb098e0c7 | |||
722b2d2066 | |||
07516e4dc6 | |||
773d94383c | |||
c535993e47 | |||
0577beb724 | |||
dc446f2026 | |||
8d9a66e012 | |||
8f3c63dc71 | |||
94b104e0e8 | |||
91744733bd | |||
c713ad9d28 | |||
3ad7148e62 | |||
5fdad7fa69 | |||
cb8bdbbaee | |||
e9d8745818 | |||
33805fd903 | |||
9544a92392 | |||
7a7058f30c | |||
cee2682e7a | |||
71484b16b0 | |||
f9b7fdf6bb | |||
75290c2538 | |||
bc6a4fb9eb | |||
7fa248e4fa | |||
f467129c42 | |||
7717f00185 | |||
9ae819722f | |||
7c6d7d9d80 | |||
463781085b | |||
a515a24a81 | |||
a61de2ecb7 | |||
742bb97446 | |||
bba0f9088d | |||
ac5d935ce1 | |||
0f62345220 | |||
624198091e | |||
33013dc52f | |||
390a6c3ef1 | |||
ac627f756c | |||
f898af0c05 | |||
fa1cf4a6d8 | |||
c16f109067 | |||
7d6c8259f6 | |||
defb1a8819 | |||
49f44385d7 | |||
c32a351932 | |||
459502cd5f | |||
4abb9fe987 | |||
a2d5d80e57 | |||
a08073232d | |||
35d9d037d3 | |||
ae652fdf3a | |||
21b4f432c4 | |||
419aa176ab | |||
6a628fddd3 | |||
aa5cf9cbba | |||
104fe9e348 | |||
06b0d5748d | |||
ac963c753f | |||
d2d0f739a8 | |||
ad26072420 | |||
bd69b73529 | |||
b964460fa3 | |||
a38124638d | |||
3ff8dfc86a | |||
bd279b1fd3 | |||
bbfae14949 | |||
f66d93e2ca | |||
57adfca53a | |||
66298fc248 | |||
cde55509bf | |||
e840f733fe | |||
1e06d16801 | |||
e43b375a6b | |||
e8373fab2c | |||
4e6b134848 | |||
99a03c0ad1 | |||
4ab8c6f0dd | |||
b92102a9d1 | |||
c2078d032f | |||
dc945dd8f9 | |||
2383ea4459 | |||
29c6deb617 | |||
3a58d281e2 | |||
a687e1b688 | |||
e98fd45083 | |||
906b29a7ef | |||
8d87ee3ed7 | |||
bfa2a2ab99 | |||
ad592beaf8 | |||
49d5529934 | |||
7de47a4959 | |||
ee9050a9b9 | |||
50a3ef901b | |||
5d457e824a | |||
f28e884a79 | |||
c9d9ec9620 | |||
1f081f58cf | |||
2de7cf1d11 | |||
986f4a83bc | |||
382dcd9954 | |||
0da5c06527 | |||
d61490e96c | |||
216832c3e2 | |||
a50e37dce1 | |||
4341383a8c | |||
f97b193c5e | |||
b9eedc6ec3 | |||
02ab2bd74a | |||
f563f964b2 | |||
8f2ed277fb | |||
a445100e58 | |||
bcf67809a8 | |||
2c61055b38 | |||
52b90dd8dd | |||
9b99ed0e33 | |||
db1a8e0502 | |||
bc168c4f0a | |||
65c36e924c | |||
89a73603a3 | |||
2c2ee3b132 | |||
94ce588376 | |||
01a62b2b2a | |||
36c261d534 | |||
a84e9504f9 | |||
d9af46ec37 | |||
e8f9e1ddd9 | |||
1e4f3d1190 | |||
b198bde247 |
@ -5,10 +5,13 @@ baseline: spec
|
||||
for: #common
|
||||
do: [
|
||||
"Dependencies"
|
||||
self xmlParserHTML: spec.
|
||||
"self rssTools: spec."
|
||||
"self xmlParserHTML: spec."
|
||||
self reStore: spec.
|
||||
self miniDocs: spec.
|
||||
"self roassal3Exporters: spec."
|
||||
"self tiddlyWikiPharo: spec."
|
||||
"Packages"
|
||||
spec
|
||||
package: 'Socialmetrica'
|
||||
with: [ spec requires: #('XMLParserHTML' "'RSSTools'") ]
|
||||
with: [ spec requires: #('ReStore' 'MiniDocs' "'XMLParserHTML' 'Roassal3Exporters' 'TiddlyWikiPharo'") ]
|
||||
]
|
@ -0,0 +1,8 @@
|
||||
accessing
|
||||
miniDocs: spec
|
||||
|
||||
| repo |
|
||||
repo := ExoRepo new
|
||||
repository: 'https://code.sustrato.red/Offray/MiniDocs'.
|
||||
repo load.
|
||||
spec baseline: 'MiniDocs' with: [ spec repository: 'gitlocal://', repo local fullName ]
|
@ -0,0 +1,10 @@
|
||||
baselines
|
||||
reStore: spec
|
||||
|
||||
Metacello new
|
||||
repository: 'github://rko281/ReStoreForPharo';
|
||||
baseline: 'ReStore';
|
||||
onConflict: [ :ex | ex useLoaded ];
|
||||
onWarningLog;
|
||||
load: 'all'.
|
||||
spec baseline: 'ReStore' with: [ spec repository: 'github://rko281/ReStoreForPharo']
|
@ -0,0 +1,7 @@
|
||||
baselines
|
||||
roassal3Exporters: spec
|
||||
Metacello new
|
||||
baseline: 'Roassal3Exporters';
|
||||
repository: 'github://ObjectProfile/Roassal3Exporters';
|
||||
load.
|
||||
spec baseline: 'Roassal3Exporters' with: [ spec repository: 'github://ObjectProfile/Roassal3Exporters']
|
@ -1,10 +0,0 @@
|
||||
baselines
|
||||
rssTools: spec
|
||||
Metacello new
|
||||
repository: 'github://brackendev/RSSTools-Pharo:v1.0.1/src';
|
||||
baseline: 'RSSTools';
|
||||
onConflict: [ :ex | ex useLoaded ];
|
||||
onUpgrade: [ :ex | ex useLoaded ];
|
||||
onDowngrade: [ :ex | ex useLoaded ];
|
||||
load.
|
||||
spec baseline: 'RSSTools' with: [ spec repository: 'github://brackendev/RSSTools-Pharo:v1.0.1/src']
|
@ -0,0 +1,8 @@
|
||||
baselines
|
||||
tiddlyWikiPharo: spec
|
||||
|
||||
| repo |
|
||||
repo := ExoRepo new
|
||||
repository: 'https://code.sustrato.red/Offray/TiddlyWikiPharo'.
|
||||
repo load.
|
||||
spec baseline: 'TiddlyWikiPharo' with: [ spec repository: 'gitlocal://', repo local fullName ]
|
@ -4,8 +4,6 @@ xmlParserHTML: spec
|
||||
baseline: 'XMLParserHTML';
|
||||
repository: 'github://pharo-contributions/XML-XMLParserHTML/src';
|
||||
onConflict: [ :ex | ex useLoaded ];
|
||||
onUpgrade: [ :ex | ex useLoaded ];
|
||||
onDowngrade: [ :ex | ex useLoaded ];
|
||||
onWarningLog;
|
||||
load.
|
||||
spec baseline: 'XMLParserHTML' with: [spec repository: 'github://pharo-contributions/XML-XMLParserHTML/src']
|
@ -6,7 +6,7 @@ To install, first install [ExoRepo](https://code.tupale.co/Offray/ExoRepo) and t
|
||||
|
||||
```smalltalk
|
||||
ExoRepo new
|
||||
repository: 'https://code.tupale.co/Offray/Socialmetrica';
|
||||
repository: 'https://code.sustrato.red/Offray/Socialmetrica';
|
||||
load.
|
||||
```
|
||||
|
||||
|
@ -0,0 +1,11 @@
|
||||
*Socialmetrica
|
||||
periodsSince: startingDate until: endingDate
|
||||
| borders subperiodDuration |
|
||||
subperiodDuration := (endingDate - startingDate) / self.
|
||||
borders := OrderedCollection new.
|
||||
borders add: startingDate.
|
||||
1 to: self do: [ :i | | ending |
|
||||
ending := startingDate + (subperiodDuration * i).
|
||||
borders add: ending.
|
||||
].
|
||||
^ borders
|
3
Socialmetrica.package/Integer.extension/properties.json
Normal file
3
Socialmetrica.package/Integer.extension/properties.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"name" : "Integer"
|
||||
}
|
0
Socialmetrica.package/Nitter.class/README.md
Normal file
0
Socialmetrica.package/Nitter.class/README.md
Normal file
@ -0,0 +1,8 @@
|
||||
accessing
|
||||
columnsDictionary
|
||||
^ {
|
||||
'url' -> 'url' .
|
||||
'healthy' -> 'healthy'.
|
||||
'healthy_percentage_overall' -> 'uptime' .
|
||||
'rss' -> 'rss' .
|
||||
'version' -> 'version'} asDictionary
|
@ -0,0 +1,4 @@
|
||||
accessing
|
||||
exportInstanceReport
|
||||
MiniDocs exportAsSton: self instances on: FileLocator temp / 'instances.ston'.
|
||||
^ FileLocator temp / 'instances.ston'
|
8
Socialmetrica.package/Nitter.class/class/instanceRows.st
Normal file
8
Socialmetrica.package/Nitter.class/class/instanceRows.st
Normal file
@ -0,0 +1,8 @@
|
||||
accessing
|
||||
instanceRows
|
||||
^ (self instances at: 'hosts') collect: [:rawRow | | newRow |
|
||||
newRow := NitterInstance new.
|
||||
self columnsDictionary keysAndValuesDo: [:key :value |
|
||||
newRow writeSlotNamed: value value: (rawRow at: key) ].
|
||||
newRow
|
||||
].
|
3
Socialmetrica.package/Nitter.class/class/instances.st
Normal file
3
Socialmetrica.package/Nitter.class/class/instances.st
Normal file
@ -0,0 +1,3 @@
|
||||
accessing
|
||||
instances
|
||||
^ self instanceRows
|
@ -0,0 +1,9 @@
|
||||
accessing
|
||||
instancesCache
|
||||
| cache cacheFile |
|
||||
cacheFile := FileLocator temp / 'nitter-instances.ston'.
|
||||
cacheFile exists
|
||||
ifFalse: [
|
||||
cache := STON fromString: 'https://status.d420.de/api/v1/instances' asUrl retrieveContents.
|
||||
MarkupFile exportAsFileOn: cacheFile containing: cache ].
|
||||
^ STON fromString: cacheFile contents
|
@ -0,0 +1,2 @@
|
||||
accessing
|
||||
instancesTable
|
@ -0,0 +1,21 @@
|
||||
accessing
|
||||
viewInstancesFor: aView
|
||||
<gtView>
|
||||
| columnedList columnNamesMap |
|
||||
self instances isEmptyOrNil ifTrue: [ ^ aView empty].
|
||||
columnedList := aView columnedList
|
||||
title: 'Instances';
|
||||
items: [ self instanceRows ];
|
||||
priority: 1.
|
||||
columnNamesMap := {
|
||||
'Instance' -> 'url'.
|
||||
'Healthy' -> 'healthy'.
|
||||
'Uptime %' -> 'uptime'.
|
||||
'RSS' -> 'rss'.
|
||||
'Nitter Version' -> 'version'} asOrderedDictionary.
|
||||
columnNamesMap keysAndValuesDo: [:aName :value |
|
||||
columnedList
|
||||
column: aName
|
||||
text: [:instanceRow | (instanceRow readSlotNamed: value) ifNil: [''] ]
|
||||
].
|
||||
^ columnedList
|
11
Socialmetrica.package/Nitter.class/properties.json
Normal file
11
Socialmetrica.package/Nitter.class/properties.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"commentStamp" : "",
|
||||
"super" : "Object",
|
||||
"category" : "Socialmetrica",
|
||||
"classinstvars" : [ ],
|
||||
"pools" : [ ],
|
||||
"classvars" : [ ],
|
||||
"instvars" : [ ],
|
||||
"name" : "Nitter",
|
||||
"type" : "normal"
|
||||
}
|
7
Socialmetrica.package/NitterInstance.class/README.md
Normal file
7
Socialmetrica.package/NitterInstance.class/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
I model a Nitter instance uptime & health tracker as described in https://status.d420.de/about.
|
||||
|
||||
Taken from the official documentation, in the previous linkm the fields we are modelling are:
|
||||
|
||||
- healthy: stands for hosts which are reachable and pass a content check.
|
||||
- rss: whether the host has RSS feeds enabled.
|
||||
- version: which nitter version the host reports.
|
@ -0,0 +1,3 @@
|
||||
accessing
|
||||
hasRSS
|
||||
^ self rss
|
@ -0,0 +1,3 @@
|
||||
accessing
|
||||
healthy
|
||||
^ healthy
|
@ -0,0 +1,3 @@
|
||||
accessing
|
||||
isHealthy
|
||||
^ self healthy
|
@ -0,0 +1,5 @@
|
||||
accessing
|
||||
printOn: aStream
|
||||
super printOn: aStream.
|
||||
^ aStream
|
||||
nextPutAll: '( ', self url ,' | uptime: ', self uptime asString, ' )'
|
@ -0,0 +1,3 @@
|
||||
accessing
|
||||
rss
|
||||
^ rss
|
@ -0,0 +1,3 @@
|
||||
accessing
|
||||
uptime
|
||||
^ uptime
|
@ -0,0 +1,3 @@
|
||||
accessing
|
||||
url
|
||||
^ url
|
17
Socialmetrica.package/NitterInstance.class/properties.json
Normal file
17
Socialmetrica.package/NitterInstance.class/properties.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"commentStamp" : "<historical>",
|
||||
"super" : "Object",
|
||||
"category" : "Socialmetrica",
|
||||
"classinstvars" : [ ],
|
||||
"pools" : [ ],
|
||||
"classvars" : [ ],
|
||||
"instvars" : [
|
||||
"url",
|
||||
"healthy",
|
||||
"uptime",
|
||||
"rss",
|
||||
"version"
|
||||
],
|
||||
"name" : "NitterInstance",
|
||||
"type" : "normal"
|
||||
}
|
@ -3,4 +3,4 @@ nitterProvider
|
||||
"For a full list of Nitter providers, see:
|
||||
|
||||
https://github.com/zedeus/nitter/wiki/Instances"
|
||||
^ 'https://nitter.42l.fr/'
|
||||
^ 'https://nitter.net/'
|
@ -0,0 +1,4 @@
|
||||
accessing
|
||||
areCommonFilesInstalled
|
||||
"Returns true if common files are in the proper location, and false elsewhere."
|
||||
^ ((TweetsCollection dataStore / 'commons') children collect: [ :file | file basename ] ) includesAll: self class commonFiles keys
|
@ -1,7 +1,29 @@
|
||||
accessing
|
||||
asDictionary
|
||||
|
||||
| tweets tweetsHistogramData repliesHistogramData quotesHistogramData retweetsHistogramData |
|
||||
[self config at: 'lang']
|
||||
onErrorDo: [ ^ self inform: 'Please put a lang key with a language (for example: en) in the object config.' ].
|
||||
tweets := self messages.
|
||||
tweetsHistogramData := self tweetsByWeeksTimeSpan.
|
||||
repliesHistogramData := self repliesByWeeksTimeSpan.
|
||||
quotesHistogramData := self quotesReportData.
|
||||
retweetsHistogramData := self retweetsReportData.
|
||||
^ { 'profile-card-avatar' -> self profileImageFile fullName.
|
||||
'profile-card-avatar-url' -> self profileImageUrl.
|
||||
'profile-card-fullname' -> self name .
|
||||
'profile-card-username' -> self userName .
|
||||
'profile-bio' -> self profileBio } asDictionary
|
||||
'profile-bio' -> self profileBio.
|
||||
'messages-size' -> tweets size.
|
||||
'messages-newest' -> tweets newest created asDate greaseString.
|
||||
'messages-oldest' -> tweets oldest created asDate greaseString.
|
||||
'tweets-histogram-labels' -> tweetsHistogramData third.
|
||||
'tweets-histogram-quantity' -> tweetsHistogramData second.
|
||||
'replies-histogram-labels' -> repliesHistogramData third.
|
||||
'replies-histogram-quantity' -> repliesHistogramData second.
|
||||
'retweets-histogram-labels' -> retweetsHistogramData third.
|
||||
'retweets-histogram-quantity' -> retweetsHistogramData second.
|
||||
'quotes-histogram-labels' -> quotesHistogramData third.
|
||||
'quotes-histogram-quantity' -> quotesHistogramData second.
|
||||
'wordcloud-data' -> (self wordcloudDataLanguage: (self config at: 'lang')) first.
|
||||
} asDictionary
|
@ -0,0 +1,28 @@
|
||||
accessing
|
||||
asDictionaryForWeb
|
||||
|
||||
| tweets tweetsHistogramData repliesHistogramData quotesHistogramData retweetsHistogramData |
|
||||
tweets := self messages.
|
||||
tweetsHistogramData := self tweetsByTimeSpan: 7.
|
||||
repliesHistogramData := self repliesByTimeSpan: 7.
|
||||
quotesHistogramData := self quotesReportData.
|
||||
retweetsHistogramData := self retweetsReportData.
|
||||
|
||||
^ { 'profile-card-avatar' -> self profileImageFile fullName.
|
||||
'profile-card-avatar-url' -> self profileImageUrl.
|
||||
'profile-card-fullname' -> self name .
|
||||
'profile-card-username' -> self userName .
|
||||
'profile-bio' -> self profileBio.
|
||||
'messages-size' -> tweets size.
|
||||
'messages-newest' -> tweets newest created asDate greaseString.
|
||||
'messages-oldest' -> tweets oldest created asDate greaseString.
|
||||
'tweets-histogram-labels' -> tweetsHistogramData third.
|
||||
'tweets-histogram-quantity' -> tweetsHistogramData second.
|
||||
'replies-histogram-labels' -> repliesHistogramData third.
|
||||
'replies-histogram-quantity' -> repliesHistogramData second.
|
||||
'retweets-histogram-labels' -> retweetsHistogramData third.
|
||||
'retweets-histogram-quantity' -> retweetsHistogramData second.
|
||||
'quotes-histogram-labels' -> quotesHistogramData third.
|
||||
'quotes-histogram-quantity' -> quotesHistogramData second.
|
||||
'wordcloud-data' -> self wordcloudData first.
|
||||
} asDictionary
|
16
Socialmetrica.package/NitterUser.class/instance/asTiddler.st
Normal file
16
Socialmetrica.package/NitterUser.class/instance/asTiddler.st
Normal file
@ -0,0 +1,16 @@
|
||||
accessing
|
||||
asTiddler
|
||||
|
||||
| tempDict tiddler |
|
||||
tiddler := Tiddler new.
|
||||
tiddler customFields.
|
||||
tempDict := self asDictionary.
|
||||
tempDict keysAndValuesDo: [ :key :value |
|
||||
tiddler customFields at: key put: value
|
||||
].
|
||||
^ tiddler
|
||||
created;
|
||||
title: (tiddler customFields at: 'profile-card-username');
|
||||
type: 'text/vnd.tiddlywiki';
|
||||
text:
|
||||
'<<image-card "', self profileImageUrl, '" title:', ($' asString), '<$transclude field="profile-card-fullname"/>', ($' asString), 'text:', ($' asString), '<$transclude field="profile-bio"/>', ($' asString), 'footer:', ($' asString), '@<$transclude field="profile-card-username"/>', ($' asString), 'align:"center" pos:"top">>'.
|
@ -0,0 +1,4 @@
|
||||
accessing
|
||||
authorIds
|
||||
|
||||
^ TwitterUser storedInstances select: [ :each | each userName = self userName ] thenCollect: [ :each | each id ]
|
@ -0,0 +1,12 @@
|
||||
accessing
|
||||
avatarPicture
|
||||
| response profileImgFile|
|
||||
profileImgFile := self profileImageFile.
|
||||
profileImgFile exists
|
||||
ifTrue: [ ^ (ImageReadWriter formFromFileNamed: profileImgFile fullName) asElement ].
|
||||
response := ZnClient new url: (self profileImageUrl); get; response.
|
||||
response contentType = ZnMimeType imageJpeg
|
||||
ifTrue: [ ^ (PluginBasedJPEGReadWriter gtFromBuffer: response contents) asElement ].
|
||||
response contentType = ZnMimeType imagePng
|
||||
ifTrue: [ ^ (PNGReadWriter gtFromBuffer: response contents) asElement ].
|
||||
^ GtABContact new avatar
|
@ -0,0 +1,33 @@
|
||||
accessing
|
||||
collectRawTweetsFrom: anUrl upToPage: anInteger
|
||||
|
||||
| pagesDict response customQuery |
|
||||
pagesDict := self getPagesContentsFrom: anUrl upTo: anInteger.
|
||||
response := TweetsCollection new.
|
||||
customQuery := Dictionary new
|
||||
at: 'parameters' put: pagesDict keys;
|
||||
at: 'date' put: DateAndTime now;
|
||||
yourself.
|
||||
response query: customQuery.
|
||||
pagesDict keysAndValuesDo: [ :key :rawTweets | | temp |
|
||||
temp := (rawTweets xpath: '//div[@class="timeline-item "]') asOrderedCollection
|
||||
collect: [ :xmlElement | xmlElement postCopy ].
|
||||
temp do: [ :tweet | | tempTweet |
|
||||
tempTweet := Tweet new fromNitterHtmlItem: tweet.
|
||||
tempTweet metadata
|
||||
at: DateAndTime now asString put: key;
|
||||
yourself.
|
||||
response add: tempTweet.
|
||||
]
|
||||
].
|
||||
response messages: (response messages select: [ :tweet | tweet isNotNil ]).
|
||||
response messages doWithIndex: [ :tweet :i |
|
||||
| current previous |
|
||||
current := response messages at: i.
|
||||
i < response lastIndex ifTrue: [
|
||||
previous := response messages at: i + 1.
|
||||
current timelines
|
||||
at: self userName put: previous id;
|
||||
yourself ]].
|
||||
^ response.
|
||||
|
@ -0,0 +1,5 @@
|
||||
accessing
|
||||
collectRawTweetsFromOldestUpToPage: anInteger
|
||||
|
||||
^ self collectRawTweetsFrom: self oldestTweetPageCursor upToPage: anInteger
|
||||
|
@ -0,0 +1,5 @@
|
||||
accessing
|
||||
collectRawTweetsUpToPage: anInteger
|
||||
|
||||
^ self collectRawTweetsFrom: self userNameLinkWithReplies upToPage: anInteger
|
||||
|
@ -0,0 +1,8 @@
|
||||
accessing
|
||||
configureDefaultReportingPeriod
|
||||
[ config at: 'reportingPeriod' ]
|
||||
onErrorDo: [ self config
|
||||
at: 'reportingPeriod'
|
||||
put: (Timespan
|
||||
starting: messages oldest created asDateAndTime
|
||||
ending: messages newest created asDateAndTime + 1 minute) ]
|
@ -2,5 +2,7 @@ accessing
|
||||
createdAt
|
||||
^ createdAt ifNil: [| joinDateString |
|
||||
joinDateString := ((self documentTree xpath: '//div[@class="profile-joindate"]/span/@title') stringValue).
|
||||
createdAt := (ZTimestampFormat fromString:'4:05 PM - 03 Feb 2001') parse: joinDateString.
|
||||
|
||||
createdAt := (ZTimestampFormat fromString:'4:05 PM - 3 Feb 2001') parse: joinDateString.
|
||||
createdAt := createdAt asDateAndTime
|
||||
]
|
@ -1,5 +0,0 @@
|
||||
accessing
|
||||
defaultConfig
|
||||
|
||||
self config: { 'folder' -> (FileLocator userData / 'Socialmetrica' / self userName) } asDictionary.
|
||||
^ self config
|
@ -1,3 +1,3 @@
|
||||
operation
|
||||
documentTree
|
||||
^ XMLHTMLParser parse: self userNameLink asUrl retrieveContents
|
||||
^ self documentTreeFor: self userNameLinkWithReplies
|
@ -0,0 +1,3 @@
|
||||
accessing
|
||||
documentTreeFor: anUrl
|
||||
^ XMLHTMLParser parse:anUrl asUrl retrieveContents
|
@ -1,4 +1,12 @@
|
||||
accessing
|
||||
downloadProfileImage
|
||||
|
||||
^ self exportProfileImageOn: self folder / 'profile-image', 'jpg'
|
||||
self remoteIsFound
|
||||
ifTrue: [ ^ self exportProfileImageOn: self folder / 'profile-image', 'jpg' ]
|
||||
ifFalse: [ | tempFile |
|
||||
tempFile := (self folder / 'profile-image', 'jpg') asFileReference.
|
||||
tempFile ensureCreateFile.
|
||||
tempFile binaryWriteStreamDo: [ :stream |
|
||||
stream nextPutAll: 'https://mutabit.com/repos.fossil/mutabit/uv/wiki/commons/twitter-user-image-default.jpg' asUrl retrieveContents.
|
||||
super class inform: 'Exported as: ', String cr, tempFile fullName.
|
||||
^ self folder / 'profile-image', 'jpg' ]]
|
@ -0,0 +1,40 @@
|
||||
as yet unclassified
|
||||
downloadWithRenaming: fileReference
|
||||
|
||||
| file tempFile fileHash tempFileHash |
|
||||
file := fileReference asFileReference .
|
||||
tempFile := FileLocator temp / fileReference basename.
|
||||
tempFile ensureCreateFile.
|
||||
tempFile binaryWriteStreamDo: [ :stream |
|
||||
stream nextPutAll: profileImageUrl asUrl retrieveContents.
|
||||
super class inform: 'Exported as: ', String cr, tempFile fullName.
|
||||
].
|
||||
|
||||
OSSUnixSubprocess new
|
||||
command: 'openssl';
|
||||
arguments: { 'dgst' . '-sha256' . file fullName};
|
||||
workingDirectory: (self folder) fullName;
|
||||
redirectStdout;
|
||||
redirectStderr;
|
||||
runAndWaitOnExitDo: [ :process :outString |
|
||||
fileHash := (outString splitOn: ' ') second trimmed].
|
||||
|
||||
OSSUnixSubprocess new
|
||||
command: 'openssl';
|
||||
arguments: { 'dgst' . '-sha256' . tempFile fullName};
|
||||
workingDirectory: (self folder) fullName;
|
||||
redirectStdout;
|
||||
redirectStderr;
|
||||
runAndWaitOnExitDo: [ :process :outString |
|
||||
tempFileHash := (outString splitOn: ' ' ) second trimmed].
|
||||
|
||||
fileHash = tempFileHash
|
||||
ifFalse: [
|
||||
file copyTo: self folder /
|
||||
(file basenameWithoutExtension , '-',
|
||||
('-' join:
|
||||
((file creationTime asLocalStringYMDHM) splitOn: ' ')), '.jpg').
|
||||
file ensureDelete.
|
||||
^ { 'Profile image changed' ->
|
||||
(tempFile moveTo: file)} asDictionary ].
|
||||
^ { 'Same Profile Image' -> file } asDictionary
|
@ -0,0 +1,6 @@
|
||||
accessing
|
||||
exportDefaultReport
|
||||
|
||||
(self hasFolder: 'templates')
|
||||
ifFalse: [ self installTemplate ].
|
||||
^ self exportWithTemplate: (TweetsCollection dataStore / 'templates' / 'template.mus.tex') into: self folder
|
@ -0,0 +1,9 @@
|
||||
accessing
|
||||
exportEmptyHistogramNamed: aDictionary
|
||||
|
||||
| histogram |
|
||||
histogram := RSChart new.
|
||||
histogram extent: (aDictionary at: 'extent').
|
||||
histogram build.
|
||||
histogram canvas exportAsFileNamed: (aDictionary at: 'messagesType'), '-histogram' into: self folder.
|
||||
^ self
|
@ -0,0 +1,49 @@
|
||||
accessing
|
||||
exportHistogramFor: aDictionary By: aTypeString
|
||||
"TODO: quotes and retweets"
|
||||
| messagesDict histogram diagram tempMessages labels subtotals |
|
||||
tempMessages := self perform: (aDictionary at: 'messagesType') asSymbol.
|
||||
|
||||
tempMessages ifEmpty: [ self exportEmptyHistogramNamed:
|
||||
(aDictionary at: 'messagesType'), '-histogram' ].
|
||||
|
||||
((((aDictionary at: 'messagesType') = 'tweets') or: [(aDictionary at: 'messagesType') = 'replies'])
|
||||
and: aTypeString isNumber)
|
||||
ifTrue: [ messagesDict := tempMessages splitBytimeSpansOf: aTypeString ].
|
||||
(aTypeString = 'day' or: [ aTypeString = 'days' ])
|
||||
ifTrue: [ messagesDict := tempMessages splitByDays ].
|
||||
(aTypeString = 'week' or: [ aTypeString = 'weeks' ])
|
||||
ifTrue: [ messagesDict := tempMessages splitByWeeks ].
|
||||
|
||||
(((aDictionary at: 'messagesType') = 'retweets') or: [ (aDictionary at: 'messagesType') = 'quotes' ])
|
||||
ifTrue: [
|
||||
messagesDict := tempMessages asMessagesUserNamesSortedByOccurrences.
|
||||
((aTypeString > messagesDict size) or: [ aTypeString = 1 ])
|
||||
ifFalse: [ | keysToRemove |
|
||||
keysToRemove := OrderedCollection new.
|
||||
1 to: messagesDict size - aTypeString do:
|
||||
[ :i | keysToRemove add: (messagesDict keys at: i + aTypeString) ].
|
||||
messagesDict removeKeys: keysToRemove. ].
|
||||
labels := messagesDict keys.
|
||||
labels := labels collect: [ :profiles | ('@', profiles) ].
|
||||
subtotals := messagesDict values
|
||||
]
|
||||
ifFalse: [ labels := messagesDict keys.
|
||||
subtotals := (messagesDict values collect: [ :collection | collection size ])].
|
||||
|
||||
histogram := RSChart new.
|
||||
histogram extent: (aDictionary at: 'extent').
|
||||
diagram := RSBarPlot new
|
||||
y: subtotals.
|
||||
diagram color: (aDictionary at: 'color').
|
||||
histogram addPlot: diagram.
|
||||
histogram addDecoration: (RSHorizontalTick new
|
||||
fromNames: labels;
|
||||
labelRotation: 0;
|
||||
fontSize: 68 /messagesDict size;
|
||||
yourself).
|
||||
histogram addDecoration: (RSVerticalTick new
|
||||
integer;
|
||||
fontSize: 68 /messagesDict size).
|
||||
histogram build.
|
||||
^ histogram canvas exportAsFileNamed: (aDictionary at: 'messagesType'), '-histogram' into: self folder
|
@ -0,0 +1,11 @@
|
||||
accessing
|
||||
exportOverviewReportLatex
|
||||
|
||||
| weeks floor divisions |
|
||||
weeks := ((self newestTweet created - self oldestTweet created) days / 7).
|
||||
floor := weeks floor.
|
||||
(weeks - floor) > 0.4
|
||||
ifTrue: [ divisions := floor ]
|
||||
ifFalse: [ divisions := floor + 1 ].
|
||||
self exportOverviewReportLatexWithBars: divisions.
|
||||
^ self folder
|
@ -0,0 +1,11 @@
|
||||
accessing
|
||||
exportOverviewReportLatexWithBars: anInteger
|
||||
|
||||
self
|
||||
exportDefaultReport;
|
||||
externalWordCloud;
|
||||
exportTweetsHistogramWithBars: anInteger;
|
||||
exportRetweetsHistogramWithBars: anInteger;
|
||||
exportRepliesHistogramWithBars: anInteger;
|
||||
exportQuotesHistogramWithBars: anInteger.
|
||||
^ self folder
|
@ -1,48 +1,13 @@
|
||||
accessing
|
||||
exportProfileImageOn: fileReference
|
||||
|
||||
| file tempFile tempFileHash fileHash |
|
||||
| file |
|
||||
file := fileReference asFileReference.
|
||||
file exists
|
||||
ifFalse: [ file ensureCreateFile.
|
||||
file exists ifFalse: [
|
||||
file ensureCreateFile.
|
||||
file binaryWriteStreamDo: [ :stream |
|
||||
stream nextPutAll: profileImageUrl retrieveContents ].
|
||||
super class inform: 'Exported as: ', String cr, file fullName.
|
||||
^ file]
|
||||
|
||||
ifTrue: [
|
||||
tempFile := FileLocator temp / fileReference basename.
|
||||
tempFile ensureCreateFile.
|
||||
tempFile binaryWriteStreamDo: [ :stream |
|
||||
stream nextPutAll: profileImageUrl retrieveContents.
|
||||
super class
|
||||
inform: 'Exported as: ', String cr, tempFile fullName. ].
|
||||
|
||||
OSSUnixSubprocess new
|
||||
command: 'openssl';
|
||||
arguments: { 'dgst' . '-sha256' . file fullName};
|
||||
workingDirectory: (self folder)fullName;
|
||||
redirectStdout;
|
||||
redirectStderr;
|
||||
runAndWaitOnExitDo: [ :process :outString |
|
||||
fileHash := (outString splitOn: ' ' ) second trimmed].
|
||||
|
||||
OSSUnixSubprocess new
|
||||
command: 'openssl';
|
||||
arguments: { 'dgst' . '-sha256' . tempFile fullName};
|
||||
workingDirectory: (self folder)fullName;
|
||||
redirectStdout;
|
||||
redirectStderr;
|
||||
runAndWaitOnExitDo: [ :process :outString |
|
||||
tempFileHash := (outString splitOn: ' ' ) second trimmed].
|
||||
|
||||
fileHash = tempFileHash
|
||||
ifFalse: [
|
||||
file copyTo: self folder /
|
||||
(file basenameWithoutExtension , '-',
|
||||
('-' join:
|
||||
((file creationTime asLocalStringYMDHM) splitOn: ' ')), '.jpg').
|
||||
file ensureDelete.
|
||||
^ { 'Profile image changed' ->
|
||||
(tempFile moveTo: file)} asDictionary ]].
|
||||
^ { 'Same Profile Image' -> file } asDictionary
|
||||
stream nextPutAll: profileImageUrl asUrl retrieveContents ].
|
||||
self class inform: 'Exported as: ', String cr, file fullName.
|
||||
^ file
|
||||
].
|
||||
self downloadWithRenaming: fileReference
|
||||
|
@ -0,0 +1,49 @@
|
||||
accessing
|
||||
exportQuotesHistogramWithBars: aNumberOfBars
|
||||
|
||||
| quotesDict |
|
||||
quotesDict := {
|
||||
'messagesType' -> 'quotes'.
|
||||
'extent' -> (800@200).
|
||||
'color' -> (Color r:(89/255) g:(217/255) b:(95/255))
|
||||
} asDictionary.
|
||||
^ self exportHistogramFor: quotesDict By: aNumberOfBars
|
||||
|
||||
|
||||
"| keysToRemove quotes labels quotesHistogram diagram |
|
||||
quotes := self quotes asMessagesUserNamesSortedByOccurrences.
|
||||
(aNumberOfBars > quotes size) ifTrue: [ ^ self exportQuotesHistogram ].
|
||||
keysToRemove := OrderedCollection new.
|
||||
1 to: quotes size - aNumberOfBars do:
|
||||
[ :i | keysToRemove add: (quotes keys at: i + aNumberOfBars) ].
|
||||
quotes removeKeys: keysToRemove.
|
||||
|
||||
labels := quotes keys.
|
||||
labels := labels collect: [ :profiles | ('@', profiles) ].
|
||||
quotesHistogram := RSChart new.
|
||||
quotesHistogram extent: 800@200.
|
||||
diagram := RSBarPlot new
|
||||
y: quotes values.
|
||||
diagram color: (Color r:(89/255) g:(217/255) b:(95/255)).
|
||||
quotesHistogram addPlot: diagram.
|
||||
quotesHistogram addDecoration: (RSHorizontalTick new
|
||||
fromNames: labels;
|
||||
labelRotation: 0;
|
||||
fontSize: 72 /quotes size;
|
||||
yourself).
|
||||
quotesHistogram addDecoration: (RSVerticalTick new
|
||||
asFloat: 2;
|
||||
fontSize: 72 /quotes size).
|
||||
quotesHistogram build.
|
||||
|
||||
quotesHistogram canvas pdfExporter
|
||||
zoomToShapes;
|
||||
noFixedShapes;
|
||||
fileName: (self folder / 'quotes-histogram.pdf')fullName;
|
||||
export.
|
||||
quotesHistogram canvas pngExporter
|
||||
zoomToShapes;
|
||||
noFixedShapes;
|
||||
fileName: (self folder / 'quotes-histogram.png')fullName;
|
||||
export.
|
||||
^ self folder / 'quotes-histogram.png'"
|
@ -0,0 +1,10 @@
|
||||
accessing
|
||||
exportRepliesHistogramWithBars: aNumberOfBars
|
||||
|
||||
| repliesDict |
|
||||
repliesDict := {
|
||||
'messagesType' -> 'replies'.
|
||||
'extent' -> (800@200).
|
||||
'color' -> (Color r:(246/255) g:(185/255) b:(46/255))
|
||||
} asDictionary.
|
||||
^ self exportHistogramFor: repliesDict By: aNumberOfBars
|
@ -0,0 +1,10 @@
|
||||
accessing
|
||||
exportRetweetsHistogramWithBars: aNumberOfBars
|
||||
|
||||
| retweetsDict |
|
||||
retweetsDict := {
|
||||
'messagesType' -> 'retweets'.
|
||||
'extent' -> (800@200).
|
||||
'color' -> (Color r:(217/255) g:(56/255) b: (124/255))
|
||||
} asDictionary.
|
||||
^ self exportHistogramFor: retweetsDict By: aNumberOfBars
|
@ -0,0 +1,8 @@
|
||||
accessing
|
||||
exportStaticWebReport
|
||||
|
||||
(self hasFolder: 'commons')
|
||||
ifFalse: [ self installCommons ].
|
||||
(self hasFolder: 'templates')
|
||||
ifFalse: [ self installTemplate ].
|
||||
^ self exportWithTemplate: (TweetsCollection dataStore / 'templates' / 'index.mus.html') into: self folder
|
@ -0,0 +1,10 @@
|
||||
accessing
|
||||
exportTweetsHistogramWithBars: aNumberOfBars
|
||||
|
||||
| tweetsDict |
|
||||
tweetsDict := {
|
||||
'messagesType' -> 'tweets'.
|
||||
'extent' -> (800@200).
|
||||
'color' -> (Color r:(91/255) g:(131/255) b:(222/255))
|
||||
} asDictionary.
|
||||
^ self exportHistogramFor: tweetsDict By: aNumberOfBars
|
@ -0,0 +1,11 @@
|
||||
accessing
|
||||
exportWeekReportLatexBeginningAt: aDay
|
||||
|
||||
self config
|
||||
at: 'reportingPeriod'
|
||||
put: (Timespan starting: aDay asDateAndTime ending: ( aDay asDateAndTime + 7 days)).
|
||||
self messages: self messages.
|
||||
self
|
||||
exportDefaultReport;
|
||||
externalWordCloud.
|
||||
^ self folder
|
@ -1,12 +1,29 @@
|
||||
accessing
|
||||
exportWithTemplate: mustacheFile into: folder
|
||||
|
||||
| tempDictionary modified |
|
||||
| tempDictionary bioModified userModified nameModified |
|
||||
tempDictionary := self asDictionary copy.
|
||||
modified := self asDictionary at: 'profile-bio'.
|
||||
modified := modified copyReplaceAll: '@' with: '\@'.
|
||||
modified := modified copyReplaceAll: '_' with: '\_'.
|
||||
tempDictionary at: 'profile-bio' put: modified.
|
||||
MarkupFile
|
||||
exportAsFileOn: (folder / self userName , 'tex')
|
||||
containing:(mustacheFile asMustacheTemplate value: tempDictionary)
|
||||
(mustacheFile fullName endsWith: '.html')
|
||||
ifTrue: [ ^ MarkupFile
|
||||
exportAsFileOn: (folder / self userName , 'html')
|
||||
containing:(mustacheFile asMustacheTemplate value: tempDictionary)].
|
||||
|
||||
(mustacheFile fullName endsWith: '.tex')
|
||||
ifTrue: [ bioModified := self asDictionary at: 'profile-bio'.
|
||||
bioModified := bioModified copyReplaceAll: '@' with: '\@'.
|
||||
bioModified := bioModified copyReplaceAll: '_' with: '\_'.
|
||||
bioModified := bioModified copyReplaceAll: '#' with: '\#'.
|
||||
bioModified := bioModified copyReplaceAll: '👑😎' with: ''.
|
||||
bioModified := bioModified copyReplaceAll: '🇨🇴' with: ''.
|
||||
bioModified := bioModified copyReplaceAll: '|' with: ''.
|
||||
userModified := self asDictionary at: 'profile-card-username'.
|
||||
userModified := userModified copyReplaceAll: '_' with: '\_'.
|
||||
nameModified := self asDictionary at: 'profile-card-fullname'.
|
||||
nameModified := nameModified copyReplaceAll: '🇨🇴' with: ''.
|
||||
tempDictionary at: 'profile-bio' put: bioModified;
|
||||
at: 'profile-card-username' put: userModified;
|
||||
at: 'profile-card-fullname' put: nameModified.
|
||||
^ MarkupFile
|
||||
exportAsFileOn: (folder / self userName , 'tex')
|
||||
containing:(mustacheFile asMustacheTemplate value: tempDictionary)]
|
||||
|
@ -1,9 +1,13 @@
|
||||
accessing
|
||||
externalWordCloud
|
||||
"TO DO: refactor with externalWordCloudWithLanguage:"
|
||||
|
||||
| text outputFile |
|
||||
outputFile := (self folder / 'nube.png')fullName.
|
||||
text := (self folder / self userName, 'words', 'txt')fullName.
|
||||
self areCommonFilesInstalled
|
||||
ifFalse: [ self installCommons ].
|
||||
self writeWordsFile.
|
||||
outputFile := (self folder / 'wordcloud.png') fullName.
|
||||
text := (self folder / 'words', 'txt') fullName.
|
||||
OSSUnixSubprocess new
|
||||
command: 'wordcloud_cli';
|
||||
arguments: { '--text' . text .
|
||||
@ -13,8 +17,9 @@ externalWordCloud
|
||||
'--height' . '357' .
|
||||
'--background' . 'white' .
|
||||
'--mode' . 'RGBA' .
|
||||
'--stopwords' . '../commons/stopwords-es.txt' .
|
||||
'--mask' . '../commons/nube-mascara.jpg'};
|
||||
'--stopwords' . '../../../commons/stopwords-es.txt'.
|
||||
"'--mask' . '../../../commons/nube-mascara.jpg'"
|
||||
};
|
||||
workingDirectory: self folder fullName;
|
||||
redirectStdout;
|
||||
redirectStderr;
|
||||
|
@ -0,0 +1,26 @@
|
||||
accessing
|
||||
externalWordCloudWithLanguage: language
|
||||
"I proces and render a wordcloud image without stopwords in: es and en."
|
||||
|
||||
| text outputFile |
|
||||
self areCommonFilesInstalled
|
||||
ifFalse: [ self installCommons ].
|
||||
self writeWordsFile.
|
||||
outputFile := (self folder / 'wordcloud.png') fullName.
|
||||
text := (self folder / 'words', 'txt') fullName.
|
||||
OSSUnixSubprocess new
|
||||
command: 'wordcloud_cli';
|
||||
arguments: { '--text' . text .
|
||||
'--imagefile' . outputFile .
|
||||
'--color' . '#5B83DE' .
|
||||
'--width' . '1153' .
|
||||
'--height' . '357' .
|
||||
'--background' . 'white' .
|
||||
'--mode' . 'RGBA' .
|
||||
'--stopwords' . '../../../commons/stopwords-', language, '.txt'.
|
||||
"'--mask' . '../../../commons/nube-mascara.jpg'"
|
||||
};
|
||||
workingDirectory: self folder fullName;
|
||||
redirectStdout;
|
||||
redirectStderr;
|
||||
runAndWaitOnExitDo: [ :process :outString | ^ outputFile asFileReference ]
|
@ -0,0 +1,14 @@
|
||||
accessing
|
||||
getLocalMessages
|
||||
|
||||
| allTweets myTweets tweetsWithAntecesor |
|
||||
|
||||
TweetsCollection storeDB.
|
||||
allTweets := Tweet storedInstances asOrderedCollection.
|
||||
allTweets ifNil: [ ^ nil ].
|
||||
myTweets := TweetsCollection new.
|
||||
tweetsWithAntecesor := allTweets select: [ :each | each timelines isNotEmpty and: [ each timelines keys first = self userName ]].
|
||||
myTweets messages: tweetsWithAntecesor.
|
||||
self messages: myTweets.
|
||||
^ self
|
||||
|
@ -1,24 +1,5 @@
|
||||
accessing
|
||||
getMessages
|
||||
| lastTweetsRaw customQuery lastTweets |
|
||||
lastTweetsRaw := self rssFeed xmlDocument xpath: '//item'.
|
||||
lastTweets := TweetsCollection new.
|
||||
customQuery := Dictionary new
|
||||
at: 'parameters' put: self userNameLink;
|
||||
at: 'date' put: DateAndTime now;
|
||||
yourself.
|
||||
lastTweets query: customQuery.
|
||||
lastTweetsRaw doWithIndex: [ :rssTweet :i | | current previous |
|
||||
current := Tweet new fromNitterRssItem: rssTweet.
|
||||
i < lastTweetsRaw size ifTrue: [
|
||||
previous := Tweet new fromNitterRssItem: (lastTweetsRaw at: i + 1).
|
||||
current timelines
|
||||
at: self userName put: previous id;
|
||||
yourself.
|
||||
].
|
||||
current queries add: customQuery.
|
||||
lastTweets add: current.
|
||||
].
|
||||
self tweets: lastTweets.
|
||||
^ self tweets
|
||||
|
||||
|
||||
self getLocalMessages ifNil: [ self getRemoteMessagesFromHtml ].
|
||||
^ self messages
|
@ -0,0 +1,19 @@
|
||||
accessing
|
||||
getPagesContentsFrom: anURL upTo: anInteger
|
||||
"I retroactively get all pages contents until a specified page number.
|
||||
|
||||
TO DO: should this be splitted back to two methods, one getting the page urls and other its content?
|
||||
or do we always be getting the cursor urls and its contents all the time.
|
||||
[ ] Benchmark alternative approaches."
|
||||
| response nextPageLink previousPageLink |
|
||||
|
||||
response := OrderedDictionary new.
|
||||
response at: anURL put: (self documentTreeFor: anURL).
|
||||
previousPageLink := anURL.
|
||||
anInteger - 1 timesRepeat: [ | pageCursor |
|
||||
pageCursor := self pageCursorFor:previousPageLink.
|
||||
nextPageLink := self userNameLink, '/with_replies', pageCursor keys first.
|
||||
response at: nextPageLink put: (XMLHTMLParser parse:nextPageLink asUrl retrieveContents).
|
||||
previousPageLink := nextPageLink
|
||||
].
|
||||
^ response
|
@ -0,0 +1,4 @@
|
||||
accessing
|
||||
getPagesContentsFromOldestUpto: anInteger
|
||||
|
||||
^ self getPagesContentsFrom: ((self oldestTweet metadata select: [ :item | item isString and: [ item beginsWith: 'https://' ]]) values first) upTo: anInteger
|
@ -0,0 +1,8 @@
|
||||
accessing
|
||||
getPagesContentsUpto: anInteger
|
||||
"I retroactively get all pages contents until a specified page number.
|
||||
|
||||
TO DO: should this be splitted back to two methods, one getting the page urls and other its content?
|
||||
or do we always be getting the cursor urls and its contents all the time.
|
||||
[ ] Benchmark alternative approaches."
|
||||
^ self getPagesContentsFrom: self userNameLinkWithReplies upTo: anInteger
|
@ -0,0 +1,4 @@
|
||||
accessing
|
||||
getRemoteMessagesFromHtml
|
||||
|
||||
^ messages := self collectRawTweetsUpToPage: 1
|
@ -0,0 +1,6 @@
|
||||
accessing
|
||||
hasFolder: folderName
|
||||
|
||||
| fullFolderPath |
|
||||
fullFolderPath :=(TweetsCollection dataStore / folderName ).
|
||||
^ fullFolderPath exists and: [ fullFolderPath children isNotEmpty ]
|
@ -1,3 +1,7 @@
|
||||
accessing
|
||||
id
|
||||
^ id ifNil: [ id := (self profileImageUrl asUrl segments select: [ :each | each isAllDigits ]) first.]
|
||||
^ id ifNil: [
|
||||
self profileImageUrl
|
||||
ifNil: [ id := 0 ]
|
||||
ifNotNil: [ id := (self profileImageUrl asUrl segments select: [ :each | each isAllDigits ]) first. ]
|
||||
]
|
@ -0,0 +1,22 @@
|
||||
accessing
|
||||
installCommons
|
||||
|
||||
| commonFiles folder |
|
||||
commonFiles := #(
|
||||
'https://mutabit.com/repos.fossil/mutabit/raw?name=wiki/commons/stopwords-es.txt&ci=tip'
|
||||
'https://mutabit.com/repos.fossil/mutabit/raw?name=wiki/commons/stopwords-en.txt&ci=tip'
|
||||
'https://mutabit.com/repos.fossil/mutabit/uv/wiki/commons/nube-mascara.jpg'
|
||||
'https://mutabit.com/repos.fossil/mutabit/uv/wiki/commons/logo-mutabit-negro.png').
|
||||
folder := TweetsCollection dataStore / 'commons'.
|
||||
folder exists
|
||||
ifTrue: [ folder ensureDeleteAllChildren ]
|
||||
ifFalse: [ folder ensureCreateDirectory].
|
||||
commonFiles do: [ :fileUrl | | temp |
|
||||
ZnClient new
|
||||
url: fileUrl;
|
||||
downloadTo: folder.
|
||||
temp := (folder children select: [ :file | file basename includesSubstring: 'raw' ]).
|
||||
temp isNotEmpty ifTrue: [
|
||||
temp first renameTo: (((fileUrl splitOn: 'raw?') second splitOn: '/') last removeSuffix: '&ci=tip')].
|
||||
].
|
||||
^ folder
|
@ -0,0 +1,22 @@
|
||||
accessing
|
||||
installTemplate
|
||||
|
||||
| templateFiles folder |
|
||||
templateFiles := #(
|
||||
'https://mutabit.com/repos.fossil/mutabit/raw?name=plantillas/TwentySecondsCV/twentysecondcvMod.cls&ci=tip'
|
||||
'https://mutabit.com/repos.fossil/mutabit/raw?name=plantillas/TwentySecondsCV/template.mus.tex&ci=tip'
|
||||
'https://mutabit.com/repos.fossil/mutabit/raw?name=plantillas/SarissaPersonalBlog/index.mus.html&ci=tip'
|
||||
'https://mutabit.com/repos.fossil/mutabit/raw?name=plantillas/SarissaPersonalBlog/output.css&ci=tip'
|
||||
'https://mutabit.com/repos.fossil/mutabit/raw?name=plantillas/SarissaPersonalBlog/echarts-wordcloud.min.js&ci=tip').
|
||||
folder := TweetsCollection dataStore / 'templates'.
|
||||
folder exists
|
||||
ifTrue: [ folder ensureDeleteAllChildren ]
|
||||
ifFalse: [ folder ensureCreateDirectory].
|
||||
templateFiles do: [ :fileUrl |
|
||||
ZnClient new
|
||||
url: fileUrl;
|
||||
downloadTo: folder.
|
||||
(folder children detect: [ :file | file basename includesSubstring: 'raw' ])
|
||||
renameTo: (((fileUrl splitOn: 'raw?') second splitOn: '/') last removeSuffix: '&ci=tip')
|
||||
].
|
||||
^ folder
|
@ -0,0 +1,5 @@
|
||||
accessing
|
||||
lastTweetsFromHtml
|
||||
|
||||
^ (self documentTree xpath: '//div[@class="timeline-item "]')
|
||||
asOrderedCollection collect: [ :xmlElement | xmlElement postCopy ]
|
12
Socialmetrica.package/NitterUser.class/instance/messages.st
Normal file
12
Socialmetrica.package/NitterUser.class/instance/messages.st
Normal file
@ -0,0 +1,12 @@
|
||||
accessing
|
||||
messages
|
||||
messages ifNil: [ messages := TweetsCollection new ].
|
||||
messages ifEmpty: [ self getLocalMessages ].
|
||||
messages ifEmpty: [ self getRemoteMessagesFromHtml ].
|
||||
config at: 'reportingPeriod' ifAbsent: [ ^ messages ].
|
||||
"self configureDefaultReportingPeriod."
|
||||
^ messages
|
||||
select: [ :message |
|
||||
message created
|
||||
between: self reportingPeriod start
|
||||
and: self reportingPeriod end ]
|
@ -1,3 +1,6 @@
|
||||
accessing
|
||||
name
|
||||
^ name ifNil: [ name := ((self rssFeed requiredItems title) splitOn: '/') first ]
|
||||
|
||||
| documentTree |
|
||||
documentTree := [ self documentTree ] onErrorDo: [ ^ nil ].
|
||||
^ name ifNil: [ name := (documentTree xpath: '//div[@class="profile-card-tabs-name"]//a[@class="profile-card-fullname"]') stringValue ]
|
@ -0,0 +1,4 @@
|
||||
accessing
|
||||
newestTweet
|
||||
|
||||
^ (self tweets select: [ :tweet | tweet created = ((self tweets collect: [ :each | each created ]) asSortedCollection last)]) first.
|
@ -1,18 +0,0 @@
|
||||
accessing
|
||||
numberOfURLsForLoadingTweets: number
|
||||
|
||||
| collectionURLs count asURLs |
|
||||
collectionURLs := {
|
||||
self userNameLink .
|
||||
(self userNameLink, ((self documentTree xPath: '//a[.="Load more"]') @ 'href') stringValue) .} asOrderedCollection.
|
||||
|
||||
number <= 2 ifTrue: [ ^ collectionURLs ].
|
||||
count := 2.
|
||||
(number-count) timesRepeat: [ | tempDoc |
|
||||
tempDoc := XMLHTMLParser parse: (collectionURLs at: count) asUrl retrieveContents.
|
||||
collectionURLs
|
||||
add: (self userNameLink,
|
||||
((tempDoc xPath: '//a[.="Load more"]') @ 'href') stringValue).
|
||||
count := count+1 ].
|
||||
asURLs := collectionURLs collect: [ :string | string asUrl ].
|
||||
^ asURLs.
|
@ -0,0 +1,4 @@
|
||||
accessing
|
||||
oldestTweet
|
||||
|
||||
^ (self tweets select: [ :tweet | tweet created = ((self tweets collect: [ :each | each created ]) asSortedCollection first)]) first.
|
@ -0,0 +1,3 @@
|
||||
accessing
|
||||
oldestTweetPageCursor
|
||||
^ (self oldestTweet metadata select: [ :item | item isString and: [ item beginsWith: 'https://' ]]) values first value
|
@ -0,0 +1,10 @@
|
||||
accessing
|
||||
pageCursorFor: anUrl
|
||||
|
||||
| response value key |
|
||||
response := Dictionary new.
|
||||
value := self documentTreeFor: anUrl.
|
||||
key := ((value xpath: '//a[.="Load more"]') @ 'href')stringValue.
|
||||
^ response
|
||||
at: key put: value;
|
||||
yourself
|
@ -0,0 +1,14 @@
|
||||
accessing
|
||||
pageDocTrees: anInteger
|
||||
|
||||
| response nextPageLink previousPageLink |
|
||||
|
||||
response := OrderedDictionary new.
|
||||
previousPageLink := self userNameLink.
|
||||
response add: previousPageLink.
|
||||
anInteger - 1 timesRepeat: [
|
||||
nextPageLink := self userNameLink, (self pageCursorFor:previousPageLink) value.
|
||||
response add: nextPageLink.
|
||||
previousPageLink := nextPageLink
|
||||
].
|
||||
^ response
|
@ -2,6 +2,8 @@ accessing
|
||||
profileImageFile
|
||||
|
||||
| file |
|
||||
file := (self folder / self userName, 'jpg').
|
||||
file exists ifTrue: [ ^ file ].
|
||||
file := (self folder / 'profile-image', 'jpg').
|
||||
file exists ifTrue: [ file asFileReference size = 0
|
||||
ifTrue:[ ^ self downloadProfileImage ].
|
||||
^ file. ].
|
||||
^ self downloadProfileImage
|
@ -1,4 +1,8 @@
|
||||
accessing
|
||||
profileImageUrl
|
||||
^ profileImageUrl ifNil: [
|
||||
profileImageUrl := ((self rssFeed xmlDocument xpath: '//image/url') stringValue copyReplaceAll: '%2F' with: '/') ]
|
||||
|
||||
| documentTree |
|
||||
self remoteIsFound
|
||||
ifFalse:[ ^ nil ].
|
||||
documentTree := self documentTree.
|
||||
^ profileImageUrl := self class nitterProvider, (((documentTree xpath: '//div[@class="profile-card-info"]//a[@class="profile-card-avatar"]') @ 'href') stringValue copyReplaceAll: '%2F' with: '/') copyWithoutFirst
|
@ -0,0 +1,9 @@
|
||||
accessing
|
||||
quotes
|
||||
|
||||
self messages ifEmpty: [ self getMessages ].
|
||||
^ TweetsCollection new
|
||||
messages: (self messages
|
||||
select: [ :each |
|
||||
(each metadata at: 'quote') isNotEmpty and: [ each user userName = self userName] ]);
|
||||
yourself
|
@ -0,0 +1,22 @@
|
||||
accessing
|
||||
quotesReportData
|
||||
|
||||
| tempDict labels xAxis |
|
||||
self quotes isEmpty
|
||||
ifTrue: [ ^ { OrderedDictionary new.
|
||||
('[''', '0', ''']').
|
||||
('[''', 'No quotes', ''']')} ].
|
||||
tempDict := self quotes asMessagesUserNamesSortedByOccurrences.
|
||||
|
||||
tempDict size > 10 ifTrue: [
|
||||
tempDict := (tempDict associations copyFrom: 1 to: 10) asOrderedDictionary ].
|
||||
|
||||
labels := tempDict keys.
|
||||
labels := labels collect: [ :profile | ($' asString), '@', profile, ($' asString) ].
|
||||
xAxis := tempDict values.
|
||||
xAxis := xAxis collect: [ :value | ($' asString), (value asString), ($' asString) ].
|
||||
^ {
|
||||
tempDict.
|
||||
('[', (',' join: xAxis), ']').
|
||||
('[', (',' join: labels), ']').
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
accessing
|
||||
refreshProfileImageUrl
|
||||
self profileImageUrl: nil.
|
||||
self profileImageUrl
|
@ -0,0 +1,5 @@
|
||||
accessing
|
||||
remoteIsFound
|
||||
|
||||
^ (ZnClient new url: (self userNameLink asUrl); get; response) isNotFound not
|
||||
|
@ -0,0 +1,7 @@
|
||||
accessing
|
||||
replies
|
||||
|
||||
self messages ifEmpty: [ self getMessages ].
|
||||
^ TweetsCollection new
|
||||
messages: (self tweets select: [ :each | (each metadata at: 'replie to') isNotEmpty]);
|
||||
yourself
|
@ -0,0 +1,19 @@
|
||||
accessing
|
||||
repliesByTimeSpan: divisions
|
||||
|
||||
| tweetsByTimeSpan xAxis labels |
|
||||
tweetsByTimeSpan := self collectMessages: [ self replies ] byTimeSpanSplits: divisions.
|
||||
xAxis := OrderedCollection new.
|
||||
(tweetsByTimeSpan values collect: [ :collection | collection size ]) do: [ :number |
|
||||
xAxis add: ($' asString), (number asString), ($' asString)
|
||||
].
|
||||
labels := OrderedCollection new.
|
||||
tweetsByTimeSpan keys do: [ :string |
|
||||
labels add: ($' asString), string, ($' asString)
|
||||
].
|
||||
^ {
|
||||
tweetsByTimeSpan.
|
||||
('[', (',' join: xAxis), ']').
|
||||
('[', (',' join: labels), ']').
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
accessing
|
||||
repliesByWeeksTimeSpan
|
||||
|
||||
| tweetsByTimeSpan xAxis labels |
|
||||
self replies isEmpty
|
||||
ifTrue: [ ^ { OrderedDictionary new.
|
||||
('[''', '0', ''']').
|
||||
('[''', 'No replies', ''']')} ].
|
||||
tweetsByTimeSpan := self collectMessages: [ self replies ] byTimeSpanSplits: self tweetsDivisionsByWeeks.
|
||||
xAxis := OrderedCollection new.
|
||||
(tweetsByTimeSpan values collect: [ :collection | collection size ]) do: [ :number |
|
||||
xAxis add: ($' asString), (number asString), ($' asString)
|
||||
].
|
||||
labels := OrderedCollection new.
|
||||
tweetsByTimeSpan keys do: [ :string |
|
||||
labels add: ($' asString), string, ($' asString)
|
||||
].
|
||||
^ {
|
||||
tweetsByTimeSpan.
|
||||
('[', (',' join: xAxis), ']').
|
||||
('[', (',' join: labels), ']').
|
||||
}
|
||||
|
@ -0,0 +1,4 @@
|
||||
private
|
||||
repliesFromImage
|
||||
"Used for testing external classes, disposable"
|
||||
^ self tweetsFromImage select: [ :tweet | (tweet metadata at: 'replie to') isNotEmpty]
|
@ -0,0 +1,5 @@
|
||||
accessing
|
||||
reportingPeriod
|
||||
^ self config
|
||||
at: 'reportingPeriod'
|
||||
ifAbsentPut: [ { self messages oldest created . self messages newest created } ]
|
@ -1,6 +1,7 @@
|
||||
accessing
|
||||
retrieveContents
|
||||
self userName ifNil: [^ self].
|
||||
" self retrieveLocalContents ifNotNil: [ ^ self ]."
|
||||
^ self
|
||||
id;
|
||||
name;
|
||||
|
@ -0,0 +1,6 @@
|
||||
accessing
|
||||
retrieveLocalContents
|
||||
| profileTemp |
|
||||
profileTemp := self class stored detect: [ :each | each userName = self userName ].
|
||||
profileTemp getLocalMessages.
|
||||
^ profileTemp
|
@ -0,0 +1,7 @@
|
||||
accessing
|
||||
retweets
|
||||
|
||||
self messages ifEmpty: [ self getMessages ].
|
||||
^ TweetsCollection new
|
||||
messages: (self messages select: [ :each | each authorId ~= self id]);
|
||||
yourself
|
@ -0,0 +1,18 @@
|
||||
accessing
|
||||
retweetsReportData
|
||||
|
||||
| tempDict labels xAxis |
|
||||
tempDict := self retweets asMessagesUserNamesSortedByOccurrences.
|
||||
|
||||
tempDict size > 10 ifTrue: [
|
||||
tempDict := (tempDict associations copyFrom: 1 to: 10) asOrderedDictionary ].
|
||||
|
||||
labels := tempDict keys.
|
||||
labels := labels collect: [ :profile | ($' asString), '@', profile, ($' asString) ].
|
||||
xAxis := tempDict values.
|
||||
xAxis := xAxis collect: [ :value | ($' asString), (value asString), ($' asString) ].
|
||||
^ {
|
||||
tempDict.
|
||||
('[', (',' join: xAxis), ']').
|
||||
('[', (',' join: labels), ']').
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
accessing
|
||||
rssFeed
|
||||
|
||||
^ RSSTools createRSSFeedFor: self userNameLink , '/rss'
|
@ -4,7 +4,7 @@ storeContents
|
||||
| objectString directory tempFile oldFile dehidratated |
|
||||
|
||||
dehidratated := self copy.
|
||||
dehidratated tweets: nil.
|
||||
dehidratated messages: nil.
|
||||
objectString := STON toStringPretty: dehidratated.
|
||||
directory := self folder ensureCreateDirectory.
|
||||
oldFile := directory / 'profile', 'ston'.
|
||||
|
14
Socialmetrica.package/NitterUser.class/instance/tweets.st
Normal file
14
Socialmetrica.package/NitterUser.class/instance/tweets.st
Normal file
@ -0,0 +1,14 @@
|
||||
accessing
|
||||
tweets
|
||||
| subcollection |
|
||||
self messages ifEmpty: [ self getMessages ].
|
||||
"TO DO: It seems that Twitter changes the user id, which we thought was unchangeable. This deals with the detected changes so far."
|
||||
|
||||
subcollection := self messages
|
||||
select: [ :each | self authorIds includes: each authorId ].
|
||||
|
||||
(#('FranciaMarquezM' 'sandralajas' 'IBetancourtCol' 'sergio_fajardo' 'ingrodolfohdez' 'CastilloMarelen') includes: self userName)
|
||||
ifFalse: [ subcollection := self messages select: [ :each | each authorId = self id ]].
|
||||
^ TweetsCollection new
|
||||
messages: subcollection;
|
||||
yourself
|
@ -0,0 +1,19 @@
|
||||
accessing
|
||||
tweetsByTimeSpan: divisions
|
||||
|
||||
| tweetsByTimeSpan xAxis labels |
|
||||
tweetsByTimeSpan := self collectMessages: [ self tweets] byTimeSpanSplits: divisions.
|
||||
xAxis := OrderedCollection new.
|
||||
(tweetsByTimeSpan values collect: [ :collection | collection size ]) do: [ :number |
|
||||
xAxis add: ($' asString), (number asString), ($' asString)
|
||||
].
|
||||
labels := OrderedCollection new.
|
||||
tweetsByTimeSpan keys do: [ :string |
|
||||
labels add: ($' asString), string, ($' asString)
|
||||
].
|
||||
^ {
|
||||
tweetsByTimeSpan.
|
||||
('[', (',' join: xAxis), ']').
|
||||
('[', (',' join: labels), ']').
|
||||
}
|
||||
|
@ -0,0 +1,19 @@
|
||||
accessing
|
||||
tweetsByWeeksTimeSpan
|
||||
|
||||
| tweetsByTimeSpan xAxis labels |
|
||||
tweetsByTimeSpan := self collectMessages: [ self tweets] byTimeSpanSplits: self tweetsDivisionsByWeeks.
|
||||
xAxis := OrderedCollection new.
|
||||
(tweetsByTimeSpan values collect: [ :collection | collection size ]) do: [ :number |
|
||||
xAxis add: ($' asString), (number asString), ($' asString)
|
||||
].
|
||||
labels := OrderedCollection new.
|
||||
tweetsByTimeSpan keys do: [ :string |
|
||||
labels add: ($' asString), string, ($' asString)
|
||||
].
|
||||
^ {
|
||||
tweetsByTimeSpan.
|
||||
('[', (',' join: xAxis), ']').
|
||||
('[', (',' join: labels), ']').
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
accessing
|
||||
tweetsDivisionsByWeeks
|
||||
|
||||
| weeks floor divisions |
|
||||
weeks := ((self newestTweet created - self oldestTweet created) days / 7).
|
||||
floor := weeks floor.
|
||||
(weeks - floor) > 0.4
|
||||
ifTrue: [ floor = 0
|
||||
ifTrue: [ divisions := 1 ]
|
||||
ifFalse: [ divisions := floor]]
|
||||
ifFalse: [ divisions := floor + 1 ].
|
||||
^ divisions
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user