Skip to content

Instantly share code, notes, and snippets.

@liddack
Last active November 1, 2020 10:31
Show Gist options
  • Save liddack/22a6f04f4813b53f55d832bd0faf85f3 to your computer and use it in GitHub Desktop.
Save liddack/22a6f04f4813b53f55d832bd0faf85f3 to your computer and use it in GitHub Desktop.
Crowdin Source Update String Notifier for Discord Project

Crowdin Source Update String Notifier for Discord

Image of a notification sent by the webhook

Limitations

  • It can send notifications to only one webhook
  • Data isn't persisted, which means that everytime this script (re)starts the last Source String Update event will be notified again
  • Notifications will not have a direct link to the specific updated strings due to Crowdin Activity API limitations
// Loading logger module
const log = require('fancy-log')
log.info('Loading...')
// Loading the rest of the modules
const Webhook = require("./webhook"),
express = require('express'),
TurndownService = require('turndown'),
turndownService = new TurndownService(),
events = require('events'),
// This URL returns JSON data containing only 'Source String Update' events
CROWDIN_ACTIVITY_URL = "https://crowdin.com/project_actions/activity_stream?date_from=&date_to=&user_id=12799052&project_id=121045&language_id=0&type=3&translation_id=0&after_build=0&before_build=0&request=5",
CROWDIN_URL = 'https://crowdin.com/',
snekfetch = require('snekfetch'),
// Replace the placeholder below w/ your actual webhook URL
Hook = new Webhook("YOUR_WEBHOOK_URL");
// Loading event emitter and catcher
var mediaEmitter = new events.EventEmitter(),
// Stores timestamp for the latest source string update event
previousTimestamp = 0;
// Add a rule to turn 'Discord' into a Markdown link to Discord's Crowdin profile
turndownService.addRule('profile-link', {
filter: (node, options) => {
return (
options.linkStyle === 'inlined' &&
node.nodeName === 'A' &&
node.getAttribute('href')
)
},
replacement: (content, node, options) => {
return '[' + content + ']('+ CROWDIN_URL + node.getAttribute('href') + ')'
}
})
// Add a rule to bold all text between 'span' tags
// (aka. "Discord", "Discord Marketing" and "Discord API" project files)
turndownService.addRule('profile-link', {
filter: (node, options) => {
return (
node.nodeName === 'SPAN'
)
},
replacement: (content, node, options) => {
return `**${content}**`
}
})
// Attempts to connect to Crowdin activity API
checkActivity = () => {
log.info("Checking activity... ")
snekfetch.get(CROWDIN_ACTIVITY_URL)
.then(function (res) {
mediaEmitter.emit('CONNECTED', res)
})
.catch(function (err) {
mediaEmitter.emit('ERROR', err)
})
}
// Emits an event when connection to Crowdin API was succesfull
mediaEmitter.on('CONNECTED', function (res) {
// Sends retrieved data to analyzeActivity() function
analyzeActivity(res.body)
})
// Emits an event when an connection attempt to Crowdin fails
mediaEmitter.on('ERROR', function (code) {
log.error('ERRROR: ' + code)
})
// App loaded successfully
log.info('App ready.')
// Do the first call to checkActivity()
checkActivity()
// Creates a loop to check activity every 2.5 minutes
// (1000 miliseconds * 60 seconds * 2.5 minutes)
global.intloop = setInterval(checkActivity, 1000 * 60 * 2.5)
// Gets activity JSON data, checks if there's a newer source string update
// and, if so, send a message to the specified webhook
analyzeActivity = (data) => {
let tempv = previousTimestamp;
for (let i = 0; i < data.activity.length; i++) {
if (data.activity[i].type == "update_project_files") {
let timestamp = data.activity[i].timestamp;
if (timestamp > previousTimestamp) {
previousTimestamp = timestamp;
let message = parseMessage(data.activity[i].message)
log.info('NEW SOURCE STRING UPDATE at ' + new Date(data.activity[i].timestamp * 1000) + ':\n' + message)
Hook.send('Crowdin Webhook', message, 'Source Strings Updated!', '#85ae52', timestamp)
}
}
}
if (tempv == previousTimestamp) log.info('Nothing yet.')
}
// Messages within the JSON data are written in HTML. This
// function converts it into a Discord-friendy Markdown text.
parseMessage = (message) => {
return turndownService.turndown(message) +
'\nhttps://crowdin.com/project/discord'
}
// This file is basically a fork of https://github.com/JoeBanks13/webhook-discord
// tweaked to fullfill the specific needs of this app.
const snekfetch = require("snekfetch");
const endpoint = "https://discordapp.com/api/v6/webhooks";
class Webhook {
constructor(url) {
this.url = url;
this.id = '';
this.token = '';
this.meta = {};
snekfetch.get(this.url)
.then((res) => {
let parsed = JSON.parse(res.text);
Object.assign(this.meta, parsed);
this.id = parsed['id'];
this.token = parsed['token'];
})
.catch((err) => {
throw err;
})
}
sendPayload(payload) {
return new Promise((resolve, reject) => {
snekfetch.post(this.endpoint)
.send(payload)
.then(() => {
resolve()
})
.catch((err) => {
reject(err.text)
})
});
}
get endpoint() {
return `${endpoint}/${this.id}/${this.token}/slack`
}
send(name, message, title, color, timestamp){
let payload;
payload = {
"username": name,
"text": "",
"attachments": [{
"color": color,
"fields": [{
"title": title,
"value": message
}],
"ts": timestamp
}]
}
return this.sendPayload(payload);
}
}
module.exports = Webhook;
@phillf
Copy link

phillf commented Feb 24, 2019

How did you get CROWDIN_ACTIVITY_URL?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment