mirror of
https://github.com/Wessel/yorushika.git
synced 2026-07-26 10:12:24 +02:00
Add webhook, avatar and guild cmd + some small fixes
This commit is contained in:
@@ -37,8 +37,14 @@ discord :
|
||||
type : 1 # 0 - Playing / 1 - Streaming / 2 - Listening / 3 - Watching
|
||||
status: 'online' # online / idle / dnd / invisible
|
||||
|
||||
webhook:
|
||||
token : '<--snip-->'
|
||||
channel: '<--snip-->'
|
||||
events :
|
||||
ready : true
|
||||
shardready : true
|
||||
shardresume : true
|
||||
sharddisconnect: true
|
||||
disconnect : true
|
||||
messagecreate: true
|
||||
messagedelete: true
|
||||
@@ -52,6 +58,7 @@ discord :
|
||||
# Category : INFORMATION
|
||||
stats : true
|
||||
guild : true
|
||||
avatar : true
|
||||
commands : true
|
||||
# Category : IMAGE
|
||||
cat : true
|
||||
|
||||
@@ -15,6 +15,7 @@ util :
|
||||
info :
|
||||
stats : 0x02C2F33 # Dark but not black
|
||||
guild : 0x7289DA # Blurple
|
||||
avatar : 0x02C2F33 # Dark but not black
|
||||
commands: 0x02C2F33 # Dark but not black
|
||||
|
||||
image :
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
cooldown : <:sad:500719642403405824> # On cooldown
|
||||
owner_only: <:sad:500719642403405824> # Only owners
|
||||
guild_only: <:sad:500719642403405824> # Only guilds
|
||||
cooldown : <:stopwatch:542477522147868672> # On cooldown
|
||||
owner_only: <:exclamation:542368036255039509> # Only owners
|
||||
guild_only: <:exclamation:542368036255039509> # Only guilds
|
||||
|
||||
core :
|
||||
locale:
|
||||
@@ -14,6 +14,13 @@ core :
|
||||
7 : <:sad:500719642403405824> # Invalid locale
|
||||
8 : <:sad:500719642403405824> # Missing permissions (guild)
|
||||
|
||||
logs :
|
||||
ready : '<:spacewump:542723940997529641>'
|
||||
shard :
|
||||
ready : '<:create_webhook:542716503674454016>'
|
||||
resume : '<:update_webhook:542716504089690124>'
|
||||
disconnect: '<:remove_webhook:542716503624253461>'
|
||||
|
||||
util :
|
||||
ping :
|
||||
0 : <:bored:500719642164199425> # Pinging
|
||||
@@ -44,6 +51,7 @@ info:
|
||||
13 : <:dnd:362670805676720128> # Total DnD members
|
||||
14 : <:offline:362670805441708032> # Total offline members
|
||||
15 : <:exclamation:542368036255039509> # Invalid query
|
||||
avatar : <:blueberry:506201160382939141> # User's avatar
|
||||
commands :
|
||||
0 : <:blueberry:506201160382939141> # Guild prefix
|
||||
1 : <:spirit:332131321759399937> # More info
|
||||
|
||||
6
src/assets/i18n/en_us/logs_simple.yml
Normal file
6
src/assets/i18n/en_us/logs_simple.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
connection:
|
||||
ready : '`[$[date:now] / $[process:hash]]` $[emoji#0] Master client on Wump (`v$[wump:version]`) connected'
|
||||
shard :
|
||||
ready : '`[$[date:now] / $[process:hash]]` $[emoji#0] Shard `#$[shard:id]` connected to Wump (`v$[wump:version]`)'
|
||||
resume : '`[$[date:now] / $[process:hash]]` $[emoji#0] Shard `#$[shard:id]` resumed to Wump (`v$[wump:version]`)'
|
||||
disconnect: '`[$[date:now] / $[process:hash]]` $[emoji#0] Shard `#$[shard:id]` disconnected from Wump (`v$[wump:version]`)'
|
||||
@@ -62,6 +62,7 @@ util :
|
||||
info :
|
||||
stats :
|
||||
fetching: '$[emoji#0] Fetching statistics, this may take some time...'
|
||||
avatar : '$[emoji#0] Here''s **$[user:full]** avatar'
|
||||
commands:
|
||||
multi :
|
||||
- '$[emoji#0] The prefix for **$[guild:name]** is `$[guild:prefix]`'
|
||||
|
||||
6
src/assets/i18n/nl/logs_simple.yml
Normal file
6
src/assets/i18n/nl/logs_simple.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
connection:
|
||||
ready : '`[$[date:now] / $[process:hash]]` $[emoji#0] Meester client op Wump (`v$[wump:version]`) is verbonden'
|
||||
shard :
|
||||
ready : '`[$[date:now] / $[process:hash]]` $[emoji#0] Shard `#$[shard:id]` is verbonden naar Wump (`v$[wump:version]`)'
|
||||
resume : '`[$[date:now] / $[process:hash]]` $[emoji#0] Shard `#$[shard:id]` is hervat naar Wump (`v$[wump:version]`)'
|
||||
disconnect: '`[$[date:now] / $[process:hash]]` $[emoji#0] Shard `#$[shard:id]` is losgekoppeld van Wump (`v$[wump:version]`)'
|
||||
@@ -61,6 +61,7 @@ util :
|
||||
info :
|
||||
stats :
|
||||
fetching: '$[emoji#0] Statistieken aan het ophalen, dit kan enige tijd duren...'
|
||||
avatar : '$[emoji#0] Hier is **$[user:full]** avatar'
|
||||
commands:
|
||||
multi :
|
||||
- '$[emoji#0] Het voorvoegsel voor **$[guild:name]** is `$[guild:prefix]`'
|
||||
|
||||
BIN
src/assets/img/emoji/Audit logs/create_webhook.png
Normal file
BIN
src/assets/img/emoji/Audit logs/create_webhook.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.9 KiB |
BIN
src/assets/img/emoji/Audit logs/remove_webhook.png
Normal file
BIN
src/assets/img/emoji/Audit logs/remove_webhook.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
BIN
src/assets/img/emoji/Audit logs/update_webhook.png
Normal file
BIN
src/assets/img/emoji/Audit logs/update_webhook.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.4 KiB |
BIN
src/assets/img/emoji/ok_hand.png
Normal file
BIN
src/assets/img/emoji/ok_hand.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
src/assets/img/logo/webhook.png
Normal file
BIN
src/assets/img/logo/webhook.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -2,7 +2,7 @@ const { DiscordCommand } = require('../../../core');
|
||||
|
||||
const w = require('wumpfetch');
|
||||
|
||||
module.exports = class Cat extends DiscordCommand {
|
||||
module.exports = class Fox extends DiscordCommand {
|
||||
constructor(bot) {
|
||||
super(bot, {
|
||||
name : 'fox',
|
||||
|
||||
@@ -2,7 +2,7 @@ const { DiscordCommand } = require('../../../core');
|
||||
|
||||
const w = require('wumpfetch');
|
||||
|
||||
module.exports = class Cat extends DiscordCommand {
|
||||
module.exports = class Lizard extends DiscordCommand {
|
||||
constructor(bot) {
|
||||
super(bot, {
|
||||
name : 'lizard',
|
||||
|
||||
@@ -2,7 +2,7 @@ const { DiscordCommand } = require('../../../core');
|
||||
|
||||
const w = require('wumpfetch');
|
||||
|
||||
module.exports = class Cat extends DiscordCommand {
|
||||
module.exports = class Penguin extends DiscordCommand {
|
||||
constructor(bot) {
|
||||
super(bot, {
|
||||
name : 'penguin',
|
||||
|
||||
80
src/commands/Discord/Information/avatar.js
Normal file
80
src/commands/Discord/Information/avatar.js
Normal file
@@ -0,0 +1,80 @@
|
||||
const { DiscordCommand } = require('../../../core');
|
||||
|
||||
const w = require('wumpfetch');
|
||||
const sizeOf = require('../../../util/sizeOf');
|
||||
const { extname } = require('path');
|
||||
|
||||
let s;
|
||||
try { s = require('sharp'); }
|
||||
catch (ex) { throw new Error('Sharp not found, not loading this command'); }
|
||||
|
||||
module.exports = class Avatar extends DiscordCommand {
|
||||
constructor(bot) {
|
||||
super(bot, {
|
||||
name : 'avatar',
|
||||
syntax : 'avatar [...user:string] [--large] [--(nearest|linear)]',
|
||||
aliases : [ 'avt', 'pfp' ],
|
||||
argument : [ '[...user:string]', '[--large]', '[--(nearest|linear)]' ],
|
||||
description: 'Get a user\'s avatar',
|
||||
|
||||
hidden : false,
|
||||
enabled : true,
|
||||
cooldown : 5000,
|
||||
category : 'Information',
|
||||
ownerOnly : false,
|
||||
guildOnly : false,
|
||||
permissions: [ 'embedLinks' ]
|
||||
});
|
||||
|
||||
this.regex = [/--large/i, /--(nearest|linear)/i, /--greyscale/i];
|
||||
}
|
||||
|
||||
async execute(msg, args) {
|
||||
const resize =
|
||||
this.regex[1].test(args) && this.regex[1].exec(args)[0].toLowerCase() === '--nearest' ? 'NEAREST' :
|
||||
this.regex[1].test(args) && this.regex[1].exec(args)[0].toLowerCase() === '--linear' ? 'LINEAR_A' : 'LINEAR_B';
|
||||
|
||||
const oldArgs = args;
|
||||
args = args.join(' ').trim().replace(this.regex[0], '').replace(this.regex[1], '').replace(this.regex[2], '').split(' ').filter((v) => { return v !== ''; });
|
||||
let user = this.bot.REST.getUser(args[0] ? args.join(' ') : msg.author.id);
|
||||
if (!user) user = this.bot.users.get(msg.author.id);
|
||||
|
||||
let img, dim;
|
||||
const avatarURL = (user.avatar ? user.avatarURL : user.defaultAvatarURL).split('?')[0];
|
||||
|
||||
if (this.regex[0].test(oldArgs.join(' '))) {
|
||||
img = await w(avatarURL).send();
|
||||
img = img.body;
|
||||
|
||||
dim = sizeOf(img);
|
||||
img = await s(img)
|
||||
.resize(dim.width * 2, dim.height * 2, { kernel: resize === 'LINEAR_B' ? s.kernel.linearB : resize === 'LINEAR_A' ? s.kernel.linearA : resize === 'NEAREST' ? s.kernel.nearest : s.kernel.linearA }) // { fit: 'inside' }
|
||||
.toBuffer();
|
||||
if (this.regex[2].exec(args)) img = await s(img).greyscale().toBuffer();
|
||||
} else {
|
||||
img = await w(avatarURL).send();
|
||||
img = img.body;
|
||||
}
|
||||
|
||||
if (this.regex[2].test(oldArgs)) img = await s(img).greyscale().toBuffer();
|
||||
|
||||
msg.channel.createMessage({
|
||||
embed: {
|
||||
image: { url: `attachment://thumb${extname(avatarURL)}` },
|
||||
color: this.bot.col['info']['avatar'],
|
||||
description: `${this.localize(msg.author.locale['info']['avatar'], { user: user })} (\`${this.regex[0].test(oldArgs.join(' ')) ? 'LARGE' : 'DEFAULT'} ${resize} ${this.regex[2].exec(oldArgs) ? 'GREYSCALE' : ''}\`)`
|
||||
}
|
||||
}, {
|
||||
file: img,
|
||||
name: `thumb${extname(avatarURL)}`
|
||||
});
|
||||
}
|
||||
|
||||
localize(msg, extData) {
|
||||
if (!msg) return '';
|
||||
|
||||
return msg
|
||||
.replace(/\$\[user:full]/g, `${extData.user.username}'${!extData.user.username.toLowerCase().endsWith('s') ? 's' : ''}`)
|
||||
.replace(/\$\[emoji#0]/g, this.bot.emote('info', 'avatar'));
|
||||
}
|
||||
};
|
||||
@@ -2,7 +2,7 @@ const { DiscordCommand } = require('../../../core');
|
||||
|
||||
const moment = require('moment'); require('../../../util/moment/diff.js');
|
||||
|
||||
module.exports = class name extends DiscordCommand {
|
||||
module.exports = class Guild extends DiscordCommand {
|
||||
constructor(bot) {
|
||||
super(bot, {
|
||||
name : 'guild',
|
||||
|
||||
@@ -39,11 +39,12 @@ module.exports = class WumpDiscord extends Eris {
|
||||
this.schema = Schema;
|
||||
this.Collection = Collection;
|
||||
|
||||
this.ua = options.ua ? options.ua : undefined;
|
||||
this.pkg = options.pkg ? options.pkg : undefined;
|
||||
this.col = options.colors ? options.colors : undefined;
|
||||
this.ico = options.emojis ? options.emojis : undefined;
|
||||
this.conf = options.config ? options.config : undefined;
|
||||
this.ua = options.ua ? options.ua : undefined;
|
||||
this.pkg = options.pkg ? options.pkg : undefined;
|
||||
this.col = options.colors ? options.colors : undefined;
|
||||
this.ico = options.emojis ? options.emojis : undefined;
|
||||
this.conf = options.config ? options.config : undefined;
|
||||
this.start = Date.now();
|
||||
|
||||
this.requestHandler.userAgent = this.ua ? this.ua : `Wump (https://github.com/PassTheWessel/wump, ${this.pkg.version})`;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const fetch = require('wumpfetch');
|
||||
const w = require('wumpfetch');
|
||||
const { randomBytes } = require('crypto');
|
||||
|
||||
module.exports = class DiscordWebhook {
|
||||
@@ -11,14 +11,16 @@ module.exports = class DiscordWebhook {
|
||||
(async() => {
|
||||
try {
|
||||
if (!this.extData.token || !this.extData.channel) return this.hook = undefined;
|
||||
this.hook = await fetch(`${this.baseURL}/${this.extData.channel}/${this.extData.token}`, { method: 'GET' }).send();
|
||||
this.payloadURL = `${this.baseURL}/${this.extData.channel}/${this.extData.token}`;
|
||||
this.hook = await w(this.payloadURL).send();
|
||||
if (this.hook.statusCode !== 200) throw new SyntaxError( 'Failed to fetch webhook' );
|
||||
} catch (ex) { throw ex; }
|
||||
})();
|
||||
}
|
||||
|
||||
async send(data = {}) {
|
||||
async send(payload = {}) {
|
||||
if (!this.hook) return true;
|
||||
return await fetch(`${this.baseURL}/${this.extData.channel}/${this.extData.token}`, { method: 'POST', data: data }).send();
|
||||
const req = await w(this.payloadURL, { method: 'POST', data: payload, sendDataAs: 'json' }).send();
|
||||
return req;
|
||||
}
|
||||
};
|
||||
@@ -105,6 +105,9 @@ module.exports = class CommandRegistry {
|
||||
cmd = this.bot.cmds.filter((v) => v.extData.name.toLowerCase() === cmd || v.extData.aliases.includes(cmd));
|
||||
|
||||
if (cmd.length > 0) {
|
||||
if (!this.bot.cache.has('commands_ran')) this.bot.cache.set('commands_ran', 1);
|
||||
else this.bot.cache.set('commands_ran', this.bot.cache.get('commands_ran') + 1);
|
||||
|
||||
user['entryAge'] = Date.now();
|
||||
guild['entryAge'] = Date.now();
|
||||
cmd = cmd[0];
|
||||
@@ -141,7 +144,7 @@ module.exports = class CommandRegistry {
|
||||
return msg.channel.createMessage({
|
||||
embed: {
|
||||
color : this.bot.col['cooldown'],
|
||||
description: this.localize(msg.author.locale['cooldown'], { left: moment.duration(timeLeft).format('YYYY[y] M[M] DD[d] H[h] m[m] s[S] SS[ms]'), author: msg.author })
|
||||
description: this.localize(msg.author.locale['cooldown'], { left: moment.duration(timeLeft).format(`YYYY[y] M[M] DD[d] H[h] m[m] ${timeLeft > 1000 ? 's[.]SS[s]' : 's[s] SS[ms]'}`), author: msg.author })
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
const { DiscordEvent } = require('../../core');
|
||||
|
||||
const moment = require('moment');
|
||||
const { safeLoad } = require('js-yaml');
|
||||
const { green, cyan } = require('../../util/colors');
|
||||
const { join: pJoin } = require('path');
|
||||
const { readFileSync } = require('fs');
|
||||
|
||||
module.exports = class Ready extends DiscordEvent {
|
||||
constructor(bot) {
|
||||
super(bot, { name: 'ready' });
|
||||
|
||||
this.strings = safeLoad(readFileSync(pJoin(__dirname, '..', '..', 'assets', 'i18n', bot.conf['discord']['locale'], 'logs_simple.yml'), { encoding: 'utf8' }));
|
||||
}
|
||||
|
||||
emit() {
|
||||
@@ -19,14 +25,26 @@ module.exports = class Ready extends DiscordEvent {
|
||||
});
|
||||
}
|
||||
|
||||
this.bot.hook.send({ content: this.localize(this.strings['connection']['ready']) });
|
||||
|
||||
// Cache flushing if entry isn't used within `flushTime` minutes
|
||||
const flushTime = 1800000;
|
||||
setInterval(() => {
|
||||
const uCache = this.bot.cache.get('users') || [];
|
||||
|
||||
const gCache = this.bot.cache.get('guilds') || [];
|
||||
|
||||
uCache.forEach((v, _) => (new Date(v.entryAge) <= new Date(new Date() - flushTime)) ? uCache.splice(_, 1) : undefined);
|
||||
gCache.forEach((v, _) => (new Date(v.entryAge) <= new Date(new Date() - flushTime)) ? gCache.splice(_, 1) : undefined);
|
||||
}, flushTime);
|
||||
}
|
||||
};
|
||||
|
||||
localize(msg) {
|
||||
if (!msg) return '';
|
||||
return msg
|
||||
.replace(/\$\[wump:version]/g, `${this.bot.pkg.version} ${this.bot.conf['nightly'] ? 'NIGHTLY' : 'DISTRIBUTIONs'}`)
|
||||
.replace(/\$\[process:hash]/g, process.hash)
|
||||
.replace(/\$\[date:now]/g, moment(Date.now()).format('HH[:]mm[:]ss'))
|
||||
.replace(/\$\[emoji#0]/g, this.bot.emote('logs', 'ready'));
|
||||
}
|
||||
};
|
||||
// YYYY[y] M[M] DD[d] H[h] m[m] ${startTime > 1000 ? 's[.]SS[s]' : 's[s] SS[ms]'}
|
||||
28
src/events/Discord/shardDisconnect.js
Normal file
28
src/events/Discord/shardDisconnect.js
Normal file
@@ -0,0 +1,28 @@
|
||||
const { DiscordEvent } = require('../../core');
|
||||
|
||||
const moment = require('moment');
|
||||
const { safeLoad } = require('js-yaml');
|
||||
const { join: pJoin } = require('path');
|
||||
const { readFileSync } = require('fs');
|
||||
|
||||
module.exports = class shardDisconnect extends DiscordEvent {
|
||||
constructor(bot) {
|
||||
super(bot, { name: 'shardDisconnect' });
|
||||
|
||||
this.strings = safeLoad(readFileSync(pJoin(__dirname, '..', '..', 'assets', 'i18n', bot.conf['discord']['locale'], 'logs_simple.yml'), { encoding: 'utf8' }));
|
||||
}
|
||||
|
||||
emit(err, id) {
|
||||
this.bot.hook.send({ content: this.localize(this.strings['connection']['shard']['disconnect'], { shard: id }) });
|
||||
}
|
||||
|
||||
localize(msg, extData) {
|
||||
if (!msg) return '';
|
||||
return msg
|
||||
.replace(/\$\[shard:id]/g, extData.shard)
|
||||
.replace(/\$\[wump:version]/g, `${this.bot.pkg.version} ${this.bot.conf['nightly'] ? 'NIGHTLY' : 'DISTRIBUTION'}`)
|
||||
.replace(/\$\[process:hash]/g, process.hash)
|
||||
.replace(/\$\[date:now]/g, moment(Date.now()).format('HH[:]mm[:]ss'))
|
||||
.replace(/\$\[emoji#0]/g, this.bot.emote('logs', 'shard', 'disconnect'));
|
||||
}
|
||||
};
|
||||
28
src/events/Discord/shardReady.js
Normal file
28
src/events/Discord/shardReady.js
Normal file
@@ -0,0 +1,28 @@
|
||||
const { DiscordEvent } = require('../../core');
|
||||
|
||||
const moment = require('moment');
|
||||
const { safeLoad } = require('js-yaml');
|
||||
const { join: pJoin } = require('path');
|
||||
const { readFileSync } = require('fs');
|
||||
|
||||
module.exports = class ShardReady extends DiscordEvent {
|
||||
constructor(bot) {
|
||||
super(bot, { name: 'shardReady' });
|
||||
|
||||
this.strings = safeLoad(readFileSync(pJoin(__dirname, '..', '..', 'assets', 'i18n', bot.conf['discord']['locale'], 'logs_simple.yml'), { encoding: 'utf8' }));
|
||||
}
|
||||
|
||||
emit(id) {
|
||||
this.bot.hook.send({ content: this.localize(this.strings['connection']['shard']['ready'], { shard: id }) });
|
||||
}
|
||||
|
||||
localize(msg, extData) {
|
||||
if (!msg) return '';
|
||||
return msg
|
||||
.replace(/\$\[shard:id]/g, extData.shard)
|
||||
.replace(/\$\[wump:version]/g, `${this.bot.pkg.version} ${this.bot.conf['nightly'] ? 'NIGHTLY' : 'DISTRIBUTION'}`)
|
||||
.replace(/\$\[process:hash]/g, process.hash)
|
||||
.replace(/\$\[date:now]/g, moment(Date.now()).format('HH[:]mm[:]ss'))
|
||||
.replace(/\$\[emoji#0]/g, this.bot.emote('logs', 'shard', 'ready'));
|
||||
}
|
||||
};
|
||||
28
src/events/Discord/shardResume.js
Normal file
28
src/events/Discord/shardResume.js
Normal file
@@ -0,0 +1,28 @@
|
||||
const { DiscordEvent } = require('../../core');
|
||||
|
||||
const moment = require('moment');
|
||||
const { safeLoad } = require('js-yaml');
|
||||
const { join: pJoin } = require('path');
|
||||
const { readFileSync } = require('fs');
|
||||
|
||||
module.exports = class ShardResume extends DiscordEvent {
|
||||
constructor(bot) {
|
||||
super(bot, { name: 'shardResume' });
|
||||
|
||||
this.strings = safeLoad(readFileSync(pJoin(__dirname, '..', '..', 'assets', 'i18n', bot.conf['discord']['locale'], 'logs_simple.yml'), { encoding: 'utf8' }));
|
||||
}
|
||||
|
||||
emit(id) {
|
||||
this.bot.hook.send({ content: this.localize(this.strings['connection']['shard']['resume'], { shard: id }) });
|
||||
}
|
||||
|
||||
localize(msg, extData) {
|
||||
if (!msg) return '';
|
||||
return msg
|
||||
.replace(/\$\[shard:id]/g, extData.shard)
|
||||
.replace(/\$\[wump:version]/g, `${this.bot.pkg.version} ${this.bot.conf['nightly'] ? 'NIGHTLY' : 'DISTRIBUTION'}`)
|
||||
.replace(/\$\[process:hash]/g, process.hash)
|
||||
.replace(/\$\[date:now]/g, moment(Date.now()).format('HH[:]mm[:]ss'))
|
||||
.replace(/\$\[emoji#0]/g, this.bot.emote('logs', 'shard', 'resume'));
|
||||
}
|
||||
};
|
||||
@@ -22,7 +22,7 @@ globalize();
|
||||
let Discord, Database;
|
||||
|
||||
process.argv = require('larg')(process.argv.slice(2));
|
||||
process.hash = String.string(Math.floor(Math.random() * 5) + 3);
|
||||
process.hash = String.string(8);
|
||||
|
||||
process.handleError = (err, name, type, exit = false, custom = false) => {
|
||||
if (!custom) print(0, `${type ? type : green('Master')} !! ${name ? name : err.name ? err.name : 'Error'} -\n${red(`${err.message && err.stack ? `${err.message}\n${err.stack}` : err}`)}`);
|
||||
@@ -71,10 +71,14 @@ process.on('SIGINT', () => {
|
||||
print(2, `${yellow('Database')} >> ${green('Connected to database')}` );
|
||||
})();
|
||||
|
||||
|
||||
if (conf['discord']['enabled']) {
|
||||
print(2, `${cyan('Discord')} >> Creating client...`);
|
||||
Discord = new DiscordClient(conf['discord']['token'], {
|
||||
webhook :{
|
||||
token : conf['discord']['webhook']['token'] || undefined,
|
||||
channel: conf['discord']['webhook']['channel'] || undefined
|
||||
},
|
||||
|
||||
clientOptions : {
|
||||
maxShards : 'auto',
|
||||
getAllUsers : false,
|
||||
|
||||
13
src/util/sizeOf/detector.js
Normal file
13
src/util/sizeOf/detector.js
Normal file
@@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
var typeHandlers = require('./types');
|
||||
|
||||
module.exports = function (buffer, filepath) {
|
||||
var type, result;
|
||||
for (type in typeHandlers) {
|
||||
result = typeHandlers[type].detect(buffer, filepath);
|
||||
if (result) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
};
|
||||
126
src/util/sizeOf/index.js
Normal file
126
src/util/sizeOf/index.js
Normal file
@@ -0,0 +1,126 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
var typeHandlers = require('./types');
|
||||
var detector = require('./detector');
|
||||
|
||||
// Maximum buffer size, with a default of 512 kilobytes.
|
||||
// TO-DO: make this adaptive based on the initial signature of the image
|
||||
var MaxBufferSize = 512*1024;
|
||||
|
||||
/**
|
||||
* Return size information based on a buffer
|
||||
*
|
||||
* @param {Buffer} buffer
|
||||
* @param {String} filepath
|
||||
* @returns {Object}
|
||||
*/
|
||||
function lookup (buffer, filepath) {
|
||||
// detect the file type.. don't rely on the extension
|
||||
var type = detector(buffer, filepath);
|
||||
|
||||
// find an appropriate handler for this file type
|
||||
if (type in typeHandlers) {
|
||||
var size = typeHandlers[type].calculate(buffer, filepath);
|
||||
if (size !== false) {
|
||||
size.type = type;
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
// throw up, if we don't understand the file
|
||||
throw new TypeError('unsupported file type: ' + type + ' (file: ' + filepath + ')');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a file into a buffer.
|
||||
*
|
||||
* The callback will be called after the process has completed. The
|
||||
* callback's first argument will be an error (or null). The second argument
|
||||
* will be the Buffer, if the operation was successful.
|
||||
*
|
||||
* @param {String} filepath
|
||||
* @param {Function} callback
|
||||
*/
|
||||
function asyncFileToBuffer (filepath, callback) {
|
||||
// open the file in read only mode
|
||||
fs.open(filepath, 'r', function (err, descriptor) {
|
||||
if (err) { return callback(err); }
|
||||
fs.fstat(descriptor, function (err, stats) {
|
||||
if (err) { return callback(err); }
|
||||
var size = stats.size;
|
||||
if (size <= 0) {
|
||||
return callback(new Error('File size is not greater than 0 —— ' + filepath));
|
||||
}
|
||||
var bufferSize = Math.min(size, MaxBufferSize);
|
||||
var buffer = Buffer.alloc(bufferSize);
|
||||
// read first buffer block from the file, asynchronously
|
||||
fs.read(descriptor, buffer, 0, bufferSize, 0, function (err) {
|
||||
if (err) { return callback(err); }
|
||||
// close the file, we are done
|
||||
fs.close(descriptor, function (err) {
|
||||
callback(err, buffer);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously reads a file into a buffer, blocking the nodejs process.
|
||||
*
|
||||
* @param {String} filepath
|
||||
* @returns {Buffer}
|
||||
*/
|
||||
function syncFileToBuffer (filepath) {
|
||||
// read from the file, synchronously
|
||||
var descriptor = fs.openSync(filepath, 'r');
|
||||
var size = fs.fstatSync(descriptor).size;
|
||||
var bufferSize = Math.min(size, MaxBufferSize);
|
||||
var buffer = Buffer.alloc(bufferSize);
|
||||
fs.readSync(descriptor, buffer, 0, bufferSize, 0);
|
||||
fs.closeSync(descriptor);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Buffer|string} input - buffer or relative/absolute path of the image file
|
||||
* @param {Function} callback - optional function for async detection
|
||||
*/
|
||||
module.exports = function (input, callback) {
|
||||
|
||||
// Handle buffer input
|
||||
if (Buffer.isBuffer(input)) {
|
||||
return lookup(input);
|
||||
}
|
||||
|
||||
// input should be a string at this point
|
||||
if (typeof input !== 'string') {
|
||||
throw new TypeError('invalid invocation');
|
||||
}
|
||||
|
||||
// resolve the file path
|
||||
var filepath = path.resolve(input);
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
asyncFileToBuffer(filepath, function (err, buffer) {
|
||||
if (err) { return callback(err); }
|
||||
|
||||
// return the dimensions
|
||||
var dimensions;
|
||||
try {
|
||||
dimensions = lookup(buffer, filepath);
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
callback(err, dimensions);
|
||||
});
|
||||
} else {
|
||||
var buffer = syncFileToBuffer(filepath);
|
||||
return lookup(buffer, filepath);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.types = Object.keys(typeHandlers);
|
||||
11
src/util/sizeOf/readUInt.js
Normal file
11
src/util/sizeOf/readUInt.js
Normal file
@@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
// Abstract reading multi-byte unsigned integers
|
||||
function readUInt (buffer, bits, offset, isBigEndian) {
|
||||
offset = offset || 0;
|
||||
var endian = isBigEndian ? 'BE' : 'LE';
|
||||
var method = buffer['readUInt' + bits + endian];
|
||||
return method.call(buffer, offset);
|
||||
}
|
||||
|
||||
module.exports = readUInt;
|
||||
19
src/util/sizeOf/types.js
Normal file
19
src/util/sizeOf/types.js
Normal file
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
// load all available handlers for browserify support
|
||||
var typeHandlers = {
|
||||
bmp: require('./types/bmp'),
|
||||
cur: require('./types/cur'),
|
||||
dds: require('./types/dds'),
|
||||
gif: require('./types/gif'),
|
||||
icns: require('./types/icns'),
|
||||
ico: require('./types/ico'),
|
||||
jpg: require('./types/jpg'),
|
||||
png: require('./types/png'),
|
||||
psd: require('./types/psd'),
|
||||
svg: require('./types/svg'),
|
||||
tiff: require('./types/tiff'),
|
||||
webp: require('./types/webp'),
|
||||
};
|
||||
|
||||
module.exports = typeHandlers;
|
||||
17
src/util/sizeOf/types/bmp.js
Normal file
17
src/util/sizeOf/types/bmp.js
Normal file
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
function isBMP (buffer) {
|
||||
return ('BM' === buffer.toString('ascii', 0, 2));
|
||||
}
|
||||
|
||||
function calculate (buffer) {
|
||||
return {
|
||||
'width': buffer.readUInt32LE(18),
|
||||
'height': Math.abs(buffer.readInt32LE(22))
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
'detect': isBMP,
|
||||
'calculate': calculate
|
||||
};
|
||||
17
src/util/sizeOf/types/cur.js
Normal file
17
src/util/sizeOf/types/cur.js
Normal file
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
var TYPE_CURSOR = 2;
|
||||
|
||||
function isCUR (buffer) {
|
||||
var type;
|
||||
if (buffer.readUInt16LE(0) !== 0) {
|
||||
return false;
|
||||
}
|
||||
type = buffer.readUInt16LE(2);
|
||||
return type === TYPE_CURSOR;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
'detect': isCUR,
|
||||
'calculate': require('./ico').calculate
|
||||
};
|
||||
18
src/util/sizeOf/types/dds.js
Normal file
18
src/util/sizeOf/types/dds.js
Normal file
@@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
function isDDS(buffer){
|
||||
return buffer.readUInt32LE(0) === 0x20534444;
|
||||
}
|
||||
|
||||
function calculate(buffer){
|
||||
// read file resolution metadata
|
||||
return {
|
||||
'height': buffer.readUInt32LE(12),
|
||||
'width': buffer.readUInt32LE(16)
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
'detect': isDDS,
|
||||
'calculate': calculate
|
||||
};
|
||||
19
src/util/sizeOf/types/gif.js
Normal file
19
src/util/sizeOf/types/gif.js
Normal file
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
var gifRegexp = /^GIF8[79]a/;
|
||||
function isGIF (buffer) {
|
||||
var signature = buffer.toString('ascii', 0, 6);
|
||||
return (gifRegexp.test(signature));
|
||||
}
|
||||
|
||||
function calculate(buffer) {
|
||||
return {
|
||||
'width': buffer.readUInt16LE(6),
|
||||
'height': buffer.readUInt16LE(8)
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
'detect': isGIF,
|
||||
'calculate': calculate
|
||||
};
|
||||
122
src/util/sizeOf/types/icns.js
Normal file
122
src/util/sizeOf/types/icns.js
Normal file
@@ -0,0 +1,122 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* ICNS Header
|
||||
*
|
||||
* | Offset | Size | Purpose |
|
||||
* | 0 | 4 | Magic literal, must be "icns" (0x69, 0x63, 0x6e, 0x73) |
|
||||
* | 4 | 4 | Length of file, in bytes, msb first. |
|
||||
*
|
||||
**/
|
||||
var SIZE_HEADER = 4 + 4; // 8
|
||||
var FILE_LENGTH_OFFSET = 4; // MSB => BIG ENDIAN
|
||||
|
||||
/**
|
||||
* Image Entry
|
||||
*
|
||||
* | Offset | Size | Purpose |
|
||||
* | 0 | 4 | Icon type, see OSType below. |
|
||||
* | 4 | 4 | Length of data, in bytes (including type and length), msb first. |
|
||||
* | 8 | n | Icon data |
|
||||
*
|
||||
**/
|
||||
var ENTRY_LENGTH_OFFSET = 4; // MSB => BIG ENDIAN
|
||||
|
||||
function isICNS (buffer) {
|
||||
return ('icns' === buffer.toString('ascii', 0, 4));
|
||||
}
|
||||
|
||||
var ICON_TYPE_SIZE = {
|
||||
ICON: 32,
|
||||
'ICN#': 32,
|
||||
// m => 16 x 16
|
||||
'icm#': 16,
|
||||
icm4: 16,
|
||||
icm8: 16,
|
||||
// s => 16 x 16
|
||||
'ics#': 16,
|
||||
ics4: 16,
|
||||
ics8: 16,
|
||||
is32: 16,
|
||||
s8mk: 16,
|
||||
icp4: 16,
|
||||
// l => 32 x 32
|
||||
icl4: 32,
|
||||
icl8: 32,
|
||||
il32: 32,
|
||||
l8mk: 32,
|
||||
icp5: 32,
|
||||
ic11: 32,
|
||||
// h => 48 x 48
|
||||
ich4: 48,
|
||||
ich8: 48,
|
||||
ih32: 48,
|
||||
h8mk: 48,
|
||||
// . => 64 x 64
|
||||
icp6: 64,
|
||||
ic12: 32,
|
||||
// t => 128 x 128
|
||||
it32: 128,
|
||||
t8mk: 128,
|
||||
ic07: 128,
|
||||
// . => 256 x 256
|
||||
ic08: 256,
|
||||
ic13: 256,
|
||||
// . => 512 x 512
|
||||
ic09: 512,
|
||||
ic14: 512,
|
||||
// . => 1024 x 1024
|
||||
ic10: 1024,
|
||||
};
|
||||
|
||||
function readImageHeader(buffer, imageOffset) {
|
||||
var imageLengthOffset = imageOffset + ENTRY_LENGTH_OFFSET;
|
||||
// returns [type, length]
|
||||
return [
|
||||
buffer.toString('ascii', imageOffset, imageLengthOffset),
|
||||
buffer.readUInt32BE(imageLengthOffset)
|
||||
];
|
||||
}
|
||||
|
||||
function getImageSize(type) {
|
||||
var size = ICON_TYPE_SIZE[type];
|
||||
return { width: size, height: size, type: type };
|
||||
}
|
||||
|
||||
function calculate (buffer) {
|
||||
var
|
||||
bufferLength = buffer.length,
|
||||
imageOffset = SIZE_HEADER,
|
||||
fileLength = buffer.readUInt32BE(FILE_LENGTH_OFFSET),
|
||||
imageHeader,
|
||||
imageSize,
|
||||
result;
|
||||
|
||||
imageHeader = readImageHeader(buffer, imageOffset);
|
||||
imageSize = getImageSize(imageHeader[0]);
|
||||
imageOffset += imageHeader[1];
|
||||
|
||||
if (imageOffset === fileLength) {
|
||||
return imageSize;
|
||||
}
|
||||
|
||||
result = {
|
||||
width: imageSize.width,
|
||||
height: imageSize.height,
|
||||
images: [imageSize]
|
||||
};
|
||||
|
||||
while (imageOffset < fileLength && imageOffset < bufferLength) {
|
||||
imageHeader = readImageHeader(buffer, imageOffset);
|
||||
imageSize = getImageSize(imageHeader[0]);
|
||||
imageOffset += imageHeader[1];
|
||||
result.images.push(imageSize);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
'detect': isICNS,
|
||||
'calculate': calculate
|
||||
};
|
||||
81
src/util/sizeOf/types/ico.js
Normal file
81
src/util/sizeOf/types/ico.js
Normal file
@@ -0,0 +1,81 @@
|
||||
'use strict';
|
||||
|
||||
var TYPE_ICON = 1;
|
||||
|
||||
/**
|
||||
* ICON Header
|
||||
*
|
||||
* | Offset | Size | Purpose |
|
||||
* | 0 | 2 | Reserved. Must always be 0. |
|
||||
* | 2 | 2 | Image type: 1 for icon (.ICO) image, 2 for cursor (.CUR) image. Other values are invalid. |
|
||||
* | 4 | 2 | Number of images in the file. |
|
||||
*
|
||||
**/
|
||||
var SIZE_HEADER = 2 + 2 + 2; // 6
|
||||
|
||||
/**
|
||||
* Image Entry
|
||||
*
|
||||
* | Offset | Size | Purpose |
|
||||
* | 0 | 1 | Image width in pixels. Can be any number between 0 and 255. Value 0 means width is 256 pixels. |
|
||||
* | 1 | 1 | Image height in pixels. Can be any number between 0 and 255. Value 0 means height is 256 pixels. |
|
||||
* | 2 | 1 | Number of colors in the color palette. Should be 0 if the image does not use a color palette. |
|
||||
* | 3 | 1 | Reserved. Should be 0. |
|
||||
* | 4 | 2 | ICO format: Color planes. Should be 0 or 1. |
|
||||
* | | | CUR format: The horizontal coordinates of the hotspot in number of pixels from the left. |
|
||||
* | 6 | 2 | ICO format: Bits per pixel. |
|
||||
* | | | CUR format: The vertical coordinates of the hotspot in number of pixels from the top. |
|
||||
* | 8 | 4 | The size of the image's data in bytes |
|
||||
* | 12 | 4 | The offset of BMP or PNG data from the beginning of the ICO/CUR file |
|
||||
*
|
||||
**/
|
||||
var SIZE_IMAGE_ENTRY = 1 + 1 + 1 + 1 + 2 + 2 + 4 + 4; // 16
|
||||
|
||||
function isICO (buffer) {
|
||||
var type;
|
||||
if (buffer.readUInt16LE(0) !== 0) {
|
||||
return false;
|
||||
}
|
||||
type = buffer.readUInt16LE(2);
|
||||
return type === TYPE_ICON;
|
||||
}
|
||||
|
||||
function getSizeFromOffset(buffer, offset) {
|
||||
var value = buffer.readUInt8(offset);
|
||||
return value === 0 ? 256 : value;
|
||||
}
|
||||
|
||||
function getImageSize(buffer, imageIndex) {
|
||||
var offset = SIZE_HEADER + (imageIndex * SIZE_IMAGE_ENTRY);
|
||||
return {
|
||||
'width': getSizeFromOffset(buffer, offset),
|
||||
'height': getSizeFromOffset(buffer, offset + 1)
|
||||
};
|
||||
}
|
||||
|
||||
function calculate (buffer) {
|
||||
var
|
||||
nbImages = buffer.readUInt16LE(4),
|
||||
result = getImageSize(buffer, 0),
|
||||
imageIndex;
|
||||
|
||||
if (nbImages === 1) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result.images = [{
|
||||
width: result.width,
|
||||
height: result.height
|
||||
}];
|
||||
|
||||
for (imageIndex = 1; imageIndex < nbImages; imageIndex += 1) {
|
||||
result.images.push(getImageSize(buffer, imageIndex));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
'detect': isICO,
|
||||
'calculate': calculate
|
||||
};
|
||||
145
src/util/sizeOf/types/jpg.js
Normal file
145
src/util/sizeOf/types/jpg.js
Normal file
@@ -0,0 +1,145 @@
|
||||
'use strict';
|
||||
|
||||
// NOTE: we only support baseline and progressive JPGs here
|
||||
// due to the structure of the loader class, we only get a buffer
|
||||
// with a maximum size of 4096 bytes. so if the SOF marker is outside
|
||||
// if this range we can't detect the file size correctly.
|
||||
|
||||
function isJPG (buffer) { //, filepath
|
||||
var SOIMarker = buffer.toString('hex', 0, 2);
|
||||
return ('ffd8' === SOIMarker);
|
||||
}
|
||||
|
||||
function isEXIF (buffer) { //, filepath
|
||||
var exifMarker = buffer.toString('hex', 2, 6);
|
||||
return (exifMarker === '45786966'); // 'Exif'
|
||||
}
|
||||
|
||||
function extractSize (buffer, i) {
|
||||
return {
|
||||
'height' : buffer.readUInt16BE(i),
|
||||
'width' : buffer.readUInt16BE(i + 2)
|
||||
};
|
||||
}
|
||||
|
||||
var APP1_DATA_SIZE_BYTES = 2;
|
||||
var EXIF_HEADER_BYTES = 6;
|
||||
var TIFF_BYTE_ALIGN_BYTES = 2;
|
||||
var MOTOROLA_BYTE_ALIGN = '4d4d';
|
||||
|
||||
// Each entry is exactly 12 bytes
|
||||
var IDF_ENTRY_BYTES = 12;
|
||||
var NUM_DIRECTORY_ENTRIES_BYTES = 2;
|
||||
|
||||
function extractOrientation (buffer, i) {
|
||||
// Skip APP1 Data Size
|
||||
var exifBlock = buffer.slice(APP1_DATA_SIZE_BYTES, i);
|
||||
|
||||
// TODO: support intel byte alignment
|
||||
var byteAlign = exifBlock.toString('hex', EXIF_HEADER_BYTES, EXIF_HEADER_BYTES + TIFF_BYTE_ALIGN_BYTES);
|
||||
if (byteAlign !== MOTOROLA_BYTE_ALIGN) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: assert that this contains 0x002A
|
||||
// var STATIC_MOTOROLA_TIFF_HEADER_BYTES = 2;
|
||||
// var TIFF_IMAGE_FILE_DIRECTORY_BYTES = 4;
|
||||
|
||||
// TODO: derive from TIFF_IMAGE_FILE_DIRECTORY_BYTES
|
||||
var idfOffset = 8;
|
||||
|
||||
// IDF osset works from right after the header bytes
|
||||
// (so the offset includes the tiff byte align)
|
||||
var offset = EXIF_HEADER_BYTES + idfOffset;
|
||||
|
||||
var idfDirectoryEntries = exifBlock.readUInt16BE(offset);
|
||||
|
||||
var start;
|
||||
var end;
|
||||
for (var directoryEntryNumber = 0; directoryEntryNumber < idfDirectoryEntries; directoryEntryNumber++) {
|
||||
start = offset + NUM_DIRECTORY_ENTRIES_BYTES + (directoryEntryNumber * IDF_ENTRY_BYTES);
|
||||
end = start + IDF_ENTRY_BYTES;
|
||||
|
||||
var block = exifBlock.slice(start, end);
|
||||
var tagNumber = block.readUInt16BE(0);
|
||||
|
||||
// 0x0112 (decimal: 274) is the `orientation` tag ID
|
||||
if (tagNumber === 274) {
|
||||
var dataFormat = block.readUInt16BE(2);
|
||||
if (dataFormat !== 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
// unsinged int has 2 bytes per component
|
||||
// if there would more than 4 bytes in total it's a pointer
|
||||
var numberOfComponents = block.readUInt32BE(4);
|
||||
if (numberOfComponents !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var orientation = block.readUInt16BE(8);
|
||||
|
||||
return orientation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateBuffer (buffer, i) {
|
||||
// index should be within buffer limits
|
||||
if (i > buffer.length) {
|
||||
throw new TypeError('Corrupt JPG, exceeded buffer limits');
|
||||
}
|
||||
// Every JPEG block must begin with a 0xFF
|
||||
if (buffer[i] !== 0xFF) {
|
||||
throw new TypeError('Invalid JPG, marker table corrupted');
|
||||
}
|
||||
}
|
||||
|
||||
function calculate (buffer) {
|
||||
// Skip 4 chars, they are for signature
|
||||
buffer = buffer.slice(4);
|
||||
|
||||
var orientation;
|
||||
|
||||
var i, next;
|
||||
while (buffer.length) {
|
||||
// read length of the next block
|
||||
i = buffer.readUInt16BE(0);
|
||||
|
||||
|
||||
if (isEXIF(buffer)) {
|
||||
orientation = extractOrientation(buffer, i);
|
||||
}
|
||||
|
||||
// ensure correct format
|
||||
validateBuffer(buffer, i);
|
||||
|
||||
// 0xFFC0 is baseline standard(SOF)
|
||||
// 0xFFC1 is baseline optimized(SOF)
|
||||
// 0xFFC2 is progressive(SOF2)
|
||||
next = buffer[i + 1];
|
||||
if (next === 0xC0 || next === 0xC1 || next === 0xC2) {
|
||||
var size = extractSize(buffer, i + 5);
|
||||
|
||||
if (!orientation) {
|
||||
return size;
|
||||
}
|
||||
|
||||
return {
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
orientation: orientation
|
||||
};
|
||||
}
|
||||
|
||||
// move to the next block
|
||||
buffer = buffer.slice(i + 2);
|
||||
}
|
||||
|
||||
throw new TypeError('Invalid JPG, no size found');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
'detect': isJPG,
|
||||
'calculate': calculate
|
||||
};
|
||||
38
src/util/sizeOf/types/png.js
Normal file
38
src/util/sizeOf/types/png.js
Normal file
@@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
var pngSignature = 'PNG\r\n\x1a\n';
|
||||
var pngImageHeaderChunkName = 'IHDR';
|
||||
|
||||
// Used to detect "fried" png's: http://www.jongware.com/pngdefry.html
|
||||
var pngFriedChunkName = 'CgBI';
|
||||
|
||||
function isPNG (buffer) {
|
||||
if (pngSignature === buffer.toString('ascii', 1, 8)) {
|
||||
var chunkName = buffer.toString('ascii', 12, 16);
|
||||
if (chunkName === pngFriedChunkName) {
|
||||
chunkName = buffer.toString('ascii', 28, 32);
|
||||
}
|
||||
if (chunkName !== pngImageHeaderChunkName) {
|
||||
throw new TypeError('invalid png');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function calculate (buffer) {
|
||||
if (buffer.toString('ascii', 12, 16) === pngFriedChunkName) {
|
||||
return {
|
||||
'width': buffer.readUInt32BE(32),
|
||||
'height': buffer.readUInt32BE(36)
|
||||
};
|
||||
}
|
||||
return {
|
||||
'width': buffer.readUInt32BE(16),
|
||||
'height': buffer.readUInt32BE(20)
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
'detect': isPNG,
|
||||
'calculate': calculate
|
||||
};
|
||||
17
src/util/sizeOf/types/psd.js
Normal file
17
src/util/sizeOf/types/psd.js
Normal file
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
function isPSD (buffer) {
|
||||
return ('8BPS' === buffer.toString('ascii', 0, 4));
|
||||
}
|
||||
|
||||
function calculate (buffer) {
|
||||
return {
|
||||
'width': buffer.readUInt32BE(18),
|
||||
'height': buffer.readUInt32BE(14)
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
'detect': isPSD,
|
||||
'calculate': calculate
|
||||
};
|
||||
78
src/util/sizeOf/types/svg.js
Normal file
78
src/util/sizeOf/types/svg.js
Normal file
@@ -0,0 +1,78 @@
|
||||
'use strict';
|
||||
|
||||
var svgReg = /<svg\s([^>"']|"[^"]*"|'[^']*')*>/;
|
||||
function isSVG (buffer) {
|
||||
return svgReg.test(buffer);
|
||||
}
|
||||
|
||||
var extractorRegExps = {
|
||||
'root': svgReg,
|
||||
'width': /\bwidth=(['"])([^%]+?)\1/,
|
||||
'height': /\bheight=(['"])([^%]+?)\1/,
|
||||
'viewbox': /\bviewBox=(['"])(.+?)\1/
|
||||
};
|
||||
|
||||
function parseViewbox (viewbox) {
|
||||
var bounds = viewbox.split(' ');
|
||||
return {
|
||||
'width': parseInt(bounds[2], 10),
|
||||
'height': parseInt(bounds[3], 10)
|
||||
};
|
||||
}
|
||||
|
||||
function parseAttributes (root) {
|
||||
var width = root.match(extractorRegExps.width);
|
||||
var height = root.match(extractorRegExps.height);
|
||||
var viewbox = root.match(extractorRegExps.viewbox);
|
||||
return {
|
||||
'width': width && parseInt(width[2], 10),
|
||||
'height': height && parseInt(height[2], 10),
|
||||
'viewbox': viewbox && parseViewbox(viewbox[2])
|
||||
};
|
||||
}
|
||||
|
||||
function calculateByDimensions (attrs) {
|
||||
return {
|
||||
'width': attrs.width,
|
||||
'height': attrs.height
|
||||
};
|
||||
}
|
||||
|
||||
function calculateByViewbox (attrs) {
|
||||
var ratio = attrs.viewbox.width / attrs.viewbox.height;
|
||||
if (attrs.width) {
|
||||
return {
|
||||
'width': attrs.width,
|
||||
'height': Math.floor(attrs.width / ratio)
|
||||
};
|
||||
}
|
||||
if (attrs.height) {
|
||||
return {
|
||||
'width': Math.floor(attrs.height * ratio),
|
||||
'height': attrs.height
|
||||
};
|
||||
}
|
||||
return {
|
||||
'width': attrs.viewbox.width,
|
||||
'height': attrs.viewbox.height
|
||||
};
|
||||
}
|
||||
|
||||
function calculate (buffer) {
|
||||
var root = buffer.toString('utf8').match(extractorRegExps.root);
|
||||
if (root) {
|
||||
var attrs = parseAttributes(root[0]);
|
||||
if (attrs.width && attrs.height) {
|
||||
return calculateByDimensions(attrs);
|
||||
}
|
||||
if (attrs.viewbox) {
|
||||
return calculateByViewbox(attrs);
|
||||
}
|
||||
}
|
||||
throw new TypeError('invalid svg');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
'detect': isSVG,
|
||||
'calculate': calculate
|
||||
};
|
||||
120
src/util/sizeOf/types/tiff.js
Normal file
120
src/util/sizeOf/types/tiff.js
Normal file
@@ -0,0 +1,120 @@
|
||||
'use strict';
|
||||
|
||||
// based on http://www.compix.com/fileformattif.htm
|
||||
// TO-DO: support big-endian as well
|
||||
|
||||
var fs = require('fs');
|
||||
var readUInt = require('../readUInt');
|
||||
|
||||
function isTIFF (buffer) {
|
||||
var hex4 = buffer.toString('hex', 0, 4);
|
||||
return ('49492a00' === hex4 || '4d4d002a' === hex4);
|
||||
}
|
||||
|
||||
// Read IFD (image-file-directory) into a buffer
|
||||
function readIFD (buffer, filepath, isBigEndian) {
|
||||
|
||||
var ifdOffset = readUInt(buffer, 32, 4, isBigEndian);
|
||||
|
||||
// read only till the end of the file
|
||||
var bufferSize = 1024;
|
||||
var fileSize = fs.statSync(filepath).size;
|
||||
if (ifdOffset + bufferSize > fileSize) {
|
||||
bufferSize = fileSize - ifdOffset - 10;
|
||||
}
|
||||
|
||||
// populate the buffer
|
||||
var endBuffer = Buffer.alloc(bufferSize);
|
||||
var descriptor = fs.openSync(filepath, 'r');
|
||||
fs.readSync(descriptor, endBuffer, 0, bufferSize, ifdOffset);
|
||||
|
||||
// var ifdLength = readUInt(endBuffer, 16, 0, isBigEndian);
|
||||
var ifdBuffer = endBuffer.slice(2); //, 2 + 12 * ifdLength);
|
||||
return ifdBuffer;
|
||||
}
|
||||
|
||||
// TIFF values seem to be messed up on Big-Endian, this helps
|
||||
function readValue (buffer, isBigEndian) {
|
||||
var low = readUInt(buffer, 16, 8, isBigEndian);
|
||||
var high = readUInt(buffer, 16, 10, isBigEndian);
|
||||
return (high << 16) + low;
|
||||
}
|
||||
|
||||
// move to the next tag
|
||||
function nextTag (buffer) {
|
||||
if (buffer.length > 24) {
|
||||
return buffer.slice(12);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract IFD tags from TIFF metadata
|
||||
/* eslint-disable complexity */
|
||||
function extractTags (buffer, isBigEndian) {
|
||||
var tags = {};
|
||||
var code, type, length;
|
||||
|
||||
while (buffer && buffer.length) {
|
||||
code = readUInt(buffer, 16, 0, isBigEndian);
|
||||
type = readUInt(buffer, 16, 2, isBigEndian);
|
||||
length = readUInt(buffer, 32, 4, isBigEndian);
|
||||
|
||||
// 0 means end of IFD
|
||||
if (code === 0) {
|
||||
break;
|
||||
} else {
|
||||
// 256 is width, 257 is height
|
||||
// if (code === 256 || code === 257) {
|
||||
if (length === 1 && (type === 3 || type === 4)) {
|
||||
tags[code] = readValue(buffer, isBigEndian);
|
||||
}
|
||||
|
||||
// move to the next tag
|
||||
buffer = nextTag(buffer);
|
||||
}
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
/* eslint-enable complexity */
|
||||
|
||||
// Test if the TIFF is Big Endian or Little Endian
|
||||
function determineEndianness (buffer) {
|
||||
var signature = buffer.toString('ascii', 0, 2);
|
||||
if ('II' === signature) {
|
||||
return 'LE';
|
||||
} else if ('MM' === signature) {
|
||||
return 'BE';
|
||||
}
|
||||
}
|
||||
|
||||
function calculate (buffer, filepath) {
|
||||
|
||||
if (!filepath) {
|
||||
throw new TypeError('Tiff doesn\'t support buffer');
|
||||
}
|
||||
|
||||
// Determine BE/LE
|
||||
var isBigEndian = determineEndianness(buffer) === 'BE';
|
||||
|
||||
// read the IFD
|
||||
var ifdBuffer = readIFD(buffer, filepath, isBigEndian);
|
||||
|
||||
// extract the tags from the IFD
|
||||
var tags = extractTags(ifdBuffer, isBigEndian);
|
||||
|
||||
var width = tags[256];
|
||||
var height = tags[257];
|
||||
|
||||
if (!width || !height) {
|
||||
throw new TypeError('Invalid Tiff, missing tags');
|
||||
}
|
||||
|
||||
return {
|
||||
'width': width,
|
||||
'height': height
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
'detect': isTIFF,
|
||||
'calculate': calculate
|
||||
};
|
||||
71
src/util/sizeOf/types/webp.js
Normal file
71
src/util/sizeOf/types/webp.js
Normal file
@@ -0,0 +1,71 @@
|
||||
'use strict';
|
||||
|
||||
// based on https://developers.google.com/speed/webp/docs/riff_container
|
||||
|
||||
function isWebP (buffer) {
|
||||
var riffHeader = 'RIFF' === buffer.toString('ascii', 0, 4);
|
||||
var webpHeader = 'WEBP' === buffer.toString('ascii', 8, 12);
|
||||
var vp8Header = 'VP8' === buffer.toString('ascii', 12, 15);
|
||||
return (riffHeader && webpHeader && vp8Header);
|
||||
}
|
||||
|
||||
/* eslint-disable complexity */
|
||||
function calculate (buffer) {
|
||||
var chunkHeader = buffer.toString('ascii', 12, 16);
|
||||
buffer = buffer.slice(20, 30);
|
||||
|
||||
// Extended webp stream signature
|
||||
if (chunkHeader === 'VP8X') {
|
||||
var extendedHeader = buffer[0];
|
||||
var validStart = (extendedHeader & 0xc0) === 0;
|
||||
var validEnd = (extendedHeader & 0x01) === 0;
|
||||
if (validStart && validEnd) {
|
||||
return calculateExtended(buffer);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Lossless webp stream signature
|
||||
if (chunkHeader === 'VP8 ' && buffer[0] !== 0x2f) {
|
||||
return calculateLossy(buffer);
|
||||
}
|
||||
|
||||
// Lossy webp stream signature
|
||||
var signature = buffer.toString('hex', 3, 6);
|
||||
if (chunkHeader === 'VP8L' && signature !== '9d012a') {
|
||||
return calculateLossless(buffer);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
/* eslint-enable complexity */
|
||||
|
||||
function calculateExtended (buffer) {
|
||||
return {
|
||||
'width': 1 + buffer.readUIntLE(4, 3),
|
||||
'height': 1 + buffer.readUIntLE(7, 3)
|
||||
};
|
||||
}
|
||||
|
||||
function calculateLossless (buffer) {
|
||||
return {
|
||||
'width': 1 + (((buffer[2] & 0x3F) << 8) | buffer[1]),
|
||||
'height': 1 + (((buffer[4] & 0xF) << 10) | (buffer[3] << 2) |
|
||||
((buffer[2] & 0xC0) >> 6))
|
||||
};
|
||||
}
|
||||
|
||||
function calculateLossy (buffer) {
|
||||
// `& 0x3fff` returns the last 14 bits
|
||||
// TO-DO: include webp scaling in the calculations
|
||||
return {
|
||||
'width': buffer.readInt16LE(6) & 0x3fff,
|
||||
'height': buffer.readInt16LE(8) & 0x3fff
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
'detect': isWebP,
|
||||
'calculate': calculate
|
||||
};
|
||||
Reference in New Issue
Block a user