Pulled in the twitter content backup functionality from curvyandtrans.com
Sadly, lost the images from one of Emmy_Zje's deleted tweets.
@ -12,16 +12,17 @@ const actions = {
|
||||
return readFile(input);
|
||||
},
|
||||
|
||||
async transcode ({ input, output }) {
|
||||
async transcode ({ input, output, duplicate }) {
|
||||
const result = await actions.image({
|
||||
input,
|
||||
output,
|
||||
duplicate,
|
||||
format: 'jpeg',
|
||||
});
|
||||
return result;
|
||||
},
|
||||
|
||||
async fetch ({ input, output }) {
|
||||
async fetch ({ input, output, duplicate }) {
|
||||
const res = await fetch(input);
|
||||
if (res.status !== 200) {
|
||||
throw new Error(`File could not be fetched (${res.status}): "${input}"`);
|
||||
@ -30,16 +31,24 @@ const actions = {
|
||||
output = resolve(output);
|
||||
await fs.ensureDir(path.dirname(output));
|
||||
await fs.writeFile(output, body);
|
||||
if (duplicate) {
|
||||
await fs.ensureDir(resolve(path.dirname(duplicate)));
|
||||
await fs.writeFile(duplicate, body);
|
||||
}
|
||||
return body;
|
||||
},
|
||||
|
||||
async write ({ output, content }) {
|
||||
async write ({ output, content, duplicate }) {
|
||||
if (!content) {
|
||||
throw new TypeError('Got an empty write?' + output);
|
||||
}
|
||||
output = resolve(output);
|
||||
await fs.ensureDir(path.dirname(output));
|
||||
await fs.writeFile(output, content);
|
||||
if (duplicate) {
|
||||
await fs.ensureDir(resolve(path.dirname(duplicate)));
|
||||
await fs.writeFile(duplicate, content);
|
||||
}
|
||||
return Buffer.from(content);
|
||||
},
|
||||
|
||||
@ -157,6 +166,10 @@ const actions = {
|
||||
let result = await Promise.fromCallback((cb) => gmfile.toBuffer(cb));
|
||||
if (options.format === 'ico') result = await ico(result);
|
||||
await fs.writeFile(output, result);
|
||||
if (options.duplicate) {
|
||||
await fs.ensureDir(resolve(path.dirname(options.duplicate)));
|
||||
await fs.writeFile(options.duplicate, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
@ -72,10 +72,10 @@ module.exports = exports = class Manifest {
|
||||
|
||||
async get (task) {
|
||||
const hash = this.hash(task);
|
||||
const { input, output } = task;
|
||||
const { input, output, cache: altCachePath } = task;
|
||||
const ext = path.extname(task.output);
|
||||
const local = !task.input.includes('://');
|
||||
const cached = path.join(CACHE, hash + ext);
|
||||
var cached = path.join(CACHE, hash + ext);
|
||||
const result = {
|
||||
iTime: 0,
|
||||
iRev: null,
|
||||
@ -85,9 +85,15 @@ module.exports = exports = class Manifest {
|
||||
action: task.action.name,
|
||||
input,
|
||||
output,
|
||||
duplicate: altCachePath,
|
||||
mode: 'new',
|
||||
};
|
||||
|
||||
var acTime;
|
||||
if (altCachePath) {
|
||||
acTime = await this.stat(altCachePath);
|
||||
}
|
||||
|
||||
const [ iTime, oTime, cTime, iRev ] = await Promise.all([
|
||||
local && this.stat(input),
|
||||
this.stat(output),
|
||||
@ -148,7 +154,17 @@ module.exports = exports = class Manifest {
|
||||
}
|
||||
|
||||
result.mode = 'cached';
|
||||
|
||||
if (acTime && acTime > cTime) {
|
||||
result.cache = await readFile(altCachePath);
|
||||
} else {
|
||||
result.cache = await readFile(cached);
|
||||
if (altCachePath && !acTime) {
|
||||
await fs.ensureDir(resolve(path.dirname(altCachePath)));
|
||||
await fs.writeFile(resolve(altCachePath), result.cache);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -157,7 +173,7 @@ module.exports = exports = class Manifest {
|
||||
if (task.nocache || !task.action.name) return null;
|
||||
|
||||
const hash = this.hash(task);
|
||||
const { input, output } = task;
|
||||
const { input, output, cache: altCachePath } = task;
|
||||
const local = !task.input.includes('://');
|
||||
|
||||
const [ iTime, iRev ] = await Promise.all([
|
||||
@ -175,6 +191,7 @@ module.exports = exports = class Manifest {
|
||||
output,
|
||||
oTime: Math.floor(lastSeen / 1000),
|
||||
lastSeen,
|
||||
duplicate: altCachePath,
|
||||
};
|
||||
|
||||
if (record.revPath) this.revManifest[output] = record.revPath;
|
||||
@ -185,17 +202,22 @@ module.exports = exports = class Manifest {
|
||||
|
||||
async set (task, result, lastSeen = new Date()) {
|
||||
const hash = this.hash(task);
|
||||
const { input, output } = task;
|
||||
const { input, output, cache: altCachePath } = task;
|
||||
const nocache = task.nocache || task.action.name === 'copy';
|
||||
const ext = path.extname(task.output);
|
||||
const local = !task.input.includes('://');
|
||||
const cached = path.join(CACHE, hash + ext);
|
||||
const oRev = revHash(result);
|
||||
|
||||
if (result && altCachePath) {
|
||||
await fs.ensureDir(resolve(path.dirname(altCachePath)));
|
||||
}
|
||||
|
||||
const [ iTime, iRev ] = await Promise.all([
|
||||
local && this.stat(input),
|
||||
local && this.compareBy.inputRev && this.revFile(input),
|
||||
result && !nocache && fs.writeFile(resolve(cached), result),
|
||||
result && altCachePath && fs.writeFile(resolve(altCachePath), result),
|
||||
]);
|
||||
|
||||
const record = {
|
||||
@ -208,6 +230,7 @@ module.exports = exports = class Manifest {
|
||||
oTime: Math.floor(lastSeen / 1000),
|
||||
oRev,
|
||||
lastSeen,
|
||||
duplicate: altCachePath,
|
||||
revPath: revPath(output, oRev),
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
var twemoji = require('twemoji' );
|
||||
const { deepPick, has } = require('./util');
|
||||
const path = require('path');
|
||||
|
||||
const schema = {
|
||||
id_str: true,
|
||||
@ -23,6 +24,7 @@ const schema = {
|
||||
} ] },
|
||||
} ] },
|
||||
media: true,
|
||||
in_reply_to_status_id_str: true,
|
||||
};
|
||||
|
||||
var entityProcessors = {
|
||||
@ -94,7 +96,8 @@ module.exports = exports = function (tweets) {
|
||||
|
||||
tweet.user.avatar = {
|
||||
input: tweet.user.profile_image_url_https,
|
||||
output: 'tweets/' + tweet.user.screen_name + '.jpg',
|
||||
output: `tweets/${tweet.user.screen_name}.jpg`,
|
||||
cache: `twitter-avatars/${tweet.user.screen_name}.jpg`,
|
||||
};
|
||||
|
||||
tweet.media = [
|
||||
@ -113,7 +116,22 @@ module.exports = exports = function (tweets) {
|
||||
}
|
||||
|
||||
if (has(tweet, 'entities.media') && has(tweet, 'extended_entities.media')) {
|
||||
tweet.entities.media = tweet.extended_entities.media;
|
||||
tweet.entities.media = tweet.extended_entities.media.map((media) => {
|
||||
media = { ...media };
|
||||
if (media.media_url_https) {
|
||||
const mediaItem = {
|
||||
input: media.media_url_https,
|
||||
output: `tweets/${tweet.id_str}/${path.basename(media.media_url_https)}`,
|
||||
cache: `twitter-entities/${tweet.id_str}/${path.basename(media.media_url_https)}`,
|
||||
};
|
||||
if (media.type === 'photo') mediaItem.input += '?name=medium';
|
||||
tweet.media.push(mediaItem);
|
||||
media.media_url_https = '/' + mediaItem.output;
|
||||
}
|
||||
|
||||
return media;
|
||||
});
|
||||
|
||||
delete tweet.extended_entities;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { chunk, uniq, difference } = require('lodash');
|
||||
const { chunk, uniq, uniqBy, difference } = require('lodash');
|
||||
const fs = require('fs-extra');
|
||||
const { resolve } = require('./resolve');
|
||||
const log = require('fancy-log');
|
||||
@ -65,7 +65,7 @@ module.exports = exports = async function tweets (pages) {
|
||||
|
||||
/* Apply Tweets to Pages **************************************************/
|
||||
|
||||
const twitterMedia = [];
|
||||
var twitterMedia = [];
|
||||
|
||||
function attachTweet (dict, tweetid) {
|
||||
if (!hasOwn(twitterCache, tweetid) && twitterBackup[tweetid]) {
|
||||
@ -91,6 +91,8 @@ module.exports = exports = async function tweets (pages) {
|
||||
}, {});
|
||||
}
|
||||
|
||||
twitterMedia = uniqBy(twitterMedia, 'output');
|
||||
|
||||
await Promise.all([
|
||||
fs.writeFile(resolve('twitter-media.json'), JSON.stringify(twitterMedia, null, 2)),
|
||||
fs.writeFile(resolve('twitter-cache.json'), JSON.stringify(twitterCache, null, 2)),
|
||||
|
107
build/twitter-client.js
Normal file
@ -0,0 +1,107 @@
|
||||
const fs = require('fs-extra');
|
||||
const { resolve } = require('./resolve');
|
||||
const { chunk, uniq, difference } = require('lodash');
|
||||
const Twitter = require('twitter-lite');
|
||||
const log = require('fancy-log');
|
||||
const tweetparse = require('./lib/tweetparse');
|
||||
const { hasOwn } = require('./lib/util');
|
||||
|
||||
module.exports = exports = async function () {
|
||||
const tc = new TwitterClient();
|
||||
await tc.initialize();
|
||||
return tc;
|
||||
};
|
||||
|
||||
|
||||
class TwitterClient {
|
||||
|
||||
async initialize () {
|
||||
const [ lookup, backup, cache ] = await Promise.all([
|
||||
fs.readJson(resolve('twitter-config.json')).catch(() => null)
|
||||
.then(makeFetcher),
|
||||
fs.readJson(resolve('twitter-backup.json')).catch(() => ({})),
|
||||
fs.readJson(resolve('twitter-cache.json')).catch(() => ({})),
|
||||
]);
|
||||
|
||||
this._lookup = lookup;
|
||||
this._backupData = backup;
|
||||
this._cache = cache;
|
||||
this._presentTweets = Object.keys(cache);
|
||||
}
|
||||
|
||||
async write () {
|
||||
return Promise.all([
|
||||
fs.writeFile(resolve('twitter-cache.json'), JSON.stringify(this._cache, null, 2)),
|
||||
fs.writeFile(resolve('twitter-backup.json'), JSON.stringify(this._backupData, null, 2)),
|
||||
]);
|
||||
}
|
||||
|
||||
async get (tweetids) {
|
||||
if (!Array.isArray(tweetids)) tweetids = [ tweetids ];
|
||||
|
||||
tweetids = uniq(tweetids.map(parseTweetId));
|
||||
|
||||
let tweetsNeeded = this._missing(tweetids);
|
||||
|
||||
while (tweetsNeeded.length) {
|
||||
log('Fetching tweets: ' + tweetsNeeded.join(', '));
|
||||
const arriving = await Promise.all(chunk(tweetsNeeded, 99).map(this._lookup));
|
||||
const tweetsRequested = tweetsNeeded;
|
||||
tweetsNeeded = [];
|
||||
const loaded = [];
|
||||
for (const tweet of arriving.flat(1)) {
|
||||
if (tweet.quoted_status_id_str && !this._cache[tweet.quoted_status_id_str]) {
|
||||
tweetsNeeded.push(tweet.quoted_status_id_str);
|
||||
}
|
||||
|
||||
this._backupData[tweet.id_str] = tweet;
|
||||
this._cache[tweet.id_str] = tweetparse(tweet);
|
||||
loaded.push(tweet.id_str);
|
||||
}
|
||||
|
||||
const absent = difference(tweetsRequested, loaded);
|
||||
for (const id of absent) {
|
||||
if (!hasOwn(this._backupData, id)) {
|
||||
log.error('Could not find tweet ' + id);
|
||||
continue;
|
||||
}
|
||||
const tweet = this._backupData[id];
|
||||
|
||||
if (tweet) {
|
||||
log('Pulled tweet from backup ' + id);
|
||||
this._cache[id] = tweetparse(this._backupData[id]);
|
||||
} else {
|
||||
this._cache[id] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tweetids.map((id) => this._cache[id] || null);
|
||||
}
|
||||
|
||||
_missing (tweetids) {
|
||||
return difference(tweetids, this._presentTweets);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function makeFetcher (config) {
|
||||
if (!config) return () => [];
|
||||
const client = new Twitter(config);
|
||||
return (tweetids) => client
|
||||
.get('statuses/lookup', { id: tweetids.join(','), tweet_mode: 'extended' })
|
||||
// .then((r) => { console.log({r}); return r; })
|
||||
.catch((e) => { log.error(e); return []; });
|
||||
}
|
||||
|
||||
const tweeturl = /https?:\/\/twitter\.com\/(?:#!\/)?(?:\w+)\/status(?:es)?\/(\d+)/i;
|
||||
const tweetidcheck = /^\d+$/;
|
||||
function parseTweetId (tweetid) {
|
||||
// we can't trust an id that isn't a string
|
||||
if (typeof tweetid !== 'string') return false;
|
||||
|
||||
const match = tweetid.match(tweeturl);
|
||||
if (match) return match[1];
|
||||
if (tweetid.match(tweetidcheck)) return tweetid;
|
||||
return false;
|
||||
}
|
34
build/twitter-thread.js
Normal file
@ -0,0 +1,34 @@
|
||||
|
||||
const twitterClient = require('./twitter-client');
|
||||
|
||||
module.exports = exports = async function loadThread (tweetid) {
|
||||
const tc = await twitterClient();
|
||||
|
||||
async function quoteds (tweet) {
|
||||
if (!tweet.quoted_status_id_str) return [];
|
||||
const [ qt ] = await tc.get(tweet.quoted_status_id_str);
|
||||
if (!qt) return [];
|
||||
return [ qt.id_str, ...(await quoteds(qt)) ];
|
||||
}
|
||||
|
||||
const embeds = [];
|
||||
const dependencies = [];
|
||||
let id = tweetid;
|
||||
do {
|
||||
const [ tweet ] = await tc.get(id);
|
||||
if (!tweet) break;
|
||||
embeds.unshift(tweet.id_str);
|
||||
dependencies.unshift(tweet.id_str);
|
||||
|
||||
if (tweet.quoted_status_id_str) {
|
||||
const qts = await quoteds(tweet);
|
||||
if (qts.length) dependencies.unshift(...qts);
|
||||
}
|
||||
|
||||
id = tweet.in_reply_to_status_id_str;
|
||||
} while (id);
|
||||
|
||||
await tc.write();
|
||||
|
||||
return [ embeds, dependencies ];
|
||||
};
|
BIN
twitter-avatars/ABC.jpg
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
twitter-avatars/AFortune69.jpg
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
twitter-avatars/AOC.jpg
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
twitter-avatars/Adoratrix.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/Amsteffy87.jpg
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
twitter-avatars/BathysphereHat.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/BellaRizinti.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/BonzoixofMN.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/CateSpice.jpg
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
twitter-avatars/Chican3ry.jpg
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
twitter-avatars/CognitiveSoc.jpg
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
twitter-avatars/CrypticGoblin.jpg
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
twitter-avatars/CuddlePotato.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/DameKraft.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/DrRachaelF.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/EloraEdwards.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/Emmy_Zje.jpg
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
twitter-avatars/ErinInTheMorn.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/GenderGP.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/KaliRainH2O.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/KatyMontgomerie.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/LisaTMullin.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/MagsVisaggs.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/MoonyXIV.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/NMorganCreates.jpg
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
twitter-avatars/NightlingBug.jpg
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
twitter-avatars/OneWeirdAngel.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/OttoLontra.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/QuinnPBrown.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/RaeGun2k.jpg
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
twitter-avatars/RebeccaRHelm.jpg
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
twitter-avatars/RileyFaelan.jpg
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
twitter-avatars/RoseOfWindsong.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/RowanChurch.jpg
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
twitter-avatars/S_Winterthorn.jpg
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
twitter-avatars/SassKnight.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/SecretGamerGrrl.jpg
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
twitter-avatars/ShaggyDemiurge.jpg
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
twitter-avatars/TheEmmelineMay.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/TransEthics.jpg
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
twitter-avatars/TransSalamander.jpg
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
twitter-avatars/Tuplet.jpg
Normal file
After Width: | Height: | Size: 769 B |
BIN
twitter-avatars/TwippingVanilla.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/alexand_erleon.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/alicemiriel.jpg
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
twitter-avatars/ayumeril.jpg
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
twitter-avatars/biscuit_heid.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/blotchkat.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/buttonsandfluff.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/bzarcher.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/canamKim.jpg
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
twitter-avatars/cismender.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/crypticenbug.jpg
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
twitter-avatars/delaneykingrox.jpg
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
twitter-avatars/itsaddis.jpg
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
twitter-avatars/meimeimeixie.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/naylorillo.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/persenche.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/queenofnevers.jpg
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
twitter-avatars/raddifferent.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/rozietoez.jpg
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
twitter-avatars/snakmicks.jpg
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
twitter-avatars/tedcruz.jpg
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
twitter-avatars/tomorrowville.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
twitter-avatars/two_n_minus_one.jpg
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
twitter-avatars/zeghostboy.jpg
Normal file
After Width: | Height: | Size: 1.9 KiB |
12437
twitter-backup.json
BIN
twitter-entities/1025590255733366784/Djugv7jWwAAGVVM.jpg
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
twitter-entities/1025590255733366784/Djugv7jXcAA8scb.jpg
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
twitter-entities/1025590255733366784/Djugv7lW0AAq52Z.jpg
Normal file
After Width: | Height: | Size: 63 KiB |
BIN
twitter-entities/1046808859074080768/DocC-j8XgAgXutl.jpg
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
twitter-entities/1092180584653381632/Dyg0IXMXQAAgrig.jpg
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
twitter-entities/1094677550486310917/DzETBhQUYAAWJSc.jpg
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
twitter-entities/1094678128335568896/DzETp3jU0AA1F26.jpg
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
twitter-entities/1094683008919957504/DzEYN-7VYAAgq1Y.jpg
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
twitter-entities/1094687092871880705/DzEbX1AUwAA157v.jpg
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
twitter-entities/1094717735974490112/DzE38mUUcAAAAWN.jpg
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
twitter-entities/1094730604501848064/DzFDbTwV4AAFFFh.jpg
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
twitter-entities/1162051481656266760/ECBvqZnW4Agg00A.png
Normal file
After Width: | Height: | Size: 63 KiB |
BIN
twitter-entities/1214536380501524481/ENrmY-hU8AET_25.jpg
Normal file
After Width: | Height: | Size: 188 KiB |
BIN
twitter-entities/1228717614630940672/EQ1IKINWkAAllKR.jpg
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
twitter-entities/1232817112139341824/rY9vCIuJrqOn2kiH.jpg
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
twitter-entities/1235235759721979906/ESRuPAHWoAA0WVi.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
twitter-entities/1235771137164009474/ESZXRt_X0AAbHrS.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
twitter-entities/1286504315960438784/EdqUVvHUMAE-0jk.png
Normal file
After Width: | Height: | Size: 299 KiB |
BIN
twitter-entities/1351347418332278785/EsDzUBtXIAANJUQ.jpg
Normal file
After Width: | Height: | Size: 226 KiB |
BIN
twitter-entities/1351349705071087616/EsD1ZOBXAAM3N4L.jpg
Normal file
After Width: | Height: | Size: 288 KiB |
BIN
twitter-entities/1351352097179107330/EsD3kb1XUAU0Awa.jpg
Normal file
After Width: | Height: | Size: 133 KiB |
BIN
twitter-entities/1351354176178188288/EsD5dbsXAAE09up.jpg
Normal file
After Width: | Height: | Size: 249 KiB |
BIN
twitter-entities/1351355625779961858/EsD6x0UXAAYTGpw.jpg
Normal file
After Width: | Height: | Size: 316 KiB |
BIN
twitter-entities/1351355676992290816/EsD6QkHVcAAEUIN.jpg
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
twitter-entities/1351358737513123844/EsD9m5JXAAMxDRR.jpg
Normal file
After Width: | Height: | Size: 265 KiB |
BIN
twitter-entities/1351361678257041410/EsEASKzXEAUqrPV.jpg
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
twitter-entities/1351362749008326656/EsEBQhDXAAECUBf.jpg
Normal file
After Width: | Height: | Size: 46 KiB |