Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
cd3b375
Added crowd-tangle content service
TheDanieLNK Nov 7, 2019
48231ec
Update content service factory
TheDanieLNK Nov 7, 2019
11c00bd
Added crowdtangle test service
TheDanieLNK Nov 7, 2019
41f5335
Corrected typo in test file
TheDanieLNK Nov 7, 2019
c110d59
Add empty test fixture and new unit test
TheDanieLNK Nov 7, 2019
77b5a2d
Added new test fixture and updated ct test service
TheDanieLNK Nov 7, 2019
e9a1315
Update lib.fetching.content-service.crowd-tangle-content-service.test.js
TheDanieLNK Nov 7, 2019
fc1c0e4
Update lib.fetching.content-service.crowd-tangle-content-service.test.js
TheDanieLNK Nov 7, 2019
aea7c9c
Added new fixtures and updated test service
TheDanieLNK Nov 7, 2019
76cf6e7
Updated fixture
TheDanieLNK Nov 7, 2019
6efaa2e
Updated crowdtangle test service
TheDanieLNK Nov 7, 2019
5b15584
Update test service
TheDanieLNK Nov 7, 2019
0dafafa
Update ct test service
TheDanieLNK Nov 7, 2019
b261d2d
Slight change
TheDanieLNK Nov 7, 2019
1c1528e
Update
TheDanieLNK Nov 7, 2019
858d07e
Updata ct test service
TheDanieLNK Nov 7, 2019
6ef022c
Add console log to inspect
TheDanieLNK Nov 7, 2019
dbd473a
Last test check for ct_id
TheDanieLNK Nov 7, 2019
9c9616b
Finished test for now :)
TheDanieLNK Nov 7, 2019
ed1ffd7
Added debug one-liner
TheDanieLNK Nov 8, 2019
9bdfa78
Update .gitignore
TheDanieLNK Nov 8, 2019
2ffc086
Update crowd-tangle-content-service.js
TheDanieLNK Nov 8, 2019
2abf699
Update crowd-tangle-content-service.js
TheDanieLNK Nov 8, 2019
ab3358a
Update crowd-tangle-content-service.js
TheDanieLNK Nov 8, 2019
79e8a89
Update service and test
TheDanieLNK Nov 8, 2019
560067e
Update crowd-tangle-content-service.js
TheDanieLNK Nov 8, 2019
5eb44ac
Update crowd-tangle-content-service.js
TheDanieLNK Nov 8, 2019
6267a62
Update fixtures
TheDanieLNK Nov 8, 2019
3a12736
Added crowdtangle to media list
TheDanieLNK Jan 15, 2020
b5c2345
Added facebook as descriptor for crowdtangle
TheDanieLNK Jan 15, 2020
c599216
temporarily removed whatsapp from media list
TheDanieLNK Jan 15, 2020
91ece58
change options in source ctrl
TheDanieLNK Jan 15, 2020
c806003
edit angular config
TheDanieLNK Jan 15, 2020
f66f57e
Update modal.html
TheDanieLNK Jan 15, 2020
f1d9c73
Edited media options for GY deployment
TheDanieLNK Jan 24, 2020
ab2731f
Edited translations file
TheDanieLNK Jan 28, 2020
e257788
Added crowdtangle add source changes
TheDanieLNK Feb 3, 2020
288370f
Resolved merge conflicts
TheDanieLNK Feb 3, 2020
002ad67
Adding ct data to frontend
TheDanieLNK Feb 3, 2020
6aae236
Crowdtangle end-to-end works; needs clean up
TheDanieLNK Feb 4, 2020
7ee9472
added check for burmese encoding
harshilshah4251 Feb 11, 2020
7b35ecd
Changed crowdtangle logo in reports to facebook
TheDanieLNK Feb 12, 2020
5b2a1df
Merge branch 'ct_integration' into harshil_dev
harshilshah4251 Feb 13, 2020
9a13665
replaced hardcoded value
harshilshah4251 Feb 13, 2020
cabf6f6
Merge pull request #280 from TID-Lab/harshil_dev
TheDanieLNK Feb 13, 2020
9521373
Added list to crowdtangle search list
TheDanieLNK Feb 13, 2020
cc016cb
resolved merge conflict with add burmese language tools
TheDanieLNK Feb 13, 2020
80c5e56
Removed media<->source filter
TheDanieLNK Feb 13, 2020
d5a6ff8
Perform zawgyi conversion on author names
TheDanieLNK Feb 13, 2020
a6db7dc
added encoding check to twitter, whatsapp, rss
harshilshah4251 Feb 14, 2020
6bc81cd
added encoding check to twitter, whatsapp, rss
harshilshah4251 Feb 14, 2020
c1aa988
Removed stub comments from ct_content-service
TheDanieLNK Feb 21, 2020
7935c01
Merge branch 'ct_integration' into harshil_dev
TheDanieLNK Feb 25, 2020
4bea120
Added a facebook section for the API Auth in settings
sripushkar Mar 5, 2020
b5dba34
Merge pull request #281 from TID-Lab/harshil_dev
TheDanieLNK Apr 3, 2020
ac50ff1
Update README with more dev setup info
cooperka Apr 3, 2020
ac7ebd3
Added code to get list name in crowdtangle content service
TheDanieLNK Jun 2, 2020
cd014d2
Removed slack because we don't use that API anymore and PrettyJSON be…
Jun 10, 2020
ae255cd
Working Crowdtangle List Tags
Jun 10, 2020
1d6b0c6
Store CT media url and statistics in database
mkyaw6 Jun 12, 2020
c10146b
Add continuous calls to CT api
mkyaw6 Jun 12, 2020
6710d04
Cleaned up white space in crowdtangle accountid-listname json pairs
TheDanieLNK Jun 16, 2020
dbc0072
Removed myanmar-tools from devdependencies. Already in dependencies
TheDanieLNK Jun 16, 2020
355c094
Removed straying console logged error
TheDanieLNK Jun 16, 2020
1ecd989
Reintroduced previous media options for future use
TheDanieLNK Jun 16, 2020
f62fc24
Removed hardcoded crowdtangle option from settings. if we wanted to h…
TheDanieLNK Jun 16, 2020
488b273
Removed commented code. We do not required users to enter dashboard a…
TheDanieLNK Jun 16, 2020
f8dc695
Removed case variations of crowdtangle in translations; retained one.
TheDanieLNK Jun 16, 2020
ee5c2bb
Removed stray TODO
TheDanieLNK Jun 16, 2020
e1503d0
Replaced setTimeout with process.nextTick to ensure all test reports …
TheDanieLNK Jun 16, 2020
bd16142
Removed setInterval called within crowdtangle context service. Crowdt…
TheDanieLNK Jun 16, 2020
69f4c73
Changed Crowdtangle's API call to happen twice per minute re: Crowdta…
TheDanieLNK Jun 16, 2020
49583f9
Corrected missing comma in JSON
TheDanieLNK Jun 16, 2020
d1c31ba
Added config to rss content service
TheDanieLNK Jun 16, 2020
d385948
Added config to whatsapp content service
TheDanieLNK Jun 16, 2020
bcf3ec6
Added crowdtangle to spanish translation
TheDanieLNK Jun 16, 2020
d3edf70
added acct id <-> list name
harshilshah4251 Jun 18, 2020
e7136a3
added acct id <-> list name
harshilshah4251 Jun 18, 2020
ba6c78f
Edited order and formatting.
harshilshah4251 Jun 18, 2020
5c2c559
Added sample secrets.json file
TheDanieLNK Jun 18, 2020
14fa15e
Call CT API based on last report date to avoid duplicates
mkyaw6 Jun 21, 2020
42e47d6
Store raw api response
mkyaw6 Jun 21, 2020
5454366
Merge branch 'develop' into ct_integration
karpawich Jun 23, 2020
7828cac
fix: remove Zawgyi detection & conversion from content services
karpawich Jun 26, 2020
41a6432
fix: keep all implemented Sources available
karpawich Jun 26, 2020
9b715a1
fix: remove CrowdTangle id's for Myanmar
karpawich Jun 26, 2020
3e4baec
docs: explain labeling CrowdTangle reports as Facebook
karpawich Jun 26, 2020
4d0324b
feat: config shorter interval for CrowdTangle bot
karpawich Jun 26, 2020
8692bea
fix: add back URL field to Source modal
karpawich Jun 26, 2020
b3fcf93
test: capitalize Errors
karpawich Jun 26, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,4 @@ config/cert.pem

