Compare commits

..

No commits in common. "master" and "malfunction-recovery" have entirely different histories.

179 changed files with 163 additions and 1446 deletions

View File

@ -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'") ]
] ]

View File

@ -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 ]

View File

@ -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']

View File

@ -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']

View File

@ -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']

View File

@ -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 ]

View File

@ -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']

View File

@ -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.
``` ```

View File

@ -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

View File

@ -1,3 +0,0 @@
{
"name" : "Integer"
}

View File

@ -1,8 +0,0 @@
accessing
columnsDictionary
^ {
'url' -> 'url' .
'healthy' -> 'healthy'.
'healthy_percentage_overall' -> 'uptime' .
'rss' -> 'rss' .
'version' -> 'version'} asDictionary

View File

@ -1,4 +0,0 @@
accessing
exportInstanceReport
MiniDocs exportAsSton: self instances on: FileLocator temp / 'instances.ston'.
^ FileLocator temp / 'instances.ston'

View File

@ -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
].

View File

@ -1,3 +0,0 @@
accessing
instances
^ self instanceRows

View File

@ -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

View File

@ -1,2 +0,0 @@
accessing
instancesTable

View File

@ -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

View File

@ -1,11 +0,0 @@
{
"commentStamp" : "",
"super" : "Object",
"category" : "Socialmetrica",
"classinstvars" : [ ],
"pools" : [ ],
"classvars" : [ ],
"instvars" : [ ],
"name" : "Nitter",
"type" : "normal"
}

View File

@ -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.

View File

@ -1,3 +0,0 @@
accessing
hasRSS
^ self rss

View File

@ -1,3 +0,0 @@
accessing
healthy
^ healthy

View File

@ -1,3 +0,0 @@
accessing
isHealthy
^ self healthy

View File

@ -1,5 +0,0 @@
accessing
printOn: aStream
super printOn: aStream.
^ aStream
nextPutAll: '( ', self url ,' | uptime: ', self uptime asString, ' )'

View File

@ -1,3 +0,0 @@
accessing
rss
^ rss

View File

@ -1,3 +0,0 @@
accessing
uptime
^ uptime

View File

@ -1,3 +0,0 @@
accessing
url
^ url

View File

@ -1,17 +0,0 @@
{
"commentStamp" : "<historical>",
"super" : "Object",
"category" : "Socialmetrica",
"classinstvars" : [ ],
"pools" : [ ],
"classvars" : [ ],
"instvars" : [
"url",
"healthy",
"uptime",
"rss",
"version"
],
"name" : "NitterInstance",
"type" : "normal"
}

View File

@ -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/'

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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">>'.

View File

@ -1,4 +0,0 @@
accessing
authorIds
^ TwitterUser storedInstances select: [ :each | each userName = self userName ] thenCollect: [ :each | each id ]

View File

@ -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

View File

@ -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.

View File

@ -1,5 +0,0 @@
accessing
collectRawTweetsFromOldestUpToPage: anInteger
^ self collectRawTweetsFrom: self oldestTweetPageCursor upToPage: anInteger

View File

@ -1,5 +0,0 @@
accessing
collectRawTweetsUpToPage: anInteger
^ self collectRawTweetsFrom: self userNameLinkWithReplies upToPage: anInteger

View File

@ -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) ]

View File

@ -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
] ]

View File

@ -0,0 +1,5 @@
accessing
defaultConfig
self config: { 'folder' -> (FileLocator userData / 'Socialmetrica' / self userName) } asDictionary.
^ self config

View File

@ -1,3 +1,3 @@
operation operation
documentTree documentTree
^ self documentTreeFor: self userNameLinkWithReplies ^ XMLHTMLParser parse: self userNameLink asUrl retrieveContents

View File

@ -1,3 +0,0 @@
accessing
documentTreeFor: anUrl
^ XMLHTMLParser parse:anUrl asUrl retrieveContents

View File

@ -1,12 +1,4 @@
accessing accessing
downloadProfileImage downloadProfileImage
self remoteIsFound ^ self exportProfileImageOn: self folder / self userName, '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' ]]

View File

@ -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

View File

@ -1,6 +0,0 @@
accessing
exportDefaultReport
(self hasFolder: 'templates')
ifFalse: [ self installTemplate ].
^ self exportWithTemplate: (TweetsCollection dataStore / 'templates' / 'template.mus.tex') into: self folder

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,11 +0,0 @@
accessing
exportOverviewReportLatexWithBars: anInteger
self
exportDefaultReport;
externalWordCloud;
exportTweetsHistogramWithBars: anInteger;
exportRetweetsHistogramWithBars: anInteger;
exportRepliesHistogramWithBars: anInteger;
exportQuotesHistogramWithBars: anInteger.
^ self folder

View File

@ -3,11 +3,9 @@ exportProfileImageOn: fileReference
| file | | file |
file := fileReference asFileReference. file := fileReference asFileReference.
file exists ifFalse: [ file ensureDelete.
file ensureCreateFile. file exists 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

View File

@ -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'"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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') exportAsFileOn: (folder / self userName , 'tex')
ifTrue: [ bioModified := self asDictionary at: 'profile-bio'. containing:(mustacheFile asMustacheTemplate value: tempDictionary)
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)]

