-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdeleteFavs.gs
327 lines (295 loc) · 11 KB
/
deleteFavs.gs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
//You must configure
var TWITTER_USER = 'YOUR TWITTER USER NAME';
var MAX_AGE_IN_DAYS = 7;
var TWEETS_PER_REQUEST = 200;
//From apps.twitter.com
var CONSUMER_KEY = 'YOUR CONSUMER KEY';
var CONSUMER_SECRET = 'YOUR CONSUMER CONSUMER_SECRET';
var ACCESS_TOKEN = 'YOUR ACCESS TOKEN';
var ACCESS_SECRET = 'YOUR ACCESS SECRET';
//You shouldn't have to change anything below this line
var NOW_DATE = new Date();
var oldest_tweet_id = 0;
var got_favs = true;
var favs = []
var destroy_count = 0;
var ignored_count = 0;
var request = 0; //Twitter API request limit (75 Requests / 15-min (app auth)) -> 1 request / 12seconds
var authUrlFetch;
function deleteFavs() {
var connection = getTwitterService();
while (got_favs && request <= 75) {
var new_favs = getFavs(oldest_tweet_id);
if (new_favs.length > 1) {
console.log("Got " + new_favs.length + " more favs, latest is " + new_favs[new_favs.length - 1].id);
oldest_tweet_id = new_favs[new_favs.length - 1].id;
favs = favs.concat(new_favs);
} else {
got_favs = false;
}
}
for (var i = favs.length - 1; i >= 0; i--) {
var tweet_date = new Date(favs[i].created_at);
var tweet_age = parseInt((NOW_DATE-tweet_date)/1000/60/60/24);
if (tweet_age < MAX_AGE_IN_DAYS) {
ignored_count++;
} else {
destroy_count++;
if (destroy(favs[i].id_str)) {
console.log('Deleted ' + favs[i].id_str + " - " + tweet_age + " days old. Favs destroy: " + destroy_count);
}
}
}
console.log("Destroy: " + destroy_count + " - Ignored: " + destroy_count);
}
function getFavs(max_id) {
request++;
var params = {
screen_name: TWITTER_USER,
count: TWEETS_PER_REQUEST,
include_entities: false
}
if (max_id > 0) params.max_id = max_id;
try {
var result = authUrlFetch.fetch('https://api.twitter.com/1.1/favorites/list.json', params, { method: 'GET' });
}
catch(e) {
console.log(e.toString());
}
if (result && result.getResponseCode() === 200) {
var data = JSON.parse(result.getContentText());
if (data) { return data; }
}
}
function destroy(tweet_id) {
var options = {
method: "POST",
payload: { id: tweet_id }
};
try {
var result = authUrlFetch.fetch('https://api.twitter.com/1.1/favorites/destroy.json', '', options);
return(true);
}
catch (e) {
console.log('Error deleting ' + tweet_id);
console.log(e.toString());
return(false);
}
}
function getTwitterService() {
if (typeof OAuth1 === 'undefined') {
var libUrl = 'https://developers.google.com/adwords/scripts/docs/examples/oauth10-library';
throw Error('OAuth1 library not found. Please take a copy of the OAuth1 ' +
'library from ' + libUrl + ' and append to the bottom of this script.');
}
authUrlFetch = OAuth1.withAccessToken(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_SECRET);
}
/**
* OAuth librery from: https://developers.google.com/adwords/scripts/docs/examples/oauth10-library
* Adds a OAuth1 object to the global scope. This can be used as follows:
*
* var urlFetch = OAuth1.withAccessToken(consumerKey, consumerSecret,
* accessToken, accessSecret);
* var response = urlFetch.fetch(url, params, options);
*/
(function(scope) {
/**
* Creates an object to provide OAuth1-based requests to API resources.
* @param {string} consumerKey
* @param {string} consumerSecret
* @param {string} accessToken
* @param {string} accessSecret
* @constructor
*/
function OAuth1UrlFetchApp(
consumerKey, consumerSecret, accessToken, accessSecret) {
this.consumerKey_ = consumerKey;
this.consumerSecret_ = consumerSecret;
this.accessToken_ = accessToken;
this.accessSecret_ = accessSecret;
}
/**
* Sends a signed OAuth 1.0 request.
* @param {string} url The URL of the API resource.
* @param {?Object.<string>=} opt_params Map of parameters for the URL.
* @param {?Object.<string>=} opt_options Options for passing to UrlFetchApp
* for example, to set the method to POST, or to include a form body.
* @return {?Object} The resulting object on success, or null if a failure.
*/
OAuth1UrlFetchApp.prototype.fetch = function(url, opt_params, opt_options) {
var oauthParams = {
'oauth_consumer_key': this.consumerKey_,
'oauth_timestamp': parseInt(new Date().getTime() / 1000),
'oauth_nonce': this.generateNonce_(),
'oauth_version': '1.0',
'oauth_token': this.accessToken_,
'oauth_signature_method': 'HMAC-SHA1'
};
var method = 'GET';
if (opt_options && opt_options.method) {
method = opt_options.method;
}
if (opt_options && opt_options.payload) {
var formPayload = opt_options.payload;
}
var requestString =
this.generateRequestString_(oauthParams, opt_params, formPayload);
var signatureBaseString =
this.generateSignatureBaseString_(method, url, requestString);
var signature = Utilities.computeHmacSignature(
Utilities.MacAlgorithm.HMAC_SHA_1, signatureBaseString,
this.getSigningKey_());
var b64signature = Utilities.base64Encode(signature);
oauthParams['oauth_signature'] = this.escape_(b64signature);
var fetchOptions = opt_options || {};
fetchOptions['headers'] = {
Authorization: this.generateAuthorizationHeader_(oauthParams)
};
if (fetchOptions.payload) {
fetchOptions.payload = this.escapeForm_(fetchOptions.payload);
}
return UrlFetchApp.fetch(
this.joinUrlToParams_(url, opt_params), fetchOptions);
};
/**
* Concatenates request URL to parameters to form a single string.
* @param {string} url The URL of the resource.
* @param {?Object.<string>=} opt_params Optional key/value map of parameters.
* @return {string} The full path built out with parameters.
*/
OAuth1UrlFetchApp.prototype.joinUrlToParams_ = function(url, opt_params) {
if (!opt_params) {
return url;
}
var paramKeys = Object.keys(opt_params);
var paramList = [];
for (var i = 0, paramKey; paramKey = paramKeys[i]; i++) {
paramList.push([paramKey, opt_params[paramKey]].join('='));
}
return url + '?' + paramList.join('&');
};
/**
* Generates a random nonce for use in the OAuth request.
* @return {string} A random string.
*/
OAuth1UrlFetchApp.prototype.generateNonce_ = function() {
return Utilities
.base64Encode(Utilities.computeDigest(
Utilities.DigestAlgorithm.SHA_1,
parseInt(Math.floor(Math.random() * 10000))))
.replace(/[\/=_+]/g, '');
};
/**
* Creates a properly-formatted string from a map of key/values from a form
* post.
* @param {!Object.<string>} payload Map of key/values.
* @return {string} The formatted string for the body of the POST message.
*/
OAuth1UrlFetchApp.prototype.escapeForm_ = function(payload) {
var escaped = [];
var keys = Object.keys(payload);
for (var i = 0, key; key = keys[i]; i++) {
escaped.push([this.escape_(key), this.escape_(payload[key])].join('='));
}
return escaped.join('&');
};
/**
* Returns a percent-escaped string for use with OAuth. Note that
* encodeURIComponent is not sufficient for this as the Twitter API expects
* characters such as exclamation-mark to be encoded. See:
* https://dev.twitter.com/discussions/12378
* @param {string} str The string to be escaped.
* @return {string} The escaped string.
*/
OAuth1UrlFetchApp.prototype.escape_ = function(str) {
return encodeURIComponent(str).replace(/[!*()']/g, function(v) {
return '%' + v.charCodeAt().toString(16);
});
};
/**
* Generates the Authorization header using the OAuth parameters and
* calculated signature.
* @param {!Object} oauthParams A map of the required OAuth parameters. See:
* https://dev.twitter.com/oauth/overview/authorizing-requests
* @return {string} An Authorization header value for use in HTTP requests.
*/
OAuth1UrlFetchApp.prototype.generateAuthorizationHeader_ = function(
oauthParams) {
var params = [];
var keys = Object.keys(oauthParams).sort();
for (var i = 0, key; key = keys[i]; i++) {
params.push(key + '="' + oauthParams[key] + '"');
}
return 'OAuth ' + params.join(', ');
};
/**
* Generates the signature string for the request.
* @param {string} method The HTTP method e.g. GET, POST
* @param {string} The URL.
* @param {string} requestString The string representing the parameters to the
* API call as constructed by generateRequestString.
* @return {string} The signature base string. See:
* https://dev.twitter.com/oauth/overview/creating-signatures
*/
OAuth1UrlFetchApp.prototype.generateSignatureBaseString_ = function(
method, url, requestString) {
return [method, this.escape_(url), this.escape_(requestString)].join('&');
};
/**
* Generates the key for signing the OAuth request
* @return {string} The signing key.
*/
OAuth1UrlFetchApp.prototype.getSigningKey_ = function() {
return this.escape_(this.consumerSecret_) + '&' +
this.escape_(this.accessSecret_);
};
/**
* Generates the request string for signing, as used to produce a signature
* for the Authorization header. see:
* https://dev.twitter.com/oauth/overview/creating-signatures
* @param {!Object} oauthParams The required OAuth parameters for the request,
* see: https://dev.twitter.com/oauth/overview/authorizing-requests
* @param {?Object=} opt_params Optional parameters specified as part of the
* request, in map form, for example to specify /path?a=b&c=d&e=f... etc
* @param {?Object=} opt_formPayload Optional mapping of pairs used in a form
* as part of a POST request.
* @return {string} The request string
*/
OAuth1UrlFetchApp.prototype.generateRequestString_ = function(
oauthParams, opt_params, opt_formPayload) {
var requestParams = {};
var requestPath = [];
for (var i = 0; i < arguments.length; i++) {
var mapping = arguments[i];
if (mapping) {
var paramKeys = Object.keys(mapping);
for (var j = 0, paramKey; paramKey = paramKeys[j]; j++) {
requestParams[paramKey] = mapping[paramKey];
}
}
}
var requestKeys = Object.keys(requestParams);
requestKeys.sort();
for (var m = 0, requestKey; requestKey = requestKeys[m]; m++) {
requestPath.push([
this.escape_(requestKey), this.escape_(requestParams[requestKey])
].join('='));
}
return requestPath.join('&');
};
/**
* Builds a OAuth1UrlFetchApp object based on supplied access token (and other
* parameters.
* @param {string} consumerKey
* @param {string} consumerSecret
* @param {string} accessToken
* @param {string} accessSecret
* @return {!OAuth1UrlFetchApp}
*/
function withAccessToken(
consumerKey, consumerSecret, accessToken, accessSecret) {
return new OAuth1UrlFetchApp(
consumerKey, consumerSecret, accessToken, accessSecret);
}
scope.OAuth1 = {withAccessToken: withAccessToken};
})(this);