aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--vnext/config/test.json1
-rw-r--r--vnext/server/common/MessageUtils.js16
-rw-r--r--vnext/server/common/MessageUtils.spec.js2
-rw-r--r--vnext/server/common/__snapshots__/MessageUtils.spec.js.snap2
-rw-r--r--vnext/server/durov.js33
-rw-r--r--vnext/server/http.js8
-rw-r--r--vnext/server/index.js2
-rw-r--r--vnext/server/middleware/event.js33
-rw-r--r--vnext/server/sender.js15
-rw-r--r--vnext/src/utils/embed.js206
10 files changed, 199 insertions, 119 deletions
diff --git a/vnext/config/test.json b/vnext/config/test.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/vnext/config/test.json
@@ -0,0 +1 @@
+{}
diff --git a/vnext/server/common/MessageUtils.js b/vnext/server/common/MessageUtils.js
index f766faf2..74638c02 100644
--- a/vnext/server/common/MessageUtils.js
+++ b/vnext/server/common/MessageUtils.js
@@ -1,3 +1,5 @@
+import config from 'config';
+
/**
* check if message is PM
*
@@ -64,3 +66,17 @@ export function formatQuote(msg) {
export function formatMessage(msg) {
return msg.body || 'Sent an image';
}
+
+const baseURL = 'https://juick.com';
+
+/**
+ * @param {import('../api').Message} msg message
+ */
+export function formatUrl(msg) {
+ if (isReply(msg)) {
+ return `${baseURL}/m/${msg.mid}#${msg.rid}`;
+ } else if (isPM(msg)) {
+ return `${baseURL}/pm/inbox`;
+ }
+ return `${baseURL}/m/${msg.mid}`;
+}
diff --git a/vnext/server/common/MessageUtils.spec.js b/vnext/server/common/MessageUtils.spec.js
index 766b1882..5e6f64c2 100644
--- a/vnext/server/common/MessageUtils.spec.js
+++ b/vnext/server/common/MessageUtils.spec.js
@@ -27,7 +27,7 @@ describe('Message formatting', () => {
'uname': 'ugnich'
},
'replyQuote': '> The message',
- 'body': 'The reply'
+ 'body': 'The #reply #bla'
};
expect(formatTitle(msg)).toMatchSnapshot();
expect(formatQuote(msg)).toMatchSnapshot();
diff --git a/vnext/server/common/__snapshots__/MessageUtils.spec.js.snap b/vnext/server/common/__snapshots__/MessageUtils.spec.js.snap
index ea58aebc..fb3c3513 100644
--- a/vnext/server/common/__snapshots__/MessageUtils.spec.js.snap
+++ b/vnext/server/common/__snapshots__/MessageUtils.spec.js.snap
@@ -14,4 +14,4 @@ exports[`Message formatting Reply message 1`] = `"Reply by ugnich:"`;
exports[`Message formatting Reply message 2`] = `"> The message"`;
-exports[`Message formatting Reply message 3`] = `"The reply"`;
+exports[`Message formatting Reply message 3`] = `"The #reply #bla"`;
diff --git a/vnext/server/durov.js b/vnext/server/durov.js
new file mode 100644
index 00000000..0f70133d
--- /dev/null
+++ b/vnext/server/durov.js
@@ -0,0 +1,33 @@
+import TelegramBot from 'node-telegram-bot-api';
+import debug from 'debug';
+var log = debug('durov');
+import config from 'config';
+
+
+import { formatMessage, formatQuote, formatTitle } from './common/MessageUtils';
+import { format } from '../src/utils/embed';
+
+const sender = new TelegramBot(config.get('service.durov.token'), { polling: true });
+const demouser = config.get('service.durov.demouser');
+
+sender.on('message', msg => {
+ log(`MESSAGE: ${JSON.stringify(msg)}`);
+});
+
+/**
+ * @param {import('../src/api').Message} msg
+ * @param {string[]} subscribers
+ */
+export const sendTelegramNotification = (msg, subscribers) => {
+ log(`Telegram event: ${JSON.stringify(msg)}, ${subscribers} ${subscribers.length}`);
+ if (!msg.service) {
+ if (subscribers && subscribers.includes(demouser)) {
+ const message = `${formatTitle(msg)}\n${formatQuote(msg)}\n${format(msg.body, msg.uid, false)}`;
+ log(message);
+ sender.sendMessage(demouser, message, {
+ parse_mode: 'HTML',
+ disable_web_page_preview: true
+ }).then(log).catch(log);
+ }
+ }
+};
diff --git a/vnext/server/http.js b/vnext/server/http.js
index c2557108..0ffa8bfe 100644
--- a/vnext/server/http.js
+++ b/vnext/server/http.js
@@ -1,6 +1,7 @@
import axios from 'axios';
import config from 'config';
-var debug = require('debug')('http');
+import debug from 'debug';
+var log = debug('http');
/**
@@ -17,6 +18,7 @@ export function subscribers(params) {
return new Promise((resolve, reject) => {
client.get(`/notifications?${params.toString()}`)
.then(response => {
+ log(`CODE: ${response.status}`);
resolve(response.data);
})
.catch(reason => { reject(reason); });
@@ -33,7 +35,7 @@ export const deleteSubscribers = async (tokens) => {
return response.data;
};
-const baseURL = config.get('service.baseURL') || process.env.JUICK_SERVICE_URL;
+const baseURL = config.get('service.baseURL') + '/api';
const user = config.get('service.user') || process.env.JUICK_SERVICE_USER;
const password = config.get('service.password') || process.env.JUICK_SERVICE_PASSWORD;
@@ -44,4 +46,4 @@ let client = axios.create({
'Authorization': 'Basic ' + Buffer.from(user + ':' + password).toString('base64')
} : {}
});
-debug(`HTTP client initialized with base URL ${baseURL} ${ user? `and ${user} user` : ''}`);
+log(`HTTP client initialized with base URL ${baseURL} ${ user? `and ${user} user` : ''}`);
diff --git a/vnext/server/index.js b/vnext/server/index.js
index 95f88cd1..89122069 100644
--- a/vnext/server/index.js
+++ b/vnext/server/index.js
@@ -5,7 +5,6 @@ import config from 'config';
import debug from 'debug';
const log = debug('http');
-// we'll talk about this in a minute:
import serverRenderer from './middleware/renderer';
import event from './middleware/event';
import oembed from './middleware/oembed';
@@ -37,6 +36,7 @@ router.use('*', serverRenderer);
app.use(router);
+
// start the app
app.listen(PORT, (error) => {
if (error) {
diff --git a/vnext/server/middleware/event.js b/vnext/server/middleware/event.js
index 54611280..1267d1c4 100644
--- a/vnext/server/middleware/event.js
+++ b/vnext/server/middleware/event.js
@@ -1,8 +1,27 @@
import { simpleParser } from 'mailparser';
import { isPM, isReply, isService } from '../common/MessageUtils';
+import { sendTelegramNotification } from '../durov';
import { subscribers } from '../http';
import { sendNotification, buildNotification } from '../sender';
-var debug = require('debug')('event');
+import debug from 'debug';
+var log = debug('event');
+import config from 'config';
+import EventSource from 'eventsource';
+
+const es = new EventSource(config.get('service.baseURL')+ '/api/events');
+es.addEventListener('msg', (msg) => {
+ log(msg.data);
+ processMessageEvent(JSON.parse(msg.data));
+});
+es.addEventListener('read', (msg) => {
+ log(msg);
+});
+es.addEventListener('open', () => {
+ log('online');
+});
+es.onerror = (evt) => {
+ log(`err: ${JSON.stringify(evt)}`);
+};
/** @type {number[]} */
const allSandboxIds = [];
@@ -23,7 +42,7 @@ function processMessageEvent(msg) {
}
subscribers(new URLSearchParams(JSON.parse(JSON.stringify(params)))).then(users => {
users.forEach(user => {
- debug(`${user.uname}: ${user.unreadCount}`);
+ log(`${user.uname}: ${user.unreadCount}`);
let [sandboxTokens, productionTokens] = (user.tokens || [])
.filter(t => ['mpns', 'apns', 'gcm'].includes(t.type))
.map(t => t.token)
@@ -34,8 +53,12 @@ function processMessageEvent(msg) {
return result;
}, [[], []]);
sendNotification(productionTokens, sandboxTokens, buildNotification(user, msg));
+ let durovIds = (user.tokens || [])
+ .filter(t => ['durov'].includes(t.type))
+ .map(t => t.token);
+ sendTelegramNotification(msg, durovIds);
});
- }).catch(console.error);
+ }).catch(log);
}
/**
@@ -47,7 +70,7 @@ export default function handleMessage(req, res) {
return simpleParser(req.body, {})
.then(parsed => {
const new_version = parsed.headers.get('x-event-version') == '1.0';
- debug(`New event: ${parsed.text}, new version: ${new_version}`);
+ log(`New event: ${parsed.text}, new version: ${new_version}`);
if (new_version) {
/** @type {import('../../client').SystemEvent} */
const event = JSON.parse(parsed.text || '');
@@ -71,5 +94,5 @@ export default function handleMessage(req, res) {
}
res.end();
})
- .catch(err => { console.error(err); res.status(400).send('Invalid request'); });
+ .catch(err => { log(err); res.status(400).send('Invalid request'); });
}
diff --git a/vnext/server/sender.js b/vnext/server/sender.js
index 6ece8eaa..f33eadcf 100644
--- a/vnext/server/sender.js
+++ b/vnext/server/sender.js
@@ -1,5 +1,6 @@
import PushNotifications from 'node-pushnotifications';
-var debug = require('debug')('sender');
+import debug from 'debug';
+const log = debug('sender');
import { deleteSubscribers } from './http';
import { formatMessage, formatTitle, formatQuote } from './common/MessageUtils';
import config from 'config';
@@ -51,7 +52,7 @@ export function sendNotification(productionIds, sandboxIds, data) {
sender.send(registrationIds, data)
.then((results) => {
results.forEach(result => {
- debug(`${result.method}: ${result.success} success, ${result.failure} failure`);
+ log(`${result.method}: ${result.success} success, ${result.failure} failure`);
if (result.failure) {
console.error(`${result.method} failure: ${JSON.stringify(result)}`);
console.error(`Failed data: ${JSON.stringify(data)}`);
@@ -59,13 +60,13 @@ export function sendNotification(productionIds, sandboxIds, data) {
});
results.filter(r => r.method === 'apn')
.forEach(async r => {
- debug(`Response message: ${JSON.stringify(r.message)}`);
+ log(`Response message: ${JSON.stringify(r.message)}`);
let badTokens = r.message.filter(m => m.errorMsg === 'BadDeviceToken').map(m => {
return { 'type': 'apns', 'token': m.regId };
});
if (badTokens.length > 0) {
await deleteSubscribers(badTokens);
- debug(`${badTokens.length} APNS tokens deleted`);
+ log(`${badTokens.length} APNS tokens deleted`);
}
});
results.filter(r => r.method === 'gcm')
@@ -75,7 +76,7 @@ export function sendNotification(productionIds, sandboxIds, data) {
});
if (badTokens.length > 0) {
await deleteSubscribers(badTokens);
- debug(`${badTokens.length} GCM tokens deleted`);
+ log(`${badTokens.length} GCM tokens deleted`);
}
});
results.filter(r => r.method === 'mpns')
@@ -85,7 +86,7 @@ export function sendNotification(productionIds, sandboxIds, data) {
});
if (badTokens.length > 0) {
await deleteSubscribers(badTokens);
- debug(`${badTokens.length} MPNS tokens deleted`);
+ log(`${badTokens.length} MPNS tokens deleted`);
}
});
})
@@ -136,7 +137,5 @@ export function buildNotification(user, msg) {
template.tag = `${tag}`;
template.android_channel_id = 'default';
}
- // FIXME: wrong type definition in node-pushnotifications: title and body and not required for silent pushes
- // @ts-ignore
return template;
}
diff --git a/vnext/src/utils/embed.js b/vnext/src/utils/embed.js
index 0c6fbd9c..7db46866 100644
--- a/vnext/src/utils/embed.js
+++ b/vnext/src/utils/embed.js
@@ -28,15 +28,15 @@ function formatText(txt, rules) {
function nextId() { return idCounter++; }
function ft(txt, rules) {
let matches = rules.map(r => { r.re.lastIndex = 0; return [r, r.re.exec(txt)]; })
- .filter(([,m]) => m !== null)
- .sort(([r1,m1],[r2,m2]) => (r1.pr - r2.pr) || (m1.index - m2.index));
+ .filter(([, m]) => m !== null)
+ .sort(([r1, m1], [r2, m2]) => (r1.pr - r2.pr) || (m1.index - m2.index));
if (matches && matches.length > 0) {
let [rule, match] = matches[0];
let subsequentRules = rules.filter(r => r.pr >= rule.pr);
let idStr = `<>(${nextId()})<>`;
let outerStr = txt.substring(0, match.index) + idStr + txt.substring(rule.re.lastIndex);
let innerStr = (rule.brackets)
- ? (() => { let [l ,r ,f] = rule.with; return l + ft((f ? f(match[1]) : match[1]), subsequentRules) + r; })()
+ ? (() => { let [l, r, f] = rule.with; return l + ft((f ? f(match[1]) : match[1]), subsequentRules) + r; })()
: match[0].replace(rule.re, rule.with);
return ft(outerStr, subsequentRules).replace(idStr, innerStr);
}
@@ -59,7 +59,7 @@ function makeNewNode(embedType, aNode, reResult) {
return embedType.makeNode(aNode, reResult, withClasses(document.createElement('div')));
}
-function makeIframe(src, w, h, scrolling='no') {
+function makeIframe(src, w, h, scrolling = 'no') {
let iframe = document.createElement('iframe');
iframe.style.width = w;
iframe.style.height = h;
@@ -120,13 +120,13 @@ function messageReplyReplace(messageId) {
* Given "txt" message in unescaped plaintext with Juick markup, this function
* returns escaped formatted HTML string.
*
- * @param {string} txt
- * @param {string} messageId - current message id
- * @param {boolean} isCode
+ * @param {string} txt text message
+ * @param {string} messageId current message id
+ * @param {boolean} isCode set when message contains *code tag
* @returns {string} formatted message
*/
function juickFormat(txt, messageId, isCode) {
- const urlRe = /(?:\[([^\][]+)\](?:\[([^\]]+)\]|\(((?:[a-z]+:\/\/|www\.|ftp\.)(?:\([-\w+*&@#/%=~|$?!:;,.]*\)|[-\w+*&@#/%=~|$?!:;,.])*(?:\([-\w+*&@#/%=~|$?!:;,.]*\)|[\w+*&@#/%=~|$]))\))|\b(?:[a-z]+:\/\/|www\.|ftp\.)(?:\([-\w+*&@#/%=~|$?!:;,.]*\)|[-\w+*&@#/%=~|$?!:;,.])*(?:\([-\w+*&@#/%=~|$?!:;,.]*\)|[\w+*&@#/%=~|$]))/gi;
+ const urlRe = /(?:\[([^\]\[]+)\](?:\[([^\]]+)\]|\(((?:[a-z]+:\/\/|www\.|ftp\.)(?:\([-\S+*&@#/%=~|$?!:;,.]*\)|[-\S+*&@#/%=~|$?!:;,.])*(?:\([-\S+*&@#/%=~|$?!:;,.]*\)|[\S+*&@#/%=~|$]))\))|\b(?:[a-z]+:\/\/|www\.|ftp\.)(?:\([-\S+*&@#/%=~|$?!:;,.]*\)|[-\S+*&@#/%=~|$?!:;,.])*(?:\([-\S+*&@#/%=~|$?!:;,.]*\)|[\S+*&@#/%=~|$]))/gi;
const bqReplace = m => m.replace(/^(?:>|&gt;)\s?/gmi, '');
return (isCode)
? formatText(txt, [
@@ -139,23 +139,50 @@ function juickFormat(txt, messageId, isCode) {
{ pr: 1, re: urlRe, with: urlReplace },
{ pr: 1, re: /\B(?:#(\d+))?(?:\/(\d+))?\b/g, with: messageReplyReplace(messageId) },
{ pr: 1, re: /\B@([\w-]+)\b/gi, with: '<a href="/$1">@$1</a>' },
- { pr: 2, re: /\B\*([^\n]+?)\*((?=\s)|(?=$)|(?=[!"#$%&'*+,\-./:;<=>?@[\]^_`{|}~()]+))/g, brackets: true, with: ['<b>', '</b>'] },
- { pr: 2, re: /\B\/([^\n]+?)\/((?=\s)|(?=$)|(?=[!"#$%&'*+,\-./:;<=>?@[\]^_`{|}~()]+))/g, brackets: true, with: ['<i>', '</i>'] },
- { pr: 2, re: /\b_([^\n]+?)_((?=\s)|(?=$)|(?=[!"#$%&'*+,\-./:;<=>?@[\]^_`{|}~()]+))/g, brackets: true, with: ['<span class="u">', '</span>'] },
+ { pr: 2, re: /\B\*([^\n]+?)\*((?=\s)|(?=$)|(?=[!\"#$%&'*+,\-./:;<=>?@[\]^_`{|}~()]+))/g, brackets: true, with: ['<b>', '</b>'] },
+ { pr: 2, re: /\B\/([^\n]+?)\/((?=\s)|(?=$)|(?=[!\"#$%&'*+,\-./:;<=>?@[\]^_`{|}~()]+))/g, brackets: true, with: ['<i>', '</i>'] },
+ { pr: 2, re: /\b\_([^\n]+?)\_((?=\s)|(?=$)|(?=[!\"#$%&'*+,\-./:;<=>?@[\]^_`{|}~()]+))/g, brackets: true, with: ['<u>', '</u>'] },
{ pr: 3, re: /\n/g, with: '<br/>' },
]);
}
+/**
+ * @external RegExpExecArray
+ */
+
+/**
+ * @callback MakeNodeCallback
+ * @param { HTMLAnchorElement } aNode a DOM node of the link
+ * @param { RegExpExecArray } reResult Result of RegExp execution
+ * @param { HTMLDivElement} div target DOM element which can be updated by callback function
+ * @returns { HTMLDivElement } updated DOM element
+ */
+
+/**
+ * @typedef { object } LinkFormatData
+ * @property { string } id Format identifier
+ * @property { string } name Format description
+ * @property { RegExp } re Regular expression to match expected hyperlinks
+ * @property { string } className list of CSS classes which
+ * will be added to the target DOM element
+ * @property { MakeNodeCallback } makeNode callback function called when a target link is matched
+ */
+/**
+ * Get supported embeddable formats
+ *
+ * @returns {LinkFormatData[]} list of supported formats
+ */
function getEmbeddableLinkTypes() {
return [
{
name: 'Jpeg and png images',
id: 'embed_jpeg_and_png_images',
className: 'picture compact',
- ctsDefault: false,
- re: /\.(jpe?g|png|svg)(:[a-zA-Z]+)?(?:\?[\w&;?=]*)?$/i,
- makeNode: function(aNode, _reResult, div) {
- div.innerHTML = `<a href="${aNode.href}"><img src="${aNode.href}"></a>`;
+ re: /\.(jpe?g|png|svg)(:[a-zA-Z]+)?(?:\?[\w&;\?=]*)?$/i,
+ makeNode: function(aNode, reResult, div) {
+ // dirty fix for dropbox urls
+ let url = aNode.href.endsWith('dl=0') ? aNode.href.replace('dl=0', 'raw=1') : aNode.href;
+ div.innerHTML = `<a href="${url}"><img src="${url}"></a>`;
return div;
}
},
@@ -163,9 +190,8 @@ function getEmbeddableLinkTypes() {
name: 'Gif images',
id: 'embed_gif_images',
className: 'picture compact',
- ctsDefault: true,
- re: /\.gif(:[a-zA-Z]+)?(?:\?[\w&;?=]*)?$/i,
- makeNode: function(aNode, _reResult, div) {
+ re: /\.gif(:[a-zA-Z]+)?(?:\?[\w&;\?=]*)?$/i,
+ makeNode: function(aNode, reResult, div) {
div.innerHTML = `<a href="${aNode.href}"><img src="${aNode.href}"></a>`;
return div;
}
@@ -174,9 +200,8 @@ function getEmbeddableLinkTypes() {
name: 'Video (webm, mp4, ogv)',
id: 'embed_webm_and_mp4_videos',
className: 'video compact',
- ctsDefault: false,
- re: /\.(webm|mp4|m4v|ogv)(?:\?[\w&;?=]*)?$/i,
- makeNode: function(aNode, _reResult, div) {
+ re: /\.(webm|mp4|m4v|ogv)(?:\?[\w&;\?=]*)?$/i,
+ makeNode: function(aNode, reResult, div) {
div.innerHTML = `<video src="${aNode.href}" title="${aNode.href}" controls></video>`;
return div;
}
@@ -185,9 +210,8 @@ function getEmbeddableLinkTypes() {
name: 'Audio (mp3, ogg, weba, opus, m4a, oga, wav)',
id: 'embed_sound_files',
className: 'audio singleColumn',
- ctsDefault: false,
- re: /\.(mp3|ogg|weba|opus|m4a|oga|wav)(?:\?[\w&;?=]*)?$/i,
- makeNode: function(aNode, _reResult, div) {
+ re: /\.(mp3|ogg|weba|opus|m4a|oga|wav)(?:\?[\w&;\?=]*)?$/i,
+ makeNode: function(aNode, reResult, div) {
div.innerHTML = `<audio src="${aNode.href}" title="${aNode.href}" controls></audio>`;
return div;
}
@@ -196,18 +220,17 @@ function getEmbeddableLinkTypes() {
name: 'YouTube videos (and playlists)',
id: 'embed_youtube_videos',
className: 'youtube resizableV singleColumn',
- ctsDefault: false,
- re: /^(?:https?:)?\/\/(?:www\.|m\.|gaming\.)?(?:youtu(?:(?:\.be\/|be\.com\/(?:v|embed)\/)([-\w]+)|be\.com\/watch)((?:(?:\?|&(?:amp;)?)(?:\w+=[-.\w]*[-\w]))*)|youtube\.com\/playlist\?list=([-\w]*)(&(amp;)?[-\w?=]*)?)/i,
- makeNode: function(_aNode, reResult, div) {
- let [, v, args, plist] = reResult;
+ re: /^(?:https?:)?\/\/(?:www\.|m\.|gaming\.)?(?:youtu(?:(?:\.be\/|be\.com\/(?:v|embed)\/)([-\w]+)|be\.com\/watch)((?:(?:\?|&(?:amp;)?)(?:\w+=[-\.\w]*[-\w]))*)|youtube\.com\/playlist\?list=([-\w]*)(&(amp;)?[-\w\?=]*)?)/i,
+ makeNode: function(aNode, reResult, div) {
+ let [url, v, args, plist] = reResult;
let iframeUrl;
if (plist) {
iframeUrl = '//www.youtube-nocookie.com/embed/videoseries?list=' + plist;
} else {
let pp = {}; args.replace(/^\?/, '')
- .split('&')
- .map(s => s.split('='))
- .forEach(z => pp[z[0]] = z[1]);
+ .split('&')
+ .map(s => s.split('='))
+ .forEach(z => pp[z[0]] = z[1]);
let embedArgs = {
rel: '0',
enablejsapi: '1',
@@ -215,16 +238,16 @@ function getEmbeddableLinkTypes() {
};
if (pp.t) {
const tre = /^(?:(\d+)|(?:(\d+)h)?(?:(\d+)m)?(\d+)s|(?:(\d+)h)?(\d+)m|(\d+)h)$/i;
- let [, t, h, m, s, h1, m1, h2] = tre.exec(pp.t) || [];
- embedArgs['start'] = (+t) || ((+(h || h1 || h2 || 0))*60*60 + (+(m || m1 || 0))*60 + (+(s || 0)));
+ let [, t, h, m, s, h1, m1, h2] = tre.exec(pp.t);
+ embedArgs['start'] = (+t) || ((+(h || h1 || h2 || 0)) * 60 * 60 + (+(m || m1 || 0)) * 60 + (+(s || 0)));
}
if (pp.list) {
embedArgs['list'] = pp.list;
}
v = v || pp.v;
let argsStr = Object.keys(embedArgs)
- .map(k => `${k}=${embedArgs[k]}`)
- .join('&');
+ .map(k => `${k}=${embedArgs[k]}`)
+ .join('&');
iframeUrl = `//www.youtube-nocookie.com/embed/${v}?${argsStr}`;
}
let iframe = makeIframe(iframeUrl, '100%', '360px');
@@ -236,7 +259,6 @@ function getEmbeddableLinkTypes() {
name: 'Vimeo videos',
id: 'embed_vimeo_videos',
className: 'vimeo resizableV',
- ctsDefault: false,
re: /^(?:https?:)?\/\/(?:www\.)?(?:player\.)?vimeo\.com\/(?:video\/|album\/[\d]+\/video\/)?([\d]+)/i,
makeNode: function(aNode, reResult, div) {
let iframe = makeIframe('//player.vimeo.com/video/' + reResult[1], '100%', '360px');
@@ -248,25 +270,25 @@ function getEmbeddableLinkTypes() {
name: 'Twitter',
id: 'embed_twitter_status',
className: 'twi compact',
- ctsDefault: false,
re: /^(?:https?:)?\/\/(?:www\.)?(?:mobile\.)?twitter\.com\/([\w-]+)\/status(?:es)?\/([\d]+)/i,
makeNode: function(aNode, reResult, div) {
- let [url] = reResult;
- url = url.replace('mobile.','');
-
- div.innerHTML = `<blockquote class="twitter-tweet" data-lang="en"><a href="${url}"></a></blockquote>`;
-
- if (window['twttr']) {
- // https://developer.twitter.com/en/docs/twitter-for-websites/javascript-api/guides/scripting-loading-and-initialization
- window['twttr'].widgets.load(div);
- } else {
- // innerHTML cannot insert scripts, so...
- let script = document.createElement('script');
- script.src = 'https://platform.twitter.com/widgets.js';
- script.charset = 'utf-8';
- div.appendChild(script);
- }
-
+ fetch('https://beta.juick.com/api/oembed?url=' + reResult[0])
+ .then(response => response.json())
+ .then(json => {
+ div.innerHTML = json.html;
+ });
+ return div;
+ }
+ },
+ {
+ name: 'Instagram media',
+ id: 'embed_instagram_images',
+ className: 'picture compact',
+ re: /https?:\/\/www\.?instagram\.com(\/p\/\w+)\/?/i,
+ makeNode: function(aNode, reResult, div) {
+ let [url, postId] = reResult;
+ let mediaUrl = `https://instagr.am${postId}/media`;
+ div.innerHTML = `<a href="${aNode.href}"><img src="${mediaUrl}"></a>`;
return div;
}
},
@@ -276,7 +298,7 @@ function getEmbeddableLinkTypes() {
className: 'tg compact',
re: /https?:\/\/t\.me\/(\S+)/i,
makeNode: function(aNode, reResult, div) {
- let [, post] = reResult;
+ let [url, post] = reResult;
// innerHTML cannot insert scripts, so...
let script = document.createElement('script');
script.src = 'https://telegram.org/js/telegram-widget.js?18';
@@ -287,30 +309,39 @@ function getEmbeddableLinkTypes() {
div.appendChild(script);
return div;
}
- }
+ },
];
}
-function embedLink(aNode, linkTypes, container, afterNode) {
+/**
+ * Embed a link
+ *
+ * @param { HTMLAnchorElement } aNode a DOM node of the link
+ * @param { LinkFormatData[] } linkTypes supported link types
+ * @param { HTMLElement } container a target DOM element with the link content
+ * @param { boolean } afterNode where to insert new DOM node
+ * @returns { boolean } `true` when some link was embedded
+ */
+function embedLink(aNode, linkTypes, container, afterNode = false) {
let anyEmbed = false;
- let linkId = (aNode.href.replace(/^https?:/i, '').replace(/'/gi,''));
+ let linkId = (aNode.href.replace(/^https?:/i, '').replace(/\'/gi, ''));
let sameEmbed = container.querySelector(`*[data-linkid='${linkId}']`); // do not embed the same thing twice
- if (sameEmbed === null) {
- anyEmbed = [].some.call(linkTypes, function(linkType) {
- let reResult = linkType.re.exec(aNode.href);
- if (reResult) {
- if (linkType.match && (linkType.match(aNode, reResult) === false)) { return false; }
- let newNode = makeNewNode(linkType, aNode, reResult);
- if (!newNode) { return false; }
- newNode.setAttribute('data-linkid', linkId);
- if (afterNode) {
- insertAfter(newNode, afterNode);
- } else {
- container.appendChild(newNode);
- }
- aNode.classList.add('embedLink');
- return true;
+ if (!sameEmbed) {
+ anyEmbed = linkTypes.some((linkType) => {
+ let reResult = linkType.re.exec(aNode.href);
+ if (reResult) {
+ if (linkType.match && (linkType.match(aNode, reResult) === false)) { return false; }
+ let newNode = makeNewNode(linkType, aNode, reResult);
+ if (!newNode) { return false; }
+ newNode.setAttribute('data-linkid', linkId);
+ if (afterNode) {
+ insertAfter(newNode, afterNode);
+ } else {
+ container.appendChild(newNode);
}
+ aNode.classList.add('embedLink');
+ return true;
+ }
});
}
return anyEmbed;
@@ -337,6 +368,7 @@ function embedLinks(aNodes, container) {
* @param {string} allLinksSelector
*/
export function embedLinksToX(x, beforeNodeSelector, allLinksSelector) {
+ let isCtsPost = false;
let allLinks = x.querySelectorAll(allLinksSelector);
let existingContainer = x.querySelector('div.embedContainer');
@@ -355,31 +387,15 @@ export function embedLinksToX(x, beforeNodeSelector, allLinksSelector) {
}
/**
- * Embed links to articles
- */
-function embedLinksToArticles() {
- let beforeNodeSelector = 'nav.l';
- let allLinksSelector = 'p:not(.ir) a, pre a';
- Array.from(document.querySelectorAll('#content article')).forEach(article => {
- embedLinksToX(article, beforeNodeSelector, allLinksSelector);
- });
-}
-
-/**
- * Embed links to post
+ * Embed all the links in all messages/replies on the page.
*/
-function embedLinksToPost() {
+export function embedAll() {
let beforeNodeSelector = '.msg-txt + *';
let allLinksSelector = '.msg-txt a';
Array.from(document.querySelectorAll('#content .msg-cont')).forEach(msg => {
embedLinksToX(msg, beforeNodeSelector, allLinksSelector);
});
}
-
-/**
- * @external NodeListOf
- */
-
/**
* Embed URLs to container
*
@@ -390,15 +406,5 @@ export function embedUrls(urls, embedContainer) {
embedLinks(urls, embedContainer);
}
-/**
- * Embed all the links in all messages/replies on the page.
- */
-export function embedAll() {
- if (document.querySelector('#content article[data-mid]')) {
- embedLinksToArticles();
- } else {
- embedLinksToPost();
- }
-}
export const format = juickFormat;