View File

@ -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;

View File

@ -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 ]

View File

@ -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

View File

@ -1,5 +1,24 @@
accessing accessing
getMessages getMessages
| lastTweetsRaw customQuery lastTweets |
self getLocalMessages ifNil: [ self getRemoteMessagesFromHtml ]. lastTweetsRaw := self rssFeed xmlDocument xpath: '//item'.
^ self messages 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

View File

@ -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

View File

@ -1,4 +0,0 @@
accessing
getPagesContentsFromOldestUpto: anInteger
^ self getPagesContentsFrom: ((self oldestTweet metadata select: [ :item | item isString and: [ item beginsWith: 'https://' ]]) values first) upTo: anInteger

View File

@ -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

View File

@ -1,4 +0,0 @@
accessing
getRemoteMessagesFromHtml
^ messages := self collectRawTweetsUpToPage: 1

View File

@ -1,6 +0,0 @@
accessing
hasFolder: folderName
| fullFolderPath |
fullFolderPath :=(TweetsCollection dataStore / folderName ).
^ fullFolderPath exists and: [ fullFolderPath children isNotEmpty ]

View File

@ -1,7 +1,3 @@
accessing accessing
id id
^ id ifNil: [ ^ id ifNil: [id := (self profileImageUrl segments select: [ :each | each asInteger class = LargePositiveInteger]) first.]
self profileImageUrl
ifNil: [ id := 0 ]
ifNotNil: [ id := (self profileImageUrl asUrl segments select: [ :each | each isAllDigits ]) first. ]
]

View File

@ -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

View File

@ -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

View File

@ -1,5 +0,0 @@
accessing
lastTweetsFromHtml
^ (self documentTree xpath: '//div[@class="timeline-item "]')
asOrderedCollection collect: [ :xmlElement | xmlElement postCopy ]

View File

@ -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 ]

View File

@ -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 ]

View File

@ -1,4 +0,0 @@
accessing
newestTweet
^ (self tweets select: [ :tweet | tweet created = ((self tweets collect: [ :each | each created ]) asSortedCollection last)]) first.

View File

@ -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.

View File

@ -1,4 +0,0 @@
accessing
oldestTweet
^ (self tweets select: [ :tweet | tweet created = ((self tweets collect: [ :each | each created ]) asSortedCollection first)]) first.

View File

@ -1,3 +0,0 @@
accessing
oldestTweetPageCursor
^ (self oldestTweet metadata select: [ :item | item isString and: [ item beginsWith: 'https://' ]]) values first value

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,8 +1,4 @@
accessing accessing
profileImageUrl profileImageUrl
^ profileImageUrl ifNil: [
| documentTree | profileImageUrl := ((self rssFeed xmlDocument xpath: '//image/url') stringValue copyReplaceAll: '%2F' with: '/') asUrl ]
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

View File

@ -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

View File

@ -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), ']').
}

View File

@ -1,4 +0,0 @@
accessing
refreshProfileImageUrl
self profileImageUrl: nil.
self profileImageUrl

View File

@ -1,5 +0,0 @@
accessing
remoteIsFound
^ (ZnClient new url: (self userNameLink asUrl); get; response) isNotFound not

View File

@ -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

View File

@ -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), ']').
}

View File

@ -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), ']').
}

View File

@ -1,4 +0,0 @@
private
repliesFromImage
"Used for testing external classes, disposable"
^ self tweetsFromImage select: [ :tweet | (tweet metadata at: 'replie to') isNotEmpty]

View File

@ -1,5 +0,0 @@
accessing
reportingPeriod
^ self config
at: 'reportingPeriod'
ifAbsentPut: [ { self messages oldest created . self messages newest created } ]

View File

@ -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;

View File

@ -1,6 +0,0 @@
accessing
retrieveLocalContents
| profileTemp |
profileTemp := self class stored detect: [ :each | each userName = self userName ].
profileTemp getLocalMessages.
^ profileTemp

View File

@ -1,7 +0,0 @@
accessing
retweets
self messages ifEmpty: [ self getMessages ].
^ TweetsCollection new
messages: (self messages select: [ :each | each authorId ~= self id]);
yourself

View File

@ -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), ']').
}

View File

@ -0,0 +1,4 @@
accessing
rssFeed
^ RSSTools createRSSFeedFor: self userNameLink , '/rss'

View File

@ -4,10 +4,10 @@ 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 := (FileLocator userData / 'Socialmetrica' / self userName) ensureCreateDirectory.
oldFile := directory / 'profile', 'ston'. oldFile := directory / self userName, 'ston'.
oldFile exists ifFalse: [ oldFile exists ifFalse: [
^ MarkupFile exportAsFileOn: oldFile containing: objectString ]. ^ MarkupFile exportAsFileOn: oldFile containing: objectString ].

View File

@ -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

View File

@ -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), ']').
}

View File

@ -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), ']').
}

View File

@ -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