config/key.pem

docs/_build

docs/_build
3 changes: 3 additions & 0 deletions config/crowdtangle_list.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"crowdtangle_list_account_pairs":{}
}
2 changes: 1 addition & 1 deletion config/secrets.json.example
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,4 @@
"experimentFile": "test/end-to-end/fixtures/experiment_reports.json",
"adminPassword": "letmein1",
"adminEmail": "aggie-admin@example.com"
}
}
3 changes: 3 additions & 0 deletions lib/fetching/bot.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ Bot.prototype._reportListener = function(reportData) {
if (reportData._media === 'facebook') {
reportData.metadata.isComment ? reportData.tags.push('FBComment') : reportData.tags.push('FBPost');
}
if (reportData._media === 'crowdtangle') {
true ? reportData.tags.push(reportData.metadata.ct_tag) : reportData.tags.push('Untagged');
}
}

var drops = this.queue.drops;
Expand Down
2 changes: 1 addition & 1 deletion lib/fetching/bots/pull-bot.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ var logger = require('../../logger');
// options.source - The source to pull from.
// options.contentService - The contentService to control.
var PullBot = function(options) {
this.interval = options.interval || 120000;
this.interval = options.contentService.interval || 120000;
Comment thread
karpawich marked this conversation as resolved.
Bot.call(this, options);
};

