Skip to content

Commit

Permalink
feat(syndicator-twitter): always use absolute urls for uploading media
Browse files Browse the repository at this point in the history
  • Loading branch information
paulrobertlloyd committed Feb 13, 2021
1 parent 32e1f35 commit 5190195
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 23 deletions.
4 changes: 2 additions & 2 deletions packages/syndicator-twitter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const TwitterSyndicator = class {
return this.info.uid;
}

async syndicate(properties) {
return twitter(this.options).post(properties);
async syndicate(properties, publication) {
return twitter(this.options).post(properties, publication);
}
};
12 changes: 8 additions & 4 deletions packages/syndicator-twitter/lib/twitter.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import got from 'got';
import Twitter from 'twitter-lite';
import {
createStatus,
getAbsoluteUrl,
getStatusIdFromUrl,
isTweetUrl
} from './utils.js';
Expand Down Expand Up @@ -73,17 +74,19 @@ export const twitter = options => ({
* Upload media and return Twitter media id
*
* @param {string} media JF2 media object
* @param {string} me Publication URL
* @returns {string} Twitter media id
*/
async uploadMedia(media) {
async uploadMedia(media, me) {
const {alt, url} = media;

if (typeof url !== 'string') {
return;
}

try {
const response = await got(url, {responseType: 'buffer'});
const mediaUrl = getAbsoluteUrl(url, me);
const response = await got(mediaUrl, {responseType: 'buffer'});
const buffer = Buffer.from(response.body).toString('base64');
const {media_id_string} = await this.client('upload').post('media/upload', {media_data: buffer});

Expand All @@ -105,9 +108,10 @@ export const twitter = options => ({
* Post to Twitter
*
* @param {object} properties JF2 properties object
* @param {object} publication Publication configuration
* @returns {string} URL of syndicated tweet
*/
async post(properties) {
async post(properties, publication) {
let mediaIds = [];

// Upload photos
Expand All @@ -117,7 +121,7 @@ export const twitter = options => ({
// Trim to 4 photos as Twitter doesn’t support more
const photos = properties.photo.slice(0, 4);
for await (const photo of photos) {
uploads.push(this.uploadMedia(photo));
uploads.push(this.uploadMedia(photo, publication.me));
}

mediaIds = await Promise.all(uploads);
Expand Down
16 changes: 16 additions & 0 deletions packages/syndicator-twitter/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,22 @@ export const createStatus = (properties, mediaIds = false) => {
return parameters;
};

/**
* Get absolute URL
*
* @param {string} string URL or path
* @param {string} me Publication URL
* @returns {URL} Absolute URL
*/
export const getAbsoluteUrl = (string, me) => {
try {
return new URL(string).toString();
} catch {
const absoluteUrl = path.posix.join(me, string);
return new URL(absoluteUrl).toString();
}
};

/**
* Get status ID from Twitter status URL
*
Expand Down
40 changes: 23 additions & 17 deletions packages/syndicator-twitter/tests/unit/twitter.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint-disable camelcase */
import 'dotenv/config.js'; // eslint-disable-line import/no-unassigned-import
import test from 'ava';
import nock from 'nock';
import {getFixture} from '@indiekit-test/get-fixture';
Expand All @@ -22,6 +23,9 @@ test.beforeEach(t => {
accessTokenKey: 'ABCDEFGHIJKLMNabcdefghijklmnopqrstuvwxyz0123456789',
accessTokenSecret: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN',
user: 'username'
},
publication: {
me: process.env.TEST_PUBLICATION_URL
}
};
});
Expand Down Expand Up @@ -133,7 +137,7 @@ test('Throws error fetching media to upload', async t => {
.get('/image.jpg')
.replyWithError('Not found');

await t.throwsAsync(twitter(t.context.options).uploadMedia(t.context.media), {
await t.throwsAsync(twitter(t.context.options).uploadMedia(t.context.media, t.context.publication), {
message: /Not found/
});
});
Expand All @@ -151,7 +155,7 @@ test('Uploads media and returns a media id', async t => {
.post('/1.1/media/metadata/create.json')
.reply(200, {});

const result = await twitter(t.context.options).uploadMedia(t.context.media);
const result = await twitter(t.context.options).uploadMedia(t.context.media, t.context.publication);

t.is(result, '1234567890987654321');
});
Expand All @@ -168,13 +172,13 @@ test('Throws error uploading media', async t => {
}]
});

await t.throwsAsync(twitter(t.context.options).uploadMedia(t.context.media), {
await t.throwsAsync(twitter(t.context.options).uploadMedia(t.context.media, t.context.publication), {
message: /Not found/
});
});

test('Returns false passing an object to media upload function', async t => {
const result = await twitter(t.context.options).uploadMedia({foo: 'bar'});
const result = await twitter(t.context.options).uploadMedia({foo: 'bar'}, t.context.publication);

t.falsy(result);
});
Expand All @@ -186,15 +190,15 @@ test('Posts a like of a tweet to Twitter', async t => {

const result = await twitter(t.context.options).post({
'like-of': t.context.tweetUrl
});
}, t.context.publication);

t.is(result, 'https://twitter.com/username/status/1234567890987654321');
});

test('Doesn’t post a like of a URL to Twitter', async t => {
const result = await twitter(t.context.options).post({
'like-of': 'https://foo.bar/lunchtime'
});
}, t.context.publication);

t.falsy(result);
});
Expand All @@ -206,15 +210,15 @@ test('Posts a repost of a tweet to Twitter', async t => {

const result = await twitter(t.context.options).post({
'repost-of': t.context.tweetUrl
});
}, t.context.publication);

t.is(result, 'https://twitter.com/username/status/1234567890987654321');
});

test('Doesn’t post a repost of a URL to Twitter', async t => {
const result = await twitter(t.context.options).post({
'repost-of': 'https://foo.bar/lunchtime'
});
}, t.context.publication);

t.falsy(result);
});
Expand All @@ -228,7 +232,7 @@ test('Posts a quote status to Twitter', async t => {
content: 'Someone else who likes cheese sandwiches.',
'repost-of': t.context.tweetUrl,
'post-type': 'repost'
});
}, t.context.publication);

