Compare commits
1 Commits
master
...
detach-rec
Author | SHA1 | Date | |
---|---|---|---|
6a8e7bff51 |
@ -5,13 +5,10 @@ baseline: spec
|
|||||||
for: #common
|
for: #common
|
||||||
do: [
|
do: [
|
||||||
"Dependencies"
|
"Dependencies"
|
||||||
"self xmlParserHTML: spec."
|
self xmlParserHTML: spec.
|
||||||
self reStore: spec.
|
"self rssTools: spec."
|
||||||
self miniDocs: spec.
|
|
||||||
"self roassal3Exporters: spec."
|
|
||||||
"self tiddlyWikiPharo: spec."
|
|
||||||
"Packages"
|
"Packages"
|
||||||
spec
|
spec
|
||||||
package: 'Socialmetrica'
|
package: 'Socialmetrica'
|
||||||
with: [ spec requires: #('ReStore' 'MiniDocs' "'XMLParserHTML' 'Roassal3Exporters' 'TiddlyWikiPharo'") ]
|
with: [ spec requires: #('XMLParserHTML' "'RSSTools'") ]
|
||||||
]
|
]
|
@ -1,8 +0,0 @@
|
|||||||
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 ]
|
|
@ -1,10 +0,0 @@
|
|||||||
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']
|
|
@ -1,7 +0,0 @@
|
|||||||
baselines
|
|
||||||
roassal3Exporters: spec
|
|
||||||
Metacello new
|
|
||||||
baseline: 'Roassal3Exporters';
|
|
||||||
repository: 'github://ObjectProfile/Roassal3Exporters';
|
|
||||||
load.
|
|
||||||
spec baseline: 'Roassal3Exporters' with: [ spec repository: 'github://ObjectProfile/Roassal3Exporters']
|
|
@ -0,0 +1,10 @@
|
|||||||
|
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']
|
@ -1,8 +0,0 @@
|
|||||||
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,6 +4,8 @@ xmlParserHTML: spec
|
|||||||
baseline: 'XMLParserHTML';
|
baseline: 'XMLParserHTML';
|
||||||
repository: 'github://pharo-contributions/XML-XMLParserHTML/src';
|
repository: 'github://pharo-contributions/XML-XMLParserHTML/src';
|
||||||
onConflict: [ :ex | ex useLoaded ];
|
onConflict: [ :ex | ex useLoaded ];
|
||||||
|
onUpgrade: [ :ex | ex useLoaded ];
|
||||||
|
onDowngrade: [ :ex | ex useLoaded ];
|
||||||
onWarningLog;
|
onWarningLog;
|
||||||
load.
|
load.
|
||||||
spec baseline: 'XMLParserHTML' with: [spec repository: 'github://pharo-contributions/XML-XMLParserHTML/src']
|
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
|
```smalltalk
|
||||||
ExoRepo new
|
ExoRepo new
|
||||||
repository: 'https://code.sustrato.red/Offray/Socialmetrica';
|
repository: 'https://code.tupale.co/Offray/Socialmetrica';
|
||||||
load.
|
load.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
*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
|
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"name" : "Integer"
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
accessing
|
|
||||||
columnsDictionary
|
|
||||||
^ {
|
|
||||||
'url' -> 'url' .
|
|
||||||
'healthy' -> 'healthy'.
|
|
||||||
'healthy_percentage_overall' -> 'uptime' .
|
|
||||||
'rss' -> 'rss' .
|
|
||||||
'version' -> 'version'} asDictionary
|
|
@ -1,4 +0,0 @@
|
|||||||
accessing
|
|
||||||
exportInstanceReport
|
|
||||||
MiniDocs exportAsSton: self instances on: FileLocator temp / 'instances.ston'.
|
|
||||||
^ FileLocator temp / 'instances.ston'
|
|
@ -1,8 +0,0 @@
|
|||||||
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
|
|
||||||
].
|
|
@ -1,3 +0,0 @@
|
|||||||
accessing
|
|
||||||
instances
|
|
||||||
^ self instanceRows
|
|
@ -1,9 +0,0 @@
|
|||||||
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
|
|
@ -1,2 +0,0 @@
|
|||||||
accessing
|
|
||||||
instancesTable
|
|
@ -1,21 +0,0 @@
|
|||||||
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
|
|
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"commentStamp" : "",
|
|
||||||
"super" : "Object",
|
|
||||||
"category" : "Socialmetrica",
|
|
||||||
"classinstvars" : [ ],
|
|
||||||
"pools" : [ ],
|
|
||||||
"classvars" : [ ],
|
|
||||||
"instvars" : [ ],
|
|
||||||
"name" : "Nitter",
|
|
||||||
"type" : "normal"
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
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.
|
|
@ -1,3 +0,0 @@
|
|||||||
accessing
|
|
||||||
hasRSS
|
|
||||||
^ self rss
|
|
@ -1,3 +0,0 @@
|
|||||||
accessing
|
|
||||||
healthy
|
|
||||||
^ healthy
|
|
@ -1,3 +0,0 @@
|
|||||||
accessing
|
|
||||||
isHealthy
|
|
||||||
^ self healthy
|
|
@ -1,5 +0,0 @@
|
|||||||
accessing
|
|
||||||
printOn: aStream
|
|
||||||
super printOn: aStream.
|
|
||||||
^ aStream
|
|
||||||
nextPutAll: '( ', self url ,' | uptime: ', self uptime asString, ' )'
|
|
@ -1,3 +0,0 @@
|
|||||||
accessing
|
|
||||||
rss
|
|
||||||
^ rss
|
|
@ -1,3 +0,0 @@
|
|||||||
accessing
|
|
||||||
uptime
|
|
||||||
^ uptime
|
|
@ -1,3 +0,0 @@
|
|||||||
accessing
|
|
||||||
url
|
|
||||||
^ url
|
|
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"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:
|
"For a full list of Nitter providers, see:
|
||||||
|
|
||||||
https://github.com/zedeus/nitter/wiki/Instances"
|
https://github.com/zedeus/nitter/wiki/Instances"
|
||||||
^ 'https://nitter.net/'
|
^ 'https://nitter.42l.fr/'
|
@ -1,4 +0,0 @@
|
|||||||
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,29 +1,7 @@
|
|||||||
accessing
|
accessing
|
||||||
asDictionary
|
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' -> self profileImageFile fullName.
|
||||||
'profile-card-avatar-url' -> self profileImageUrl.
|
|
||||||
'profile-card-fullname' -> self name .
|
'profile-card-fullname' -> self name .
|
||||||
'profile-card-username' -> self userName .
|
'profile-card-username' -> self userName .
|
||||||
'profile-bio' -> self profileBio.
|
'profile-bio' -> self profileBio } asDictionary
|
||||||
'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
|
|
@ -1,28 +0,0 @@
|
|||||||
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
|
|
@ -1,16 +0,0 @@
|
|||||||
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">>'.
|
|
@ -1,4 +0,0 @@
|
|||||||
accessing
|
|
||||||
authorIds
|
|
||||||
|
|
||||||
^ TwitterUser storedInstances select: [ :each | each userName = self userName ] thenCollect: [ :each | each id ]
|
|
@ -1,12 +0,0 @@
|
|||||||
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
|
|
@ -1,33 +0,0 @@
|
|||||||
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.
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
|||||||
accessing
|
|
||||||
collectRawTweetsFromOldestUpToPage: anInteger
|
|
||||||
|
|
||||||
^ self collectRawTweetsFrom: self oldestTweetPageCursor upToPage: anInteger
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
|||||||
accessing
|
|
||||||
collectRawTweetsUpToPage: anInteger
|
|
||||||
|
|
||||||
^ self collectRawTweetsFrom: self userNameLinkWithReplies upToPage: anInteger
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
|||||||
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,7 +2,5 @@ accessing
|
|||||||
createdAt
|
createdAt
|
||||||
^ createdAt ifNil: [| joinDateString |
|
^ createdAt ifNil: [| joinDateString |
|
||||||
joinDateString := ((self documentTree xpath: '//div[@class="profile-joindate"]/span/@title') stringValue).
|
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
|
|
||||||
]
|
]
|
@ -0,0 +1,5 @@
|
|||||||
|
accessing
|
||||||
|
defaultConfig
|
||||||
|
|
||||||
|
self config: { 'folder' -> (FileLocator userData / 'Socialmetrica' / self userName) } asDictionary.
|
||||||
|
^ self config
|
@ -1,3 +1,3 @@
|
|||||||
operation
|
operation
|
||||||
documentTree
|
documentTree
|
||||||
^ self documentTreeFor: self userNameLinkWithReplies
|
^ XMLHTMLParser parse: self userNameLink asUrl retrieveContents
|
@ -1,3 +0,0 @@
|
|||||||
accessing
|
|
||||||
documentTreeFor: anUrl
|
|
||||||
^ XMLHTMLParser parse:anUrl asUrl retrieveContents
|
|
@ -1,12 +1,4 @@
|
|||||||
accessing
|
accessing
|
||||||
downloadProfileImage
|
downloadProfileImage
|
||||||
|
|
||||||
self remoteIsFound
|
^ self exportProfileImageOn: self folder / 'profile-image', 'jpg'
|
||||||
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' ]]
|
|
@ -1,40 +0,0 @@
|
|||||||
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
|
|
@ -1,6 +0,0 @@
|
|||||||
accessing
|
|
||||||
exportDefaultReport
|
|
||||||
|
|
||||||
(self hasFolder: 'templates')
|
|
||||||
ifFalse: [ self installTemplate ].
|
|
||||||
^ self exportWithTemplate: (TweetsCollection dataStore / 'templates' / 'template.mus.tex') into: self folder
|
|
@ -1,9 +0,0 @@
|
|||||||
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
|
|
@ -1,49 +0,0 @@
|
|||||||
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
|
|
@ -1,11 +0,0 @@
|
|||||||
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
|
|
@ -1,11 +0,0 @@
|
|||||||
accessing
|
|
||||||
exportOverviewReportLatexWithBars: anInteger
|
|
||||||
|
|
||||||
self
|
|
||||||
exportDefaultReport;
|
|
||||||
externalWordCloud;
|
|
||||||
exportTweetsHistogramWithBars: anInteger;
|
|
||||||
exportRetweetsHistogramWithBars: anInteger;
|
|
||||||
exportRepliesHistogramWithBars: anInteger;
|
|
||||||
exportQuotesHistogramWithBars: anInteger.
|
|
||||||
^ self folder
|
|
@ -1,13 +1,48 @@
|
|||||||
accessing
|
accessing
|
||||||
exportProfileImageOn: fileReference
|
exportProfileImageOn: fileReference
|
||||||
|
|
||||||
| file |
|
| file tempFile tempFileHash fileHash |
|
||||||
file := fileReference asFileReference.
|
file := fileReference asFileReference.
|
||||||
file exists ifFalse: [
|
file exists
|
||||||
file ensureCreateFile.
|
ifFalse: [ file ensureCreateFile.
|
||||||
file binaryWriteStreamDo: [ :stream |
|
file binaryWriteStreamDo: [ :stream |
|
||||||
stream nextPutAll: profileImageUrl asUrl retrieveContents ].
|
stream nextPutAll: profileImageUrl retrieveContents ].
|
||||||
self class inform: 'Exported as: ', String cr, file fullName.
|
super class inform: 'Exported as: ', String cr, file fullName.
|
||||||
^ file
|
^ file]
|
||||||
].
|
|
||||||
self downloadWithRenaming: fileReference
|
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
|
@ -1,49 +0,0 @@
|
|||||||
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'"
|
|
@ -1,10 +0,0 @@
|
|||||||
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
|
|
@ -1,10 +0,0 @@
|
|||||||
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
|
|
@ -1,8 +0,0 @@
|
|||||||
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
|
|
@ -1,10 +0,0 @@
|
|||||||
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
|
|
@ -1,11 +0,0 @@
|
|||||||
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,29 +1,12 @@
|
|||||||
accessing
|
accessing
|
||||||
exportWithTemplate: mustacheFile into: folder
|
exportWithTemplate: mustacheFile into: folder
|
||||||
|
|
||||||
| tempDictionary bioModified userModified nameModified |
|
| tempDictionary modified |
|
||||||
tempDictionary := self asDictionary copy.
|
tempDictionary := self asDictionary copy.
|
||||||
(mustacheFile fullName endsWith: '.html')
|
modified := self asDictionary at: 'profile-bio'.
|
||||||
ifTrue: [ ^ MarkupFile
|
modified := modified copyReplaceAll: '@' with: '\@'.
|
||||||
exportAsFileOn: (folder / self userName , 'html')
|
modified := modified copyReplaceAll: '_' with: '\_'.
|
||||||
containing:(mustacheFile asMustacheTemplate value: tempDictionary)].
|
tempDictionary at: 'profile-bio' put: modified.
|
||||||
|
MarkupFile
|
||||||
(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')
|
exportAsFileOn: (folder / self userName , 'tex')
|
||||||
containing:(mustacheFile asMustacheTemplate value: tempDictionary)]
|
containing:(mustacheFile asMustacheTemplate value: tempDictionary)
|
||||||
|
|
@ -1,13 +1,9 @@
|
|||||||
accessing
|
accessing
|
||||||
externalWordCloud
|
externalWordCloud
|
||||||
"TO DO: refactor with externalWordCloudWithLanguage:"
|
|
||||||
|
|
||||||
| text outputFile |
|
| text outputFile |
|
||||||
self areCommonFilesInstalled
|
outputFile := (self folder / 'nube.png')fullName.
|
||||||
ifFalse: [ self installCommons ].
|
text := (self folder / self userName, 'words', 'txt')fullName.
|
||||||
self writeWordsFile.
|
|
||||||
outputFile := (self folder / 'wordcloud.png') fullName.
|
|
||||||
text := (self folder / 'words', 'txt') fullName.
|
|
||||||
OSSUnixSubprocess new
|
OSSUnixSubprocess new
|
||||||
command: 'wordcloud_cli';
|
command: 'wordcloud_cli';
|
||||||
arguments: { '--text' . text .
|
arguments: { '--text' . text .
|
||||||
@ -17,9 +13,8 @@ externalWordCloud
|
|||||||
'--height' . '357' .
|
'--height' . '357' .
|
||||||
'--background' . 'white' .
|
'--background' . 'white' .
|
||||||
'--mode' . 'RGBA' .
|
'--mode' . 'RGBA' .
|
||||||
'--stopwords' . '../../../commons/stopwords-es.txt'.
|
'--stopwords' . '../commons/stopwords-es.txt' .
|
||||||
"'--mask' . '../../../commons/nube-mascara.jpg'"
|
'--mask' . '../commons/nube-mascara.jpg'};
|
||||||
};
|
|
||||||
workingDirectory: self folder fullName;
|
workingDirectory: self folder fullName;
|
||||||
redirectStdout;
|
redirectStdout;
|
||||||
redirectStderr;
|
redirectStderr;
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
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 ]
|
|
@ -1,14 +0,0 @@
|
|||||||
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,5 +1,24 @@
|
|||||||
accessing
|
accessing
|
||||||
getMessages
|
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
|
|
@ -1,19 +0,0 @@
|
|||||||
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
|
|
@ -1,4 +0,0 @@
|
|||||||
accessing
|
|
||||||
getPagesContentsFromOldestUpto: anInteger
|
|
||||||
|
|
||||||
^ self getPagesContentsFrom: ((self oldestTweet metadata select: [ :item | item isString and: [ item beginsWith: 'https://' ]]) values first) upTo: anInteger
|
|
@ -1,8 +0,0 @@
|
|||||||
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
|
|
@ -1,4 +0,0 @@
|
|||||||
accessing
|
|
||||||
getRemoteMessagesFromHtml
|
|
||||||
|
|
||||||
^ messages := self collectRawTweetsUpToPage: 1
|
|
@ -1,6 +0,0 @@
|
|||||||
accessing
|
|
||||||
hasFolder: folderName
|
|
||||||
|
|
||||||
| fullFolderPath |
|
|
||||||
fullFolderPath :=(TweetsCollection dataStore / folderName ).
|
|
||||||
^ fullFolderPath exists and: [ fullFolderPath children isNotEmpty ]
|
|
@ -1,7 +1,3 @@
|
|||||||
accessing
|
accessing
|
||||||
id
|
id
|
||||||
^ id ifNil: [
|
^ id ifNil: [ id := (self profileImageUrl asUrl segments select: [ :each | each isAllDigits ]) first.]
|
||||||
self profileImageUrl
|
|
||||||
ifNil: [ id := 0 ]
|
|
||||||
ifNotNil: [ id := (self profileImageUrl asUrl segments select: [ :each | each isAllDigits ]) first. ]
|
|
||||||
]
|
|
@ -1,22 +0,0 @@
|
|||||||
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
|
|
@ -1,22 +0,0 @@
|
|||||||
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
|
|
@ -1,5 +0,0 @@
|
|||||||
accessing
|
|
||||||
lastTweetsFromHtml
|
|
||||||
|
|
||||||
^ (self documentTree xpath: '//div[@class="timeline-item "]')
|
|
||||||
asOrderedCollection collect: [ :xmlElement | xmlElement postCopy ]
|
|
@ -1,12 +0,0 @@
|
|||||||
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,6 +1,3 @@
|
|||||||
accessing
|
accessing
|
||||||
name
|
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 ]
|
|
@ -1,4 +0,0 @@
|
|||||||
accessing
|
|
||||||
newestTweet
|
|
||||||
|
|
||||||
^ (self tweets select: [ :tweet | tweet created = ((self tweets collect: [ :each | each created ]) asSortedCollection last)]) first.
|
|
@ -0,0 +1,18 @@
|
|||||||
|
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.
|
@ -1,4 +0,0 @@
|
|||||||
accessing
|
|
||||||
oldestTweet
|
|
||||||
|
|
||||||
^ (self tweets select: [ :tweet | tweet created = ((self tweets collect: [ :each | each created ]) asSortedCollection first)]) first.
|
|
@ -1,3 +0,0 @@
|
|||||||
accessing
|
|
||||||
oldestTweetPageCursor
|
|
||||||
^ (self oldestTweet metadata select: [ :item | item isString and: [ item beginsWith: 'https://' ]]) values first value
|
|
@ -1,10 +0,0 @@
|
|||||||
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
|
|
@ -1,14 +0,0 @@
|
|||||||
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,8 +2,6 @@ accessing
|
|||||||
profileImageFile
|
profileImageFile
|
||||||
|
|
||||||
| file |
|
| file |
|
||||||
file := (self folder / 'profile-image', 'jpg').
|
file := (self folder / self userName, 'jpg').
|
||||||
file exists ifTrue: [ file asFileReference size = 0
|
file exists ifTrue: [ ^ file ].
|
||||||
ifTrue:[ ^ self downloadProfileImage ].
|
|
||||||
^ file. ].
|
|
||||||
^ self downloadProfileImage
|
^ self downloadProfileImage
|
@ -1,8 +1,4 @@
|
|||||||
accessing
|
accessing
|
||||||
profileImageUrl
|
profileImageUrl
|
||||||
|
^ profileImageUrl ifNil: [
|
||||||
| documentTree |
|
profileImageUrl := ((self rssFeed xmlDocument xpath: '//image/url') stringValue copyReplaceAll: '%2F' with: '/') ]
|
||||||
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
|
|
@ -1,9 +0,0 @@
|
|||||||
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
|
|
@ -1,22 +0,0 @@
|
|||||||
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), ']').
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
accessing
|
|
||||||
refreshProfileImageUrl
|
|
||||||
self profileImageUrl: nil.
|
|
||||||
self profileImageUrl
|
|
@ -1,5 +0,0 @@
|
|||||||
accessing
|
|
||||||
remoteIsFound
|
|
||||||
|
|
||||||
^ (ZnClient new url: (self userNameLink asUrl); get; response) isNotFound not
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
|||||||
accessing
|
|
||||||
replies
|
|
||||||
|
|
||||||
self messages ifEmpty: [ self getMessages ].
|
|
||||||
^ TweetsCollection new
|
|
||||||
messages: (self tweets select: [ :each | (each metadata at: 'replie to') isNotEmpty]);
|
|
||||||
yourself
|
|
@ -1,19 +0,0 @@
|
|||||||
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), ']').
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
|||||||
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), ']').
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
|||||||
private
|
|
||||||
repliesFromImage
|
|
||||||
"Used for testing external classes, disposable"
|
|
||||||
^ self tweetsFromImage select: [ :tweet | (tweet metadata at: 'replie to') isNotEmpty]
|
|
@ -1,5 +0,0 @@
|
|||||||
accessing
|
|
||||||
reportingPeriod
|
|
||||||
^ self config
|
|
||||||
at: 'reportingPeriod'
|
|
||||||
ifAbsentPut: [ { self messages oldest created . self messages newest created } ]
|
|
@ -1,7 +1,6 @@
|
|||||||
accessing
|
accessing
|
||||||
retrieveContents
|
retrieveContents
|
||||||
self userName ifNil: [^ self].
|
self userName ifNil: [^ self].
|
||||||
" self retrieveLocalContents ifNotNil: [ ^ self ]."
|
|
||||||
^ self
|
^ self
|
||||||
id;
|
id;
|
||||||
name;
|
name;
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
accessing
|
|
||||||
retrieveLocalContents
|
|
||||||
| profileTemp |
|
|
||||||
profileTemp := self class stored detect: [ :each | each userName = self userName ].
|
|
||||||
profileTemp getLocalMessages.
|
|
||||||
^ profileTemp
|
|
@ -1,7 +0,0 @@
|
|||||||
accessing
|
|
||||||
retweets
|
|
||||||
|
|
||||||
self messages ifEmpty: [ self getMessages ].
|
|
||||||
^ TweetsCollection new
|
|
||||||
messages: (self messages select: [ :each | each authorId ~= self id]);
|
|
||||||
yourself
|
|
@ -1,18 +0,0 @@
|
|||||||
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), ']').
|
|
||||||
}
|
|
@ -0,0 +1,4 @@
|
|||||||
|
accessing
|
||||||
|
rssFeed
|
||||||
|
|
||||||
|
^ RSSTools createRSSFeedFor: self userNameLink , '/rss'
|
@ -4,7 +4,7 @@ storeContents
|
|||||||
| objectString directory tempFile oldFile dehidratated |
|
| objectString directory tempFile oldFile dehidratated |
|
||||||
|
|
||||||
dehidratated := self copy.
|
dehidratated := self copy.
|
||||||
dehidratated messages: nil.
|
dehidratated tweets: nil.
|
||||||
objectString := STON toStringPretty: dehidratated.
|
objectString := STON toStringPretty: dehidratated.
|
||||||
directory := self folder ensureCreateDirectory.
|
directory := self folder ensureCreateDirectory.
|
||||||
oldFile := directory / 'profile', 'ston'.
|
oldFile := directory / 'profile', 'ston'.
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
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
|
|
@ -1,19 +0,0 @@
|
|||||||
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), ']').
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
|||||||
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), ']').
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
|||||||
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