Expand Down
2 changes: 2 additions & 0 deletions lib/fetching/content-service-factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ contentServices['dummy-fast'] = require('./content-services/dummy-fast-content-s
contentServices.smsgh = require('./content-services/smsgh-content-service');
contentServices.whatsapp = require('./content-services/whatsapp-content-service');
contentServices.replay = require('./content-services/replay-content-service');
contentServices.crowdtangle = require('./content-services/crowd-tangle-content-service');

function ContentServiceFactory() { /* empty constructor */ }

// Creates a new content service to match the given source.
ContentServiceFactory.prototype.create = function(source) {

var Service = contentServices[source.media];
// Content services use only the lastReportDate, url, and keywords params.
var basicSource = _.pick(source, 'lastReportDate', 'url', 'keywords');
Expand Down
155 changes: 155 additions & 0 deletions lib/fetching/content-services/crowd-tangle-content-service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
var request = require('request');
var url = require('url');

var ContentService = require('../content-service');
var util = require('util');
var config = require('../../../config/secrets');
var crowdtangle_lists = require('../../../config/crowdtangle_list');

var request = require('request');
var _ = require('underscore');


//options.lastReportDate is passed here through the content-service-factory and will be utilised in calling the api, but its actually only maintained by the parent content service, it is only utilised by the child
var CrowdTangleContentService = function(options) {
this._baseUrl = config.get().crowdtangle.baseUrl;
this._pathName = config.get().crowdtangle.pathName;
this._count = config.get().crowdtangle.count;
this._language = config.get().crowdtangle.language;
this._sortParameter = config.get().crowdtangle.sortParam;
this._apiToken = config.get().crowdtangle.apiToken;
this._keywords = options.keywords;
this._lastReportDate = options._lastReportDate;
this._listIds = parseInt(options.tags);
this.fetchType = 'pull';
this.interval = 30000;
ContentService.call(this, options); //associates the child service with its parent, which has the notion of lastreportdate
}


util.inherits(CrowdTangleContentService, ContentService);

//this method overwrites the _doFetch method in the content-service (which is the parent class)
//and then inside the content-service, we have a fetch method that is called by the pull-bot and is responsible for emitting (writing) the reports and updating the final value of lastReportDate
//options.maxCount (present but not used, used by the parent content service)
//what about types? -- discuss with Michael
CrowdTangleContentService.prototype._doFetch = function(options, callback) {
var self = this;
//handle errors using process.nextTick
// this will depend mostly on the options that we expect to receive, and if there is issue with that

// if (!this._url) {
// process.nextTick(function() { self.emit('error', new Error('Missing URL')); });
// return callback([]);
// }

//now we need to submit the request
this._httpRequest( {url: this._completeUrl(options)}, function(err, res, body) {
if (err) {
self.emit('error', new Error('HTTP error: ' + err.message));
return callback([]);
} else if (res.statusCode != 200) {
self.emit('error', new Error.HTTP(res.statusCode));
return callback([]);
}

//if no errors, parse the body..
var responses;
try {
responses = JSON.parse(body).result.posts;
if (!(responses instanceof Array)) {
self.emit('error', new Error('Wrong data'));
return callback([]);
}
// any other error handling wrt the structure of responses
} catch (e) {
self.emit('error', new Error('Parse error: ' + e.message));
return callback([]);
}
// the responses will be sorted by the content-service (parent method)
// Parse response data and return them.
var reportData = responses.map(function(x) { return self._parse(x); });
callback(reportData);
})
};

CrowdTangleContentService.prototype._httpRequest = function(params, callback) {
request(params, callback);
};

CrowdTangleContentService.prototype._completeUrl = function() {
// add one second to last report date to start fetching from
var startDate = new Date(this._lastReportDate.getTime() + 1000).toISOString();
return url.format({
protocol: 'https',
hostname: this._baseUrl,
pathname: this._pathName,
query: {
token: this._apiToken,
count: this._count,
language: this._language,
listIds: this._listIds,
searchTerm: this._keywords,
sortBy: this._sortParameter,
startDate: startDate,
endDate: new Date().toISOString()
}
});
}

CrowdTangleContentService.prototype._parse = function(data) {

var metadata = {
sponsor: data.brandedContentSponsor || null,
caption: data.caption || null,
description: data.description || null,
title: data.title || null,
crowdtangleId: data.id || null,
externalUrl: data.link || null,
platform: data.platform || null,
type: data.type || null,
accountVerified: data.account ? data.account.verified : false,
accountHandle: data.account ? data.account.handle : null,
subscriberCount: data.account ? data.account.subscriberCount : 0,
accountUrl: data.account ? data.account.url : null,
mediaUrl: data.media? data.media.map(function(medium) {
return {type: medium.type, url: medium.url}
}) : null,
actualStatistics: data.statistics.actual,
expectedStatistics: data.statistics.expected,
rawAPIResponse: data
};
var text = data.message || data.description || data.title || data.caption || "[No Content]"; // TODO need to revisit, what if there is no text? what about youtube case
var author = data.account ? data.account.name || data.account.handle : null;

// This code deals specifically with matching a crowdtangle list to a report's account id
this.crowdtangle_lists = crowdtangle_lists.crowdtangle_list_account_pairs;
// If the list is found and matched, then the ct_tag is the list name
if (this.crowdtangle_lists[data.account.id]) {
metadata.ct_tag = this.crowdtangle_lists[data.account.id];
} else {
// If the list is not found and not matched, make the ct_tag the account.id so we can identify it later.
metadata.ct_tag = data.account.id;
}

return {
authoredAt: new Date(data.date + " UTC") || new Date(),
fetchedAt: new Date(),
content: text,
author: author,
metadata: metadata,
url: data.postUrl,
//_sources: '', //need to get this info from somewhere
//_sourceNicknames: ''
};
}


CrowdTangleContentService.prototype.reloadSettings = function() {
this._baseUrl = config.get().crowdtangle.baseUrl;
this._pathName = config.get().crowdtangle.pathName;
this._count = config.get().crowdtangle.count;
this._language = config.get().crowdtangle.language;
};

module.exports = CrowdTangleContentService;
1 change: 1 addition & 0 deletions lib/fetching/content-services/rss-content-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ var util = require('util');
var _ = require('underscore');
var logger = require('../../logger');


// options.url - The URL of the RSS feed.
// options.lastReportDate - The fetchedAt time of the last already fetched report (optional).
var RSSContentService = function(options) {
Expand Down
3 changes: 2 additions & 1 deletion lib/fetching/content-services/twitter-content-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ TwitterContentService.prototype._parse = function(data) {
latitude: data.coordinates ? data.coordinates[0] : 0,
longitude: data.coordinates ? data.coordinates[1] : 0,
retweetCount: data.retweet_count ? data.retweet_count : 0,
favouriteCount: data.favorite_count ? data.favorite_count : 0
favouriteCount: data.favorite_count ? data.favorite_count : 0,
rawAPIResponse: data
};
return {
authoredAt: new Date(data.created_at),
Expand Down
2 changes: 1 addition & 1 deletion models/source.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ var urlValidator = function(url) {
)
}

var mediaValues = ['facebook', 'elmo', 'twitter', 'rss', 'dummy', 'smsgh', 'whatsapp', 'dummy-pull', 'dummy-fast'];
var mediaValues = ['facebook', 'crowdtangle', 'elmo', 'twitter', 'rss', 'dummy', 'smsgh', 'whatsapp', 'dummy-pull', 'dummy-fast'];

var sourceSchema = new mongoose.Schema({
media: { type: String, enum: mediaValues },
Expand Down
70 changes: 0 additions & 70 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@
"locale": "^0.1.0",
"lodash": "^4.17.15",
"merge-stream": "^2.0.0",
"migrate": "^1.6.2",
"mkdirp": "^1.0.4",
"moment": "^2.25.3",
"mongoose": "^5.9.16",
Expand Down
Binary file added public/angular/images/crowdtangle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion public/angular/js/config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
angular.module('Aggie')

.value('mediaOptions', ['twitter', 'facebook', 'rss', 'elmo', 'smsgh', 'whatsapp'])
.value('mediaOptions', ['twitter', 'facebook', 'rss', 'elmo', 'smsgh', 'whatsapp', 'crowdtangle'])

.value('apiSettingsOptions', ['twitter', 'facebook', 'elmo', 'gplaces'])

Expand Down
18 changes: 12 additions & 6 deletions public/angular/js/controllers/incidents/show.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,20 @@ angular.module('Aggie')
// Pick one of the sources that has a media type. For now, it happens that
// if a report has multiple sources, they all have the same type, or are
// deleted
for (var i = 0; i < report._sources.length; i++) {
var sourceId = report._sources[i];
var source = $scope.sourcesById[sourceId];
if (source && $scope.mediaOptions[source.media] !== -1) {
return source.media + '-source';

if (report.metadata.platform === "Facebook") {
// set Facebook as source for CrowdTangle reports
return 'facebook-source';
} else {
for (var i = 0; i < report._sources.length; i++) {
var sourceId = report._sources[i];
var source = $scope.sourcesById[sourceId];
if (source && $scope.mediaOptions[source.media] !== -1) {
return source.media + '-source';
}
}
return 'unknown-source';
}
return 'unknown-source';
};

$scope.delete = function() {
Expand Down
Loading