t.is(result, 'https://twitter.com/username/status/1234567890987654321');
});
Expand All @@ -244,19 +248,19 @@ test('Posts a status to Twitter', async t => {
text: 'I ate a cheese sandwich, which was nice.'
},
url: 'https://foo.bar/lunchtime'
});
}, t.context.publication);

t.is(result, 'https://twitter.com/username/status/1234567890987654321');
});

test('Posts a status to Twitter with 4 out of 5 photos', async t => {
nock('https://website.example')
nock(t.context.publication.me)
.get('/image1.jpg')
.reply(200, {body: getFixture('file-types/photo.jpg', false)});
nock('https://website.example')
nock(t.context.publication.me)
.get('/image2.jpg')
.reply(200, {body: getFixture('file-types/photo.jpg', false)});
nock('https://website.example')
nock(t.context.publication.me)
.get('/image3.jpg')
.reply(200, {body: getFixture('file-types/photo.jpg', false)});
nock('https://website.example')
Expand All @@ -281,13 +285,15 @@ test('Posts a status to Twitter with 4 out of 5 photos', async t => {
const result = await twitter(t.context.options).post({
content: 'Here’s the cheese sandwiches I ate.',
photo: [
{url: 'https://website.example/image1.jpg'},
{url: 'https://website.example/image2.jpg'},
{url: 'https://website.example/image3.jpg'},
{url: `${t.context.publication.me}image1.jpg`},
{url: `${t.context.publication.me}image2.jpg`},
{url: 'image3.jpg'},
{url: 'https://website.example/image4.jpg'},
{url: 'https://website.example/image5.jpg'}
]
});
}, t.context.publication);

t.log(t.context.publication.me);

t.is(result, 'https://twitter.com/username/status/1234567890987654321');
});
14 changes: 14 additions & 0 deletions packages/syndicator-twitter/tests/unit/utils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import 'dotenv/config.js'; // eslint-disable-line import/no-unassigned-import
import test from 'ava';
import {getFixture} from '@indiekit-test/get-fixture';
import {
createStatus,
getAbsoluteUrl,
getStatusIdFromUrl,
htmlToStatusText,
isTweetUrl
Expand Down Expand Up @@ -76,6 +78,18 @@ test('Tests if string is a tweet permalink', t => {
t.false(isTweetUrl('https://getindiekit.com'));
});

test('Gets absolute URL', t => {
const result = getAbsoluteUrl(`${process.env.TEST_PUBLICATION_URL}media/photo.jpg`, process.env.TEST_PUBLICATION_URL);

t.is(result, `${process.env.TEST_PUBLICATION_URL}media/photo.jpg`);
});

test('Gets absolute URL by prepending publication URL', t => {
const result = getAbsoluteUrl('/media/photo.jpg', process.env.TEST_PUBLICATION_URL);

t.is(result, `${process.env.TEST_PUBLICATION_URL}media/photo.jpg`);
});

test('Gets status ID from Twitter permalink', t => {
const result = getStatusIdFromUrl('https://twitter.com/paulrobertlloyd/status/1341502435760680961');

Expand Down

0 comments on commit 5190195

Please sign in to comment.