From 989dcd57de215c3f3dac8515e0efbe35e52aa760 Mon Sep 17 00:00:00 2001 From: sirx Date: Sun, 6 Nov 2016 03:14:18 +0100 Subject: [PATCH 1/7] animiertes eygon --- s/anim.js | 26 ++ s/index.tpl.html | 2 + s/libgif.js | 990 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1018 insertions(+) create mode 100644 s/anim.js create mode 100644 s/libgif.js diff --git a/s/anim.js b/s/anim.js new file mode 100644 index 0000000..691a671 --- /dev/null +++ b/s/anim.js @@ -0,0 +1,26 @@ +var $parent = document.createElement("div") + $gif = document.createElement("img") + ,$favicon = document.createElement("link") + +$gif.crossOrigin = "anonymous" + +$gif.src = "./s/favicon.gif" + +$favicon.rel = "icon" + +window.parent.document.head.appendChild($favicon) + +$parent.appendChild($gif) + +var supergif = new SuperGif({gif: $gif}) + ,$canvas + +supergif.load(()=> { + $canvas = supergif.get_canvas() + updateFavicon() +}) + +function updateFavicon() { + $favicon.href = $canvas.toDataURL() + window.requestAnimationFrame(updateFavicon) +} diff --git a/s/index.tpl.html b/s/index.tpl.html index 4f4ac55..29ee593 100644 --- a/s/index.tpl.html +++ b/s/index.tpl.html @@ -15,5 +15,7 @@ + + diff --git a/s/libgif.js b/s/libgif.js new file mode 100644 index 0000000..b14e0e9 --- /dev/null +++ b/s/libgif.js @@ -0,0 +1,990 @@ +/* + SuperGif + + Example usage: + + + + + + Image tag attributes: + + rel:animated_src - If this url is specified, it's loaded into the player instead of src. + This allows a preview frame to be shown until animated gif data is streamed into the canvas + + rel:auto_play - Defaults to 1 if not specified. If set to zero, a call to the play() method is needed + + Constructor options args + + gif Required. The DOM element of an img tag. + loop_mode Optional. Setting this to false will force disable looping of the gif. + auto_play Optional. Same as the rel:auto_play attribute above, this arg overrides the img tag info. + max_width Optional. Scale images over max_width down to max_width. Helpful with mobile. + on_end Optional. Add a callback for when the gif reaches the end of a single loop (one iteration). The first argument passed will be the gif HTMLElement. + loop_delay Optional. The amount of time to pause (in ms) after each single loop (iteration). + draw_while_loading Optional. Determines whether the gif will be drawn to the canvas whilst it is loaded. + show_progress_bar Optional. Only applies when draw_while_loading is set to true. + + Instance methods + + // loading + load( callback ) Loads the gif specified by the src or rel:animated_src sttributie of the img tag into a canvas element and then calls callback if one is passed + load_url( src, callback ) Loads the gif file specified in the src argument into a canvas element and then calls callback if one is passed + + // play controls + play - Start playing the gif + pause - Stop playing the gif + move_to(i) - Move to frame i of the gif + move_relative(i) - Move i frames ahead (or behind if i < 0) + + // getters + get_canvas The canvas element that the gif is playing in. Handy for assigning event handlers to. + get_playing Whether or not the gif is currently playing + get_loading Whether or not the gif has finished loading/parsing + get_auto_play Whether or not the gif is set to play automatically + get_length The number of frames in the gif + get_current_frame The index of the currently displayed frame of the gif + + For additional customization (viewport inside iframe) these params may be passed: + c_w, c_h - width and height of canvas + vp_t, vp_l, vp_ w, vp_h - top, left, width and height of the viewport + + A bonus: few articles to understand what is going on + http://enthusiasms.org/post/16976438906 + http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp + http://humpy77.deviantart.com/journal/Frame-Delay-Times-for-Animated-GIFs-214150546 + +*/ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define([], factory); + } else if (typeof exports === 'object') { + module.exports = factory(); + } else { + root.SuperGif = factory(); + } +}(this, function () { + // Generic functions + var bitsToNum = function (ba) { + return ba.reduce(function (s, n) { + return s * 2 + n; + }, 0); + }; + + var byteToBitArr = function (bite) { + var a = []; + for (var i = 7; i >= 0; i--) { + a.push( !! (bite & (1 << i))); + } + return a; + }; + + // Stream + /** + * @constructor + */ + // Make compiler happy. + var Stream = function (data) { + this.data = data; + this.len = this.data.length; + this.pos = 0; + + this.readByte = function () { + if (this.pos >= this.data.length) { + throw new Error('Attempted to read past end of stream.'); + } + if (data instanceof Uint8Array) + return data[this.pos++]; + else + return data.charCodeAt(this.pos++) & 0xFF; + }; + + this.readBytes = function (n) { + var bytes = []; + for (var i = 0; i < n; i++) { + bytes.push(this.readByte()); + } + return bytes; + }; + + this.read = function (n) { + var s = ''; + for (var i = 0; i < n; i++) { + s += String.fromCharCode(this.readByte()); + } + return s; + }; + + this.readUnsigned = function () { // Little-endian. + var a = this.readBytes(2); + return (a[1] << 8) + a[0]; + }; + }; + + var lzwDecode = function (minCodeSize, data) { + // TODO: Now that the GIF parser is a bit different, maybe this should get an array of bytes instead of a String? + var pos = 0; // Maybe this streaming thing should be merged with the Stream? + var readCode = function (size) { + var code = 0; + for (var i = 0; i < size; i++) { + if (data.charCodeAt(pos >> 3) & (1 << (pos & 7))) { + code |= 1 << i; + } + pos++; + } + return code; + }; + + var output = []; + + var clearCode = 1 << minCodeSize; + var eoiCode = clearCode + 1; + + var codeSize = minCodeSize + 1; + + var dict = []; + + var clear = function () { + dict = []; + codeSize = minCodeSize + 1; + for (var i = 0; i < clearCode; i++) { + dict[i] = [i]; + } + dict[clearCode] = []; + dict[eoiCode] = null; + + }; + + var code; + var last; + + while (true) { + last = code; + code = readCode(codeSize); + + if (code === clearCode) { + clear(); + continue; + } + if (code === eoiCode) break; + + if (code < dict.length) { + if (last !== clearCode) { + dict.push(dict[last].concat(dict[code][0])); + } + } + else { + if (code !== dict.length) throw new Error('Invalid LZW code.'); + dict.push(dict[last].concat(dict[last][0])); + } + output.push.apply(output, dict[code]); + + if (dict.length === (1 << codeSize) && codeSize < 12) { + // If we're at the last code and codeSize is 12, the next code will be a clearCode, and it'll be 12 bits long. + codeSize++; + } + } + + // I don't know if this is technically an error, but some GIFs do it. + //if (Math.ceil(pos / 8) !== data.length) throw new Error('Extraneous LZW bytes.'); + return output; + }; + + + // The actual parsing; returns an object with properties. + var parseGIF = function (st, handler) { + handler || (handler = {}); + + // LZW (GIF-specific) + var parseCT = function (entries) { // Each entry is 3 bytes, for RGB. + var ct = []; + for (var i = 0; i < entries; i++) { + ct.push(st.readBytes(3)); + } + return ct; + }; + + var readSubBlocks = function () { + var size, data; + data = ''; + do { + size = st.readByte(); + data += st.read(size); + } while (size !== 0); + return data; + }; + + var parseHeader = function () { + var hdr = {}; + hdr.sig = st.read(3); + hdr.ver = st.read(3); + if (hdr.sig !== 'GIF') throw new Error('Not a GIF file.'); // XXX: This should probably be handled more nicely. + hdr.width = st.readUnsigned(); + hdr.height = st.readUnsigned(); + + var bits = byteToBitArr(st.readByte()); + hdr.gctFlag = bits.shift(); + hdr.colorRes = bitsToNum(bits.splice(0, 3)); + hdr.sorted = bits.shift(); + hdr.gctSize = bitsToNum(bits.splice(0, 3)); + + hdr.bgColor = st.readByte(); + hdr.pixelAspectRatio = st.readByte(); // if not 0, aspectRatio = (pixelAspectRatio + 15) / 64 + if (hdr.gctFlag) { + hdr.gct = parseCT(1 << (hdr.gctSize + 1)); + } + handler.hdr && handler.hdr(hdr); + }; + + var parseExt = function (block) { + var parseGCExt = function (block) { + var blockSize = st.readByte(); // Always 4 + var bits = byteToBitArr(st.readByte()); + block.reserved = bits.splice(0, 3); // Reserved; should be 000. + block.disposalMethod = bitsToNum(bits.splice(0, 3)); + block.userInput = bits.shift(); + block.transparencyGiven = bits.shift(); + + block.delayTime = st.readUnsigned(); + + block.transparencyIndex = st.readByte(); + + block.terminator = st.readByte(); + + handler.gce && handler.gce(block); + }; + + var parseComExt = function (block) { + block.comment = readSubBlocks(); + handler.com && handler.com(block); + }; + + var parsePTExt = function (block) { + // No one *ever* uses this. If you use it, deal with parsing it yourself. + var blockSize = st.readByte(); // Always 12 + block.ptHeader = st.readBytes(12); + block.ptData = readSubBlocks(); + handler.pte && handler.pte(block); + }; + + var parseAppExt = function (block) { + var parseNetscapeExt = function (block) { + var blockSize = st.readByte(); // Always 3 + block.unknown = st.readByte(); // ??? Always 1? What is this? + block.iterations = st.readUnsigned(); + block.terminator = st.readByte(); + handler.app && handler.app.NETSCAPE && handler.app.NETSCAPE(block); + }; + + var parseUnknownAppExt = function (block) { + block.appData = readSubBlocks(); + // FIXME: This won't work if a handler wants to match on any identifier. + handler.app && handler.app[block.identifier] && handler.app[block.identifier](block); + }; + + var blockSize = st.readByte(); // Always 11 + block.identifier = st.read(8); + block.authCode = st.read(3); + switch (block.identifier) { + case 'NETSCAPE': + parseNetscapeExt(block); + break; + default: + parseUnknownAppExt(block); + break; + } + }; + + var parseUnknownExt = function (block) { + block.data = readSubBlocks(); + handler.unknown && handler.unknown(block); + }; + + block.label = st.readByte(); + switch (block.label) { + case 0xF9: + block.extType = 'gce'; + parseGCExt(block); + break; + case 0xFE: + block.extType = 'com'; + parseComExt(block); + break; + case 0x01: + block.extType = 'pte'; + parsePTExt(block); + break; + case 0xFF: + block.extType = 'app'; + parseAppExt(block); + break; + default: + block.extType = 'unknown'; + parseUnknownExt(block); + break; + } + }; + + var parseImg = function (img) { + var deinterlace = function (pixels, width) { + // Of course this defeats the purpose of interlacing. And it's *probably* + // the least efficient way it's ever been implemented. But nevertheless... + var newPixels = new Array(pixels.length); + var rows = pixels.length / width; + var cpRow = function (toRow, fromRow) { + var fromPixels = pixels.slice(fromRow * width, (fromRow + 1) * width); + newPixels.splice.apply(newPixels, [toRow * width, width].concat(fromPixels)); + }; + + // See appendix E. + var offsets = [0, 4, 2, 1]; + var steps = [8, 8, 4, 2]; + + var fromRow = 0; + for (var pass = 0; pass < 4; pass++) { + for (var toRow = offsets[pass]; toRow < rows; toRow += steps[pass]) { + cpRow(toRow, fromRow) + fromRow++; + } + } + + return newPixels; + }; + + img.leftPos = st.readUnsigned(); + img.topPos = st.readUnsigned(); + img.width = st.readUnsigned(); + img.height = st.readUnsigned(); + + var bits = byteToBitArr(st.readByte()); + img.lctFlag = bits.shift(); + img.interlaced = bits.shift(); + img.sorted = bits.shift(); + img.reserved = bits.splice(0, 2); + img.lctSize = bitsToNum(bits.splice(0, 3)); + + if (img.lctFlag) { + img.lct = parseCT(1 << (img.lctSize + 1)); + } + + img.lzwMinCodeSize = st.readByte(); + + var lzwData = readSubBlocks(); + + img.pixels = lzwDecode(img.lzwMinCodeSize, lzwData); + + if (img.interlaced) { // Move + img.pixels = deinterlace(img.pixels, img.width); + } + + handler.img && handler.img(img); + }; + + var parseBlock = function () { + var block = {}; + block.sentinel = st.readByte(); + + switch (String.fromCharCode(block.sentinel)) { // For ease of matching + case '!': + block.type = 'ext'; + parseExt(block); + break; + case ',': + block.type = 'img'; + parseImg(block); + break; + case ';': + block.type = 'eof'; + handler.eof && handler.eof(block); + break; + default: + throw new Error('Unknown block: 0x' + block.sentinel.toString(16)); // TODO: Pad this with a 0. + } + + if (block.type !== 'eof') setTimeout(parseBlock, 0); + }; + + var parse = function () { + parseHeader(); + setTimeout(parseBlock, 0); + }; + + parse(); + }; + + var SuperGif = function ( opts ) { + var options = { + //viewport position + vp_l: 0, + vp_t: 0, + vp_w: null, + vp_h: null, + //canvas sizes + c_w: null, + c_h: null + }; + for (var i in opts ) { options[i] = opts[i] } + if (options.vp_w && options.vp_h) options.is_vp = true; + + var stream; + var hdr; + + var loadError = null; + var loading = false; + + var transparency = null; + var delay = null; + var disposalMethod = null; + var disposalRestoreFromIdx = null; + var lastDisposalMethod = null; + var frame = null; + var lastImg = null; + + var playing = true; + var forward = true; + + var ctx_scaled = false; + + var frames = []; + var frameOffsets = []; // elements have .x and .y properties + + var gif = options.gif; + if (typeof options.auto_play == 'undefined') + options.auto_play = (!gif.getAttribute('rel:auto_play') || gif.getAttribute('rel:auto_play') == '1'); + + var onEndListener = (options.hasOwnProperty('on_end') ? options.on_end : null); + var loopDelay = (options.hasOwnProperty('loop_delay') ? options.loop_delay : 0); + var overrideLoopMode = (options.hasOwnProperty('loop_mode') ? options.loop_mode : 'auto'); + var drawWhileLoading = (options.hasOwnProperty('draw_while_loading') ? options.draw_while_loading : true); + var showProgressBar = drawWhileLoading ? (options.hasOwnProperty('show_progress_bar') ? options.show_progress_bar : true) : false; + var progressBarHeight = (options.hasOwnProperty('progressbar_height') ? options.progressbar_height : 25); + var progressBarBackgroundColor = (options.hasOwnProperty('progressbar_background_color') ? options.progressbar_background_color : 'rgba(255,255,255,0.4)'); + var progressBarForegroundColor = (options.hasOwnProperty('progressbar_foreground_color') ? options.progressbar_foreground_color : 'rgba(255,0,22,.8)'); + + var clear = function () { + transparency = null; + delay = null; + lastDisposalMethod = disposalMethod; + disposalMethod = null; + frame = null; + }; + + // XXX: There's probably a better way to handle catching exceptions when + // callbacks are involved. + var doParse = function () { + try { + parseGIF(stream, handler); + } + catch (err) { + doLoadError('parse'); + } + }; + + var doText = function (text) { + toolbar.innerHTML = text; // innerText? Escaping? Whatever. + toolbar.style.visibility = 'visible'; + }; + + var setSizes = function(w, h) { + canvas.width = w * get_canvas_scale(); + canvas.height = h * get_canvas_scale(); + toolbar.style.minWidth = ( w * get_canvas_scale() ) + 'px'; + + tmpCanvas.width = w; + tmpCanvas.height = h; + tmpCanvas.style.width = w + 'px'; + tmpCanvas.style.height = h + 'px'; + tmpCanvas.getContext('2d').setTransform(1, 0, 0, 1, 0, 0); + }; + + var setFrameOffset = function(frame, offset) { + if (!frameOffsets[frame]) { + frameOffsets[frame] = offset; + return; + } + if (typeof offset.x !== 'undefined') { + frameOffsets[frame].x = offset.x; + } + if (typeof offset.y !== 'undefined') { + frameOffsets[frame].y = offset.y; + } + }; + + var doShowProgress = function (pos, length, draw) { + if (draw && showProgressBar) { + var height = progressBarHeight; + var left, mid, top, width; + if (options.is_vp) { + if (!ctx_scaled) { + top = (options.vp_t + options.vp_h - height); + height = height; + left = options.vp_l; + mid = left + (pos / length) * options.vp_w; + width = canvas.width; + } else { + top = (options.vp_t + options.vp_h - height) / get_canvas_scale(); + height = height / get_canvas_scale(); + left = (options.vp_l / get_canvas_scale() ); + mid = left + (pos / length) * (options.vp_w / get_canvas_scale()); + width = canvas.width / get_canvas_scale(); + } + //some debugging, draw rect around viewport + if (false) { + if (!ctx_scaled) { + var l = options.vp_l, t = options.vp_t; + var w = options.vp_w, h = options.vp_h; + } else { + var l = options.vp_l/get_canvas_scale(), t = options.vp_t/get_canvas_scale(); + var w = options.vp_w/get_canvas_scale(), h = options.vp_h/get_canvas_scale(); + } + ctx.rect(l,t,w,h); + ctx.stroke(); + } + } + else { + top = (canvas.height - height) / (ctx_scaled ? get_canvas_scale() : 1); + mid = ((pos / length) * canvas.width) / (ctx_scaled ? get_canvas_scale() : 1); + width = canvas.width / (ctx_scaled ? get_canvas_scale() : 1 ); + height /= ctx_scaled ? get_canvas_scale() : 1; + } + + ctx.fillStyle = progressBarBackgroundColor; + ctx.fillRect(mid, top, width - mid, height); + + ctx.fillStyle = progressBarForegroundColor; + ctx.fillRect(0, top, mid, height); + } + }; + + var doLoadError = function (originOfError) { + var drawError = function () { + ctx.fillStyle = 'black'; + ctx.fillRect(0, 0, options.c_w ? options.c_w : hdr.width, options.c_h ? options.c_h : hdr.height); + ctx.strokeStyle = 'red'; + ctx.lineWidth = 3; + ctx.moveTo(0, 0); + ctx.lineTo(options.c_w ? options.c_w : hdr.width, options.c_h ? options.c_h : hdr.height); + ctx.moveTo(0, options.c_h ? options.c_h : hdr.height); + ctx.lineTo(options.c_w ? options.c_w : hdr.width, 0); + ctx.stroke(); + }; + + loadError = originOfError; + hdr = { + width: gif.width, + height: gif.height + }; // Fake header. + frames = []; + drawError(); + }; + + var doHdr = function (_hdr) { + hdr = _hdr; + setSizes(hdr.width, hdr.height) + }; + + var doGCE = function (gce) { + pushFrame(); + clear(); + transparency = gce.transparencyGiven ? gce.transparencyIndex : null; + delay = gce.delayTime; + disposalMethod = gce.disposalMethod; + // We don't have much to do with the rest of GCE. + }; + + var pushFrame = function () { + if (!frame) return; + frames.push({ + data: frame.getImageData(0, 0, hdr.width, hdr.height), + delay: delay + }); + frameOffsets.push({ x: 0, y: 0 }); + }; + + var doImg = function (img) { + if (!frame) frame = tmpCanvas.getContext('2d'); + + var currIdx = frames.length; + + //ct = color table, gct = global color table + var ct = img.lctFlag ? img.lct : hdr.gct; // TODO: What if neither exists? + + /* + Disposal method indicates the way in which the graphic is to + be treated after being displayed. + + Values : 0 - No disposal specified. The decoder is + not required to take any action. + 1 - Do not dispose. The graphic is to be left + in place. + 2 - Restore to background color. The area used by the + graphic must be restored to the background color. + 3 - Restore to previous. The decoder is required to + restore the area overwritten by the graphic with + what was there prior to rendering the graphic. + + Importantly, "previous" means the frame state + after the last disposal of method 0, 1, or 2. + */ + if (currIdx > 0) { + if (lastDisposalMethod === 3) { + // Restore to previous + // If we disposed every frame including first frame up to this point, then we have + // no composited frame to restore to. In this case, restore to background instead. + if (disposalRestoreFromIdx !== null) { + frame.putImageData(frames[disposalRestoreFromIdx].data, 0, 0); + } else { + frame.clearRect(lastImg.leftPos, lastImg.topPos, lastImg.width, lastImg.height); + } + } else { + disposalRestoreFromIdx = currIdx - 1; + } + + if (lastDisposalMethod === 2) { + // Restore to background color + // Browser implementations historically restore to transparent; we do the same. + // http://www.wizards-toolkit.org/discourse-server/viewtopic.php?f=1&t=21172#p86079 + frame.clearRect(lastImg.leftPos, lastImg.topPos, lastImg.width, lastImg.height); + } + } + // else, Undefined/Do not dispose. + // frame contains final pixel data from the last frame; do nothing + + //Get existing pixels for img region after applying disposal method + var imgData = frame.getImageData(img.leftPos, img.topPos, img.width, img.height); + + //apply color table colors + img.pixels.forEach(function (pixel, i) { + // imgData.data === [R,G,B,A,R,G,B,A,...] + if (pixel !== transparency) { + imgData.data[i * 4 + 0] = ct[pixel][0]; + imgData.data[i * 4 + 1] = ct[pixel][1]; + imgData.data[i * 4 + 2] = ct[pixel][2]; + imgData.data[i * 4 + 3] = 255; // Opaque. + } + }); + + frame.putImageData(imgData, img.leftPos, img.topPos); + + if (!ctx_scaled) { + ctx.scale(get_canvas_scale(),get_canvas_scale()); + ctx_scaled = true; + } + + // We could use the on-page canvas directly, except that we draw a progress + // bar for each image chunk (not just the final image). + if (drawWhileLoading) { + ctx.drawImage(tmpCanvas, 0, 0); + drawWhileLoading = options.auto_play; + } + + lastImg = img; + }; + + var player = (function () { + var i = -1; + var iterationCount = 0; + + var showingInfo = false; + var pinned = false; + + /** + * Gets the index of the frame "up next". + * @returns {number} + */ + var getNextFrameNo = function () { + var delta = (forward ? 1 : -1); + return (i + delta + frames.length) % frames.length; + }; + + var stepFrame = function (amount) { // XXX: Name is confusing. + i = i + amount; + + putFrame(); + }; + + var step = (function () { + var stepping = false; + + var completeLoop = function () { + if (onEndListener !== null) + onEndListener(gif); + iterationCount++; + + if (overrideLoopMode !== false || iterationCount < 0) { + doStep(); + } else { + stepping = false; + playing = false; + } + }; + + var doStep = function () { + stepping = playing; + if (!stepping) return; + + stepFrame(1); + var delay = frames[i].delay * 10; + if (!delay) delay = 100; // FIXME: Should this even default at all? What should it be? + + var nextFrameNo = getNextFrameNo(); + if (nextFrameNo === 0) { + delay += loopDelay; + setTimeout(completeLoop, delay); + } else { + setTimeout(doStep, delay); + } + }; + + return function () { + if (!stepping) setTimeout(doStep, 0); + }; + }()); + + var putFrame = function () { + var offset; + i = parseInt(i, 10); + + if (i > frames.length - 1){ + i = 0; + } + + if (i < 0){ + i = 0; + } + + offset = frameOffsets[i]; + + tmpCanvas.getContext("2d").putImageData(frames[i].data, offset.x, offset.y); + ctx.globalCompositeOperation = "copy"; + ctx.drawImage(tmpCanvas, 0, 0); + }; + + var play = function () { + playing = true; + step(); + }; + + var pause = function () { + playing = false; + }; + + + return { + init: function () { + if (loadError) return; + + if ( ! (options.c_w && options.c_h) ) { + ctx.scale(get_canvas_scale(),get_canvas_scale()); + } + + if (options.auto_play) { + step(); + } + else { + i = 0; + putFrame(); + } + }, + step: step, + play: play, + pause: pause, + playing: playing, + move_relative: stepFrame, + current_frame: function() { return i; }, + length: function() { return frames.length }, + move_to: function ( frame_idx ) { + i = frame_idx; + putFrame(); + } + } + }()); + + var doDecodeProgress = function (draw) { + doShowProgress(stream.pos, stream.data.length, draw); + }; + + var doNothing = function () {}; + /** + * @param{boolean=} draw Whether to draw progress bar or not; this is not idempotent because of translucency. + * Note that this means that the text will be unsynchronized with the progress bar on non-frames; + * but those are typically so small (GCE etc.) that it doesn't really matter. TODO: Do this properly. + */ + var withProgress = function (fn, draw) { + return function (block) { + fn(block); + doDecodeProgress(draw); + }; + }; + + + var handler = { + hdr: withProgress(doHdr), + gce: withProgress(doGCE), + com: withProgress(doNothing), + // I guess that's all for now. + app: { + // TODO: Is there much point in actually supporting iterations? + NETSCAPE: withProgress(doNothing) + }, + img: withProgress(doImg, true), + eof: function (block) { + //toolbar.style.display = ''; + pushFrame(); + doDecodeProgress(false); + if ( ! (options.c_w && options.c_h) ) { + canvas.width = hdr.width * get_canvas_scale(); + canvas.height = hdr.height * get_canvas_scale(); + } + player.init(); + loading = false; + if (load_callback) { + load_callback(gif); + } + + } + }; + + var init = function () { + var parent = gif.parentNode; + + var div = document.createElement('div'); + canvas = document.createElement('canvas'); + ctx = canvas.getContext('2d'); + toolbar = document.createElement('div'); + + tmpCanvas = document.createElement('canvas'); + + div.width = canvas.width = gif.width; + div.height = canvas.height = gif.height; + toolbar.style.minWidth = gif.width + 'px'; + + div.className = 'jsgif'; + toolbar.className = 'jsgif_toolbar'; + div.appendChild(canvas); + div.appendChild(toolbar); + + parent.insertBefore(div, gif); + parent.removeChild(gif); + + if (options.c_w && options.c_h) setSizes(options.c_w, options.c_h); + initialized=true; + }; + + var get_canvas_scale = function() { + var scale; + if (options.max_width && hdr && hdr.width > options.max_width) { + scale = options.max_width / hdr.width; + } + else { + scale = 1; + } + return scale; + } + + var canvas, ctx, toolbar, tmpCanvas; + var initialized = false; + var load_callback = false; + + var load_setup = function(callback) { + if (loading) return false; + if (callback) load_callback = callback; + else load_callback = false; + + loading = true; + frames = []; + clear(); + disposalRestoreFromIdx = null; + lastDisposalMethod = null; + frame = null; + lastImg = null; + + return true; + } + + return { + // play controls + play: player.play, + pause: player.pause, + move_relative: player.move_relative, + move_to: player.move_to, + + // getters for instance vars + get_playing : function() { return playing }, + get_canvas : function() { return canvas }, + get_canvas_scale : function() { return get_canvas_scale() }, + get_loading : function() { return loading }, + get_auto_play : function() { return options.auto_play }, + get_length : function() { return player.length() }, + get_current_frame: function() { return player.current_frame() }, + load_url: function(src,callback){ + if (!load_setup(callback)) return; + + var h = new XMLHttpRequest(); + // new browsers (XMLHttpRequest2-compliant) + h.open('GET', src, true); + + if ('overrideMimeType' in h) { + h.overrideMimeType('text/plain; charset=x-user-defined'); + } + + // old browsers (XMLHttpRequest-compliant) + else if ('responseType' in h) { + h.responseType = 'arraybuffer'; + } + + // IE9 (Microsoft.XMLHTTP-compliant) + else { + h.setRequestHeader('Accept-Charset', 'x-user-defined'); + } + + h.onloadstart = function() { + // Wait until connection is opened to replace the gif element with a canvas to avoid a blank img + if (!initialized) init(); + }; + h.onload = function(e) { + if (this.status != 200) { + doLoadError('xhr - response'); + } + // emulating response field for IE9 + if (!('response' in this)) { + this.response = new VBArray(this.responseText).toArray().map(String.fromCharCode).join(''); + } + var data = this.response; + if (data.toString().indexOf("ArrayBuffer") > 0) { + data = new Uint8Array(data); + } + + stream = new Stream(data); + setTimeout(doParse, 0); + }; + h.onprogress = function (e) { + if (e.lengthComputable) doShowProgress(e.loaded, e.total, true); + }; + h.onerror = function() { doLoadError('xhr'); }; + h.send(); + }, + load: function (callback) { + this.load_url(gif.getAttribute('rel:animated_src') || gif.src,callback); + }, + load_raw: function(arr, callback) { + if (!load_setup(callback)) return; + if (!initialized) init(); + stream = new Stream(arr); + setTimeout(doParse, 0); + }, + set_frame_offset: setFrameOffset + }; + }; + + return SuperGif; +})); + + From fb5823fcbb8dc89c34f1c89531c7885d08ac6ad6 Mon Sep 17 00:00:00 2001 From: sirx Date: Sun, 6 Nov 2016 03:21:36 +0100 Subject: [PATCH 2/7] s vergessen --- s/index.tpl.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/s/index.tpl.html b/s/index.tpl.html index 29ee593..b6db49b 100644 --- a/s/index.tpl.html +++ b/s/index.tpl.html @@ -16,6 +16,6 @@ - + From 74b6c992952ed2a971cae7c015a39e3f7b1ea01d Mon Sep 17 00:00:00 2001 From: sirx Date: Sun, 6 Nov 2016 03:40:02 +0100 Subject: [PATCH 3/7] =?UTF-8?q?animiert=20icons=20sehen=20in=20chromium=20?= =?UTF-8?q?schei=C3=9Fe=20aus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- s/anim.js | 26 -- s/index.tpl.html | 2 - s/libgif.js | 990 ----------------------------------------------- 3 files changed, 1018 deletions(-) delete mode 100644 s/anim.js delete mode 100644 s/libgif.js diff --git a/s/anim.js b/s/anim.js deleted file mode 100644 index 691a671..0000000 --- a/s/anim.js +++ /dev/null @@ -1,26 +0,0 @@ -var $parent = document.createElement("div") - $gif = document.createElement("img") - ,$favicon = document.createElement("link") - -$gif.crossOrigin = "anonymous" - -$gif.src = "./s/favicon.gif" - -$favicon.rel = "icon" - -window.parent.document.head.appendChild($favicon) - -$parent.appendChild($gif) - -var supergif = new SuperGif({gif: $gif}) - ,$canvas - -supergif.load(()=> { - $canvas = supergif.get_canvas() - updateFavicon() -}) - -function updateFavicon() { - $favicon.href = $canvas.toDataURL() - window.requestAnimationFrame(updateFavicon) -} diff --git a/s/index.tpl.html b/s/index.tpl.html index b6db49b..4f4ac55 100644 --- a/s/index.tpl.html +++ b/s/index.tpl.html @@ -15,7 +15,5 @@ - - diff --git a/s/libgif.js b/s/libgif.js deleted file mode 100644 index b14e0e9..0000000 --- a/s/libgif.js +++ /dev/null @@ -1,990 +0,0 @@ -/* - SuperGif - - Example usage: - - - - - - Image tag attributes: - - rel:animated_src - If this url is specified, it's loaded into the player instead of src. - This allows a preview frame to be shown until animated gif data is streamed into the canvas - - rel:auto_play - Defaults to 1 if not specified. If set to zero, a call to the play() method is needed - - Constructor options args - - gif Required. The DOM element of an img tag. - loop_mode Optional. Setting this to false will force disable looping of the gif. - auto_play Optional. Same as the rel:auto_play attribute above, this arg overrides the img tag info. - max_width Optional. Scale images over max_width down to max_width. Helpful with mobile. - on_end Optional. Add a callback for when the gif reaches the end of a single loop (one iteration). The first argument passed will be the gif HTMLElement. - loop_delay Optional. The amount of time to pause (in ms) after each single loop (iteration). - draw_while_loading Optional. Determines whether the gif will be drawn to the canvas whilst it is loaded. - show_progress_bar Optional. Only applies when draw_while_loading is set to true. - - Instance methods - - // loading - load( callback ) Loads the gif specified by the src or rel:animated_src sttributie of the img tag into a canvas element and then calls callback if one is passed - load_url( src, callback ) Loads the gif file specified in the src argument into a canvas element and then calls callback if one is passed - - // play controls - play - Start playing the gif - pause - Stop playing the gif - move_to(i) - Move to frame i of the gif - move_relative(i) - Move i frames ahead (or behind if i < 0) - - // getters - get_canvas The canvas element that the gif is playing in. Handy for assigning event handlers to. - get_playing Whether or not the gif is currently playing - get_loading Whether or not the gif has finished loading/parsing - get_auto_play Whether or not the gif is set to play automatically - get_length The number of frames in the gif - get_current_frame The index of the currently displayed frame of the gif - - For additional customization (viewport inside iframe) these params may be passed: - c_w, c_h - width and height of canvas - vp_t, vp_l, vp_ w, vp_h - top, left, width and height of the viewport - - A bonus: few articles to understand what is going on - http://enthusiasms.org/post/16976438906 - http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp - http://humpy77.deviantart.com/journal/Frame-Delay-Times-for-Animated-GIFs-214150546 - -*/ -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - define([], factory); - } else if (typeof exports === 'object') { - module.exports = factory(); - } else { - root.SuperGif = factory(); - } -}(this, function () { - // Generic functions - var bitsToNum = function (ba) { - return ba.reduce(function (s, n) { - return s * 2 + n; - }, 0); - }; - - var byteToBitArr = function (bite) { - var a = []; - for (var i = 7; i >= 0; i--) { - a.push( !! (bite & (1 << i))); - } - return a; - }; - - // Stream - /** - * @constructor - */ - // Make compiler happy. - var Stream = function (data) { - this.data = data; - this.len = this.data.length; - this.pos = 0; - - this.readByte = function () { - if (this.pos >= this.data.length) { - throw new Error('Attempted to read past end of stream.'); - } - if (data instanceof Uint8Array) - return data[this.pos++]; - else - return data.charCodeAt(this.pos++) & 0xFF; - }; - - this.readBytes = function (n) { - var bytes = []; - for (var i = 0; i < n; i++) { - bytes.push(this.readByte()); - } - return bytes; - }; - - this.read = function (n) { - var s = ''; - for (var i = 0; i < n; i++) { - s += String.fromCharCode(this.readByte()); - } - return s; - }; - - this.readUnsigned = function () { // Little-endian. - var a = this.readBytes(2); - return (a[1] << 8) + a[0]; - }; - }; - - var lzwDecode = function (minCodeSize, data) { - // TODO: Now that the GIF parser is a bit different, maybe this should get an array of bytes instead of a String? - var pos = 0; // Maybe this streaming thing should be merged with the Stream? - var readCode = function (size) { - var code = 0; - for (var i = 0; i < size; i++) { - if (data.charCodeAt(pos >> 3) & (1 << (pos & 7))) { - code |= 1 << i; - } - pos++; - } - return code; - }; - - var output = []; - - var clearCode = 1 << minCodeSize; - var eoiCode = clearCode + 1; - - var codeSize = minCodeSize + 1; - - var dict = []; - - var clear = function () { - dict = []; - codeSize = minCodeSize + 1; - for (var i = 0; i < clearCode; i++) { - dict[i] = [i]; - } - dict[clearCode] = []; - dict[eoiCode] = null; - - }; - - var code; - var last; - - while (true) { - last = code; - code = readCode(codeSize); - - if (code === clearCode) { - clear(); - continue; - } - if (code === eoiCode) break; - - if (code < dict.length) { - if (last !== clearCode) { - dict.push(dict[last].concat(dict[code][0])); - } - } - else { - if (code !== dict.length) throw new Error('Invalid LZW code.'); - dict.push(dict[last].concat(dict[last][0])); - } - output.push.apply(output, dict[code]); - - if (dict.length === (1 << codeSize) && codeSize < 12) { - // If we're at the last code and codeSize is 12, the next code will be a clearCode, and it'll be 12 bits long. - codeSize++; - } - } - - // I don't know if this is technically an error, but some GIFs do it. - //if (Math.ceil(pos / 8) !== data.length) throw new Error('Extraneous LZW bytes.'); - return output; - }; - - - // The actual parsing; returns an object with properties. - var parseGIF = function (st, handler) { - handler || (handler = {}); - - // LZW (GIF-specific) - var parseCT = function (entries) { // Each entry is 3 bytes, for RGB. - var ct = []; - for (var i = 0; i < entries; i++) { - ct.push(st.readBytes(3)); - } - return ct; - }; - - var readSubBlocks = function () { - var size, data; - data = ''; - do { - size = st.readByte(); - data += st.read(size); - } while (size !== 0); - return data; - }; - - var parseHeader = function () { - var hdr = {}; - hdr.sig = st.read(3); - hdr.ver = st.read(3); - if (hdr.sig !== 'GIF') throw new Error('Not a GIF file.'); // XXX: This should probably be handled more nicely. - hdr.width = st.readUnsigned(); - hdr.height = st.readUnsigned(); - - var bits = byteToBitArr(st.readByte()); - hdr.gctFlag = bits.shift(); - hdr.colorRes = bitsToNum(bits.splice(0, 3)); - hdr.sorted = bits.shift(); - hdr.gctSize = bitsToNum(bits.splice(0, 3)); - - hdr.bgColor = st.readByte(); - hdr.pixelAspectRatio = st.readByte(); // if not 0, aspectRatio = (pixelAspectRatio + 15) / 64 - if (hdr.gctFlag) { - hdr.gct = parseCT(1 << (hdr.gctSize + 1)); - } - handler.hdr && handler.hdr(hdr); - }; - - var parseExt = function (block) { - var parseGCExt = function (block) { - var blockSize = st.readByte(); // Always 4 - var bits = byteToBitArr(st.readByte()); - block.reserved = bits.splice(0, 3); // Reserved; should be 000. - block.disposalMethod = bitsToNum(bits.splice(0, 3)); - block.userInput = bits.shift(); - block.transparencyGiven = bits.shift(); - - block.delayTime = st.readUnsigned(); - - block.transparencyIndex = st.readByte(); - - block.terminator = st.readByte(); - - handler.gce && handler.gce(block); - }; - - var parseComExt = function (block) { - block.comment = readSubBlocks(); - handler.com && handler.com(block); - }; - - var parsePTExt = function (block) { - // No one *ever* uses this. If you use it, deal with parsing it yourself. - var blockSize = st.readByte(); // Always 12 - block.ptHeader = st.readBytes(12); - block.ptData = readSubBlocks(); - handler.pte && handler.pte(block); - }; - - var parseAppExt = function (block) { - var parseNetscapeExt = function (block) { - var blockSize = st.readByte(); // Always 3 - block.unknown = st.readByte(); // ??? Always 1? What is this? - block.iterations = st.readUnsigned(); - block.terminator = st.readByte(); - handler.app && handler.app.NETSCAPE && handler.app.NETSCAPE(block); - }; - - var parseUnknownAppExt = function (block) { - block.appData = readSubBlocks(); - // FIXME: This won't work if a handler wants to match on any identifier. - handler.app && handler.app[block.identifier] && handler.app[block.identifier](block); - }; - - var blockSize = st.readByte(); // Always 11 - block.identifier = st.read(8); - block.authCode = st.read(3); - switch (block.identifier) { - case 'NETSCAPE': - parseNetscapeExt(block); - break; - default: - parseUnknownAppExt(block); - break; - } - }; - - var parseUnknownExt = function (block) { - block.data = readSubBlocks(); - handler.unknown && handler.unknown(block); - }; - - block.label = st.readByte(); - switch (block.label) { - case 0xF9: - block.extType = 'gce'; - parseGCExt(block); - break; - case 0xFE: - block.extType = 'com'; - parseComExt(block); - break; - case 0x01: - block.extType = 'pte'; - parsePTExt(block); - break; - case 0xFF: - block.extType = 'app'; - parseAppExt(block); - break; - default: - block.extType = 'unknown'; - parseUnknownExt(block); - break; - } - }; - - var parseImg = function (img) { - var deinterlace = function (pixels, width) { - // Of course this defeats the purpose of interlacing. And it's *probably* - // the least efficient way it's ever been implemented. But nevertheless... - var newPixels = new Array(pixels.length); - var rows = pixels.length / width; - var cpRow = function (toRow, fromRow) { - var fromPixels = pixels.slice(fromRow * width, (fromRow + 1) * width); - newPixels.splice.apply(newPixels, [toRow * width, width].concat(fromPixels)); - }; - - // See appendix E. - var offsets = [0, 4, 2, 1]; - var steps = [8, 8, 4, 2]; - - var fromRow = 0; - for (var pass = 0; pass < 4; pass++) { - for (var toRow = offsets[pass]; toRow < rows; toRow += steps[pass]) { - cpRow(toRow, fromRow) - fromRow++; - } - } - - return newPixels; - }; - - img.leftPos = st.readUnsigned(); - img.topPos = st.readUnsigned(); - img.width = st.readUnsigned(); - img.height = st.readUnsigned(); - - var bits = byteToBitArr(st.readByte()); - img.lctFlag = bits.shift(); - img.interlaced = bits.shift(); - img.sorted = bits.shift(); - img.reserved = bits.splice(0, 2); - img.lctSize = bitsToNum(bits.splice(0, 3)); - - if (img.lctFlag) { - img.lct = parseCT(1 << (img.lctSize + 1)); - } - - img.lzwMinCodeSize = st.readByte(); - - var lzwData = readSubBlocks(); - - img.pixels = lzwDecode(img.lzwMinCodeSize, lzwData); - - if (img.interlaced) { // Move - img.pixels = deinterlace(img.pixels, img.width); - } - - handler.img && handler.img(img); - }; - - var parseBlock = function () { - var block = {}; - block.sentinel = st.readByte(); - - switch (String.fromCharCode(block.sentinel)) { // For ease of matching - case '!': - block.type = 'ext'; - parseExt(block); - break; - case ',': - block.type = 'img'; - parseImg(block); - break; - case ';': - block.type = 'eof'; - handler.eof && handler.eof(block); - break; - default: - throw new Error('Unknown block: 0x' + block.sentinel.toString(16)); // TODO: Pad this with a 0. - } - - if (block.type !== 'eof') setTimeout(parseBlock, 0); - }; - - var parse = function () { - parseHeader(); - setTimeout(parseBlock, 0); - }; - - parse(); - }; - - var SuperGif = function ( opts ) { - var options = { - //viewport position - vp_l: 0, - vp_t: 0, - vp_w: null, - vp_h: null, - //canvas sizes - c_w: null, - c_h: null - }; - for (var i in opts ) { options[i] = opts[i] } - if (options.vp_w && options.vp_h) options.is_vp = true; - - var stream; - var hdr; - - var loadError = null; - var loading = false; - - var transparency = null; - var delay = null; - var disposalMethod = null; - var disposalRestoreFromIdx = null; - var lastDisposalMethod = null; - var frame = null; - var lastImg = null; - - var playing = true; - var forward = true; - - var ctx_scaled = false; - - var frames = []; - var frameOffsets = []; // elements have .x and .y properties - - var gif = options.gif; - if (typeof options.auto_play == 'undefined') - options.auto_play = (!gif.getAttribute('rel:auto_play') || gif.getAttribute('rel:auto_play') == '1'); - - var onEndListener = (options.hasOwnProperty('on_end') ? options.on_end : null); - var loopDelay = (options.hasOwnProperty('loop_delay') ? options.loop_delay : 0); - var overrideLoopMode = (options.hasOwnProperty('loop_mode') ? options.loop_mode : 'auto'); - var drawWhileLoading = (options.hasOwnProperty('draw_while_loading') ? options.draw_while_loading : true); - var showProgressBar = drawWhileLoading ? (options.hasOwnProperty('show_progress_bar') ? options.show_progress_bar : true) : false; - var progressBarHeight = (options.hasOwnProperty('progressbar_height') ? options.progressbar_height : 25); - var progressBarBackgroundColor = (options.hasOwnProperty('progressbar_background_color') ? options.progressbar_background_color : 'rgba(255,255,255,0.4)'); - var progressBarForegroundColor = (options.hasOwnProperty('progressbar_foreground_color') ? options.progressbar_foreground_color : 'rgba(255,0,22,.8)'); - - var clear = function () { - transparency = null; - delay = null; - lastDisposalMethod = disposalMethod; - disposalMethod = null; - frame = null; - }; - - // XXX: There's probably a better way to handle catching exceptions when - // callbacks are involved. - var doParse = function () { - try { - parseGIF(stream, handler); - } - catch (err) { - doLoadError('parse'); - } - }; - - var doText = function (text) { - toolbar.innerHTML = text; // innerText? Escaping? Whatever. - toolbar.style.visibility = 'visible'; - }; - - var setSizes = function(w, h) { - canvas.width = w * get_canvas_scale(); - canvas.height = h * get_canvas_scale(); - toolbar.style.minWidth = ( w * get_canvas_scale() ) + 'px'; - - tmpCanvas.width = w; - tmpCanvas.height = h; - tmpCanvas.style.width = w + 'px'; - tmpCanvas.style.height = h + 'px'; - tmpCanvas.getContext('2d').setTransform(1, 0, 0, 1, 0, 0); - }; - - var setFrameOffset = function(frame, offset) { - if (!frameOffsets[frame]) { - frameOffsets[frame] = offset; - return; - } - if (typeof offset.x !== 'undefined') { - frameOffsets[frame].x = offset.x; - } - if (typeof offset.y !== 'undefined') { - frameOffsets[frame].y = offset.y; - } - }; - - var doShowProgress = function (pos, length, draw) { - if (draw && showProgressBar) { - var height = progressBarHeight; - var left, mid, top, width; - if (options.is_vp) { - if (!ctx_scaled) { - top = (options.vp_t + options.vp_h - height); - height = height; - left = options.vp_l; - mid = left + (pos / length) * options.vp_w; - width = canvas.width; - } else { - top = (options.vp_t + options.vp_h - height) / get_canvas_scale(); - height = height / get_canvas_scale(); - left = (options.vp_l / get_canvas_scale() ); - mid = left + (pos / length) * (options.vp_w / get_canvas_scale()); - width = canvas.width / get_canvas_scale(); - } - //some debugging, draw rect around viewport - if (false) { - if (!ctx_scaled) { - var l = options.vp_l, t = options.vp_t; - var w = options.vp_w, h = options.vp_h; - } else { - var l = options.vp_l/get_canvas_scale(), t = options.vp_t/get_canvas_scale(); - var w = options.vp_w/get_canvas_scale(), h = options.vp_h/get_canvas_scale(); - } - ctx.rect(l,t,w,h); - ctx.stroke(); - } - } - else { - top = (canvas.height - height) / (ctx_scaled ? get_canvas_scale() : 1); - mid = ((pos / length) * canvas.width) / (ctx_scaled ? get_canvas_scale() : 1); - width = canvas.width / (ctx_scaled ? get_canvas_scale() : 1 ); - height /= ctx_scaled ? get_canvas_scale() : 1; - } - - ctx.fillStyle = progressBarBackgroundColor; - ctx.fillRect(mid, top, width - mid, height); - - ctx.fillStyle = progressBarForegroundColor; - ctx.fillRect(0, top, mid, height); - } - }; - - var doLoadError = function (originOfError) { - var drawError = function () { - ctx.fillStyle = 'black'; - ctx.fillRect(0, 0, options.c_w ? options.c_w : hdr.width, options.c_h ? options.c_h : hdr.height); - ctx.strokeStyle = 'red'; - ctx.lineWidth = 3; - ctx.moveTo(0, 0); - ctx.lineTo(options.c_w ? options.c_w : hdr.width, options.c_h ? options.c_h : hdr.height); - ctx.moveTo(0, options.c_h ? options.c_h : hdr.height); - ctx.lineTo(options.c_w ? options.c_w : hdr.width, 0); - ctx.stroke(); - }; - - loadError = originOfError; - hdr = { - width: gif.width, - height: gif.height - }; // Fake header. - frames = []; - drawError(); - }; - - var doHdr = function (_hdr) { - hdr = _hdr; - setSizes(hdr.width, hdr.height) - }; - - var doGCE = function (gce) { - pushFrame(); - clear(); - transparency = gce.transparencyGiven ? gce.transparencyIndex : null; - delay = gce.delayTime; - disposalMethod = gce.disposalMethod; - // We don't have much to do with the rest of GCE. - }; - - var pushFrame = function () { - if (!frame) return; - frames.push({ - data: frame.getImageData(0, 0, hdr.width, hdr.height), - delay: delay - }); - frameOffsets.push({ x: 0, y: 0 }); - }; - - var doImg = function (img) { - if (!frame) frame = tmpCanvas.getContext('2d'); - - var currIdx = frames.length; - - //ct = color table, gct = global color table - var ct = img.lctFlag ? img.lct : hdr.gct; // TODO: What if neither exists? - - /* - Disposal method indicates the way in which the graphic is to - be treated after being displayed. - - Values : 0 - No disposal specified. The decoder is - not required to take any action. - 1 - Do not dispose. The graphic is to be left - in place. - 2 - Restore to background color. The area used by the - graphic must be restored to the background color. - 3 - Restore to previous. The decoder is required to - restore the area overwritten by the graphic with - what was there prior to rendering the graphic. - - Importantly, "previous" means the frame state - after the last disposal of method 0, 1, or 2. - */ - if (currIdx > 0) { - if (lastDisposalMethod === 3) { - // Restore to previous - // If we disposed every frame including first frame up to this point, then we have - // no composited frame to restore to. In this case, restore to background instead. - if (disposalRestoreFromIdx !== null) { - frame.putImageData(frames[disposalRestoreFromIdx].data, 0, 0); - } else { - frame.clearRect(lastImg.leftPos, lastImg.topPos, lastImg.width, lastImg.height); - } - } else { - disposalRestoreFromIdx = currIdx - 1; - } - - if (lastDisposalMethod === 2) { - // Restore to background color - // Browser implementations historically restore to transparent; we do the same. - // http://www.wizards-toolkit.org/discourse-server/viewtopic.php?f=1&t=21172#p86079 - frame.clearRect(lastImg.leftPos, lastImg.topPos, lastImg.width, lastImg.height); - } - } - // else, Undefined/Do not dispose. - // frame contains final pixel data from the last frame; do nothing - - //Get existing pixels for img region after applying disposal method - var imgData = frame.getImageData(img.leftPos, img.topPos, img.width, img.height); - - //apply color table colors - img.pixels.forEach(function (pixel, i) { - // imgData.data === [R,G,B,A,R,G,B,A,...] - if (pixel !== transparency) { - imgData.data[i * 4 + 0] = ct[pixel][0]; - imgData.data[i * 4 + 1] = ct[pixel][1]; - imgData.data[i * 4 + 2] = ct[pixel][2]; - imgData.data[i * 4 + 3] = 255; // Opaque. - } - }); - - frame.putImageData(imgData, img.leftPos, img.topPos); - - if (!ctx_scaled) { - ctx.scale(get_canvas_scale(),get_canvas_scale()); - ctx_scaled = true; - } - - // We could use the on-page canvas directly, except that we draw a progress - // bar for each image chunk (not just the final image). - if (drawWhileLoading) { - ctx.drawImage(tmpCanvas, 0, 0); - drawWhileLoading = options.auto_play; - } - - lastImg = img; - }; - - var player = (function () { - var i = -1; - var iterationCount = 0; - - var showingInfo = false; - var pinned = false; - - /** - * Gets the index of the frame "up next". - * @returns {number} - */ - var getNextFrameNo = function () { - var delta = (forward ? 1 : -1); - return (i + delta + frames.length) % frames.length; - }; - - var stepFrame = function (amount) { // XXX: Name is confusing. - i = i + amount; - - putFrame(); - }; - - var step = (function () { - var stepping = false; - - var completeLoop = function () { - if (onEndListener !== null) - onEndListener(gif); - iterationCount++; - - if (overrideLoopMode !== false || iterationCount < 0) { - doStep(); - } else { - stepping = false; - playing = false; - } - }; - - var doStep = function () { - stepping = playing; - if (!stepping) return; - - stepFrame(1); - var delay = frames[i].delay * 10; - if (!delay) delay = 100; // FIXME: Should this even default at all? What should it be? - - var nextFrameNo = getNextFrameNo(); - if (nextFrameNo === 0) { - delay += loopDelay; - setTimeout(completeLoop, delay); - } else { - setTimeout(doStep, delay); - } - }; - - return function () { - if (!stepping) setTimeout(doStep, 0); - }; - }()); - - var putFrame = function () { - var offset; - i = parseInt(i, 10); - - if (i > frames.length - 1){ - i = 0; - } - - if (i < 0){ - i = 0; - } - - offset = frameOffsets[i]; - - tmpCanvas.getContext("2d").putImageData(frames[i].data, offset.x, offset.y); - ctx.globalCompositeOperation = "copy"; - ctx.drawImage(tmpCanvas, 0, 0); - }; - - var play = function () { - playing = true; - step(); - }; - - var pause = function () { - playing = false; - }; - - - return { - init: function () { - if (loadError) return; - - if ( ! (options.c_w && options.c_h) ) { - ctx.scale(get_canvas_scale(),get_canvas_scale()); - } - - if (options.auto_play) { - step(); - } - else { - i = 0; - putFrame(); - } - }, - step: step, - play: play, - pause: pause, - playing: playing, - move_relative: stepFrame, - current_frame: function() { return i; }, - length: function() { return frames.length }, - move_to: function ( frame_idx ) { - i = frame_idx; - putFrame(); - } - } - }()); - - var doDecodeProgress = function (draw) { - doShowProgress(stream.pos, stream.data.length, draw); - }; - - var doNothing = function () {}; - /** - * @param{boolean=} draw Whether to draw progress bar or not; this is not idempotent because of translucency. - * Note that this means that the text will be unsynchronized with the progress bar on non-frames; - * but those are typically so small (GCE etc.) that it doesn't really matter. TODO: Do this properly. - */ - var withProgress = function (fn, draw) { - return function (block) { - fn(block); - doDecodeProgress(draw); - }; - }; - - - var handler = { - hdr: withProgress(doHdr), - gce: withProgress(doGCE), - com: withProgress(doNothing), - // I guess that's all for now. - app: { - // TODO: Is there much point in actually supporting iterations? - NETSCAPE: withProgress(doNothing) - }, - img: withProgress(doImg, true), - eof: function (block) { - //toolbar.style.display = ''; - pushFrame(); - doDecodeProgress(false); - if ( ! (options.c_w && options.c_h) ) { - canvas.width = hdr.width * get_canvas_scale(); - canvas.height = hdr.height * get_canvas_scale(); - } - player.init(); - loading = false; - if (load_callback) { - load_callback(gif); - } - - } - }; - - var init = function () { - var parent = gif.parentNode; - - var div = document.createElement('div'); - canvas = document.createElement('canvas'); - ctx = canvas.getContext('2d'); - toolbar = document.createElement('div'); - - tmpCanvas = document.createElement('canvas'); - - div.width = canvas.width = gif.width; - div.height = canvas.height = gif.height; - toolbar.style.minWidth = gif.width + 'px'; - - div.className = 'jsgif'; - toolbar.className = 'jsgif_toolbar'; - div.appendChild(canvas); - div.appendChild(toolbar); - - parent.insertBefore(div, gif); - parent.removeChild(gif); - - if (options.c_w && options.c_h) setSizes(options.c_w, options.c_h); - initialized=true; - }; - - var get_canvas_scale = function() { - var scale; - if (options.max_width && hdr && hdr.width > options.max_width) { - scale = options.max_width / hdr.width; - } - else { - scale = 1; - } - return scale; - } - - var canvas, ctx, toolbar, tmpCanvas; - var initialized = false; - var load_callback = false; - - var load_setup = function(callback) { - if (loading) return false; - if (callback) load_callback = callback; - else load_callback = false; - - loading = true; - frames = []; - clear(); - disposalRestoreFromIdx = null; - lastDisposalMethod = null; - frame = null; - lastImg = null; - - return true; - } - - return { - // play controls - play: player.play, - pause: player.pause, - move_relative: player.move_relative, - move_to: player.move_to, - - // getters for instance vars - get_playing : function() { return playing }, - get_canvas : function() { return canvas }, - get_canvas_scale : function() { return get_canvas_scale() }, - get_loading : function() { return loading }, - get_auto_play : function() { return options.auto_play }, - get_length : function() { return player.length() }, - get_current_frame: function() { return player.current_frame() }, - load_url: function(src,callback){ - if (!load_setup(callback)) return; - - var h = new XMLHttpRequest(); - // new browsers (XMLHttpRequest2-compliant) - h.open('GET', src, true); - - if ('overrideMimeType' in h) { - h.overrideMimeType('text/plain; charset=x-user-defined'); - } - - // old browsers (XMLHttpRequest-compliant) - else if ('responseType' in h) { - h.responseType = 'arraybuffer'; - } - - // IE9 (Microsoft.XMLHTTP-compliant) - else { - h.setRequestHeader('Accept-Charset', 'x-user-defined'); - } - - h.onloadstart = function() { - // Wait until connection is opened to replace the gif element with a canvas to avoid a blank img - if (!initialized) init(); - }; - h.onload = function(e) { - if (this.status != 200) { - doLoadError('xhr - response'); - } - // emulating response field for IE9 - if (!('response' in this)) { - this.response = new VBArray(this.responseText).toArray().map(String.fromCharCode).join(''); - } - var data = this.response; - if (data.toString().indexOf("ArrayBuffer") > 0) { - data = new Uint8Array(data); - } - - stream = new Stream(data); - setTimeout(doParse, 0); - }; - h.onprogress = function (e) { - if (e.lengthComputable) doShowProgress(e.loaded, e.total, true); - }; - h.onerror = function() { doLoadError('xhr'); }; - h.send(); - }, - load: function (callback) { - this.load_url(gif.getAttribute('rel:animated_src') || gif.src,callback); - }, - load_raw: function(arr, callback) { - if (!load_setup(callback)) return; - if (!initialized) init(); - stream = new Stream(arr); - setTimeout(doParse, 0); - }, - set_frame_offset: setFrameOffset - }; - }; - - return SuperGif; -})); - - From 948fd4e6478f1b0338968bae0cedda65a6613fcc Mon Sep 17 00:00:00 2001 From: Flummi Date: Mon, 7 Nov 2016 02:02:02 +0100 Subject: [PATCH 4/7] new configsystem --- src/lib.js | 84 ++++++++++++++++++++++++++++++++----------- src/trigger/parser.js | 30 ++++++++-------- src/websrv.js | 14 ++++---- 3 files changed, 85 insertions(+), 43 deletions(-) diff --git a/src/lib.js b/src/lib.js index a431d8a..dd92cea 100644 --- a/src/lib.js +++ b/src/lib.js @@ -5,25 +5,10 @@ var exec = require('child_process').exec; var crypto = require('crypto'); var request = require('request'); var mysql = require('mysql'); -var irccfg = require('../cfg/irc.json'); +//var irccfg = require('../cfg/irc.json'); var self = Lib.prototype; self.bot = require('coffea')(); -irccfg.forEach(e => { - self.bot.add({ - "name": e.name, - "host": e.host, - "port": e.port, - "ssl": e.ssl, - "ssl_allow_invalid": e.ssl_allow_invalid, - "pass": e.pass, - "nick": e.nick, - "username": e.username, - "realname": e.realname, - "throttling": e.throttling - }); - console.log("Server "+e.name+" wurde geladen"); -}); self.admins = []; self.debug = false; @@ -53,18 +38,28 @@ haDC(); module.exports = Lib; function Lib() { - self.cfg = require('../cfg/main.json'); - self.debug = self.cfg.debug; + //self.cfg = require('../cfg/main.json'); + self.cfg = {}; + self.getcfg((cfg) => { + self.cfg = cfg; + self.debug = self.cfg.main.debug; + self.loadIRC(); + self.loadUser((cb) => console.log((cb)?"Admins wurden geladen":"Admins konnten nicht geladen werden")); + self.loadTrigger(); + self.loadEvents(); + }); + + /*self.debug = self.cfg.debug; self.loadUser((cb) => console.log((cb)?"Admins wurden geladen":"Admins konnten nicht geladen werden")); self.loadTrigger(); - self.loadEvents(); + self.loadEvents();*/ } self.getUserlevel = (user, network, channel, cb) => { self.bot.whois(user, network, (err, data) => { self.bot.names(channel, network, (names) => { - var lvl_channel = (data.nick in names)? self.cfg.level[ names[data.nick] ] : 0; + var lvl_channel = (data.nick in names)? self.cfg.main.level[ names[data.nick] ] : 0; var lvl_db = 0; if(data.account in self.admins) lvl_db = (self.admins[data.account].network === network)? self.admins[data.account].level : 0; @@ -161,7 +156,7 @@ self.generateThumbs = () => { }; self.log = (msg) => { if(self.debug) - self.bot.send(self.cfg.debugchannel, msg, 'n0xy'); + self.bot.send(self.cfg.main.debugchannel, msg, 'n0xy'); }; self.loadTrigger = () => { var files = fs.readdirSync(__dirname+'/trigger/'); @@ -237,4 +232,51 @@ self.levelToModes = level => { if(level >= 10) return "v"; return ""; +}; + +self.getcfg = (cb) => { + var out = { + irc: {}, + main: {}, + websrv: {} + }; + self.sql.query("select * from `f0ck`.`cfg`", (err, rows) => { + rows.forEach(e => { + out[e.class][e.key] = ((type, value) => { + switch(type) { + case 'string': + return value; + break; + case 'int': + return parseInt(value); + break; + case 'bool': + return (value === 'true')?true:false; + break; + case 'json': + return JSON.parse(value); + break; + } + })(e.type, e.value); + }); + if(cb) cb(out); + }); +}; + +self.loadIRC = () => { + self.cfg.irc.forEach(e => { + self.bot.add({ + "name": e.name, + "host": e.host, + "port": e.port, + "ssl": e.ssl, + "ssl_allow_invalid": e.ssl_allow_invalid, + "pass": e.pass, + "nick": e.nick, + "username": e.username, + "realname": e.realname, + "throttling": e.throttling + }); + console.log("Server "+e.name+" wurde geladen"); + }); }; \ No newline at end of file diff --git a/src/trigger/parser.js b/src/trigger/parser.js index eef2b4d..3e323a2 100644 --- a/src/trigger/parser.js +++ b/src/trigger/parser.js @@ -7,7 +7,7 @@ var fileType = require('file-type'); var request = require('request'); var ytdl = require('ytdl-core'); var Readable = require('stream').Readable; -var cfg = require('../../cfg/main.json'); +//var cfg = require('../../cfg/main.json'); module.exports = (lib) => { lib.trigger.add({ @@ -40,7 +40,7 @@ module.exports = (lib) => { (cb.info.thumb !== null)?cb.info.thumb:'' ]).on('result', (result) => { lib.generateThumbs(); - e.reply(cfg.url+"/"+result.insertId+" - "+cb.info.title+" ("+cb.info.mime+", ~"+lib.formatSize(cb.size)+") from "+cbgu['nick']+" ("+cbgu['username']+"@"+cbgu['hostname']+")"); + e.reply(lib.cfg.main.url+"/"+result.insertId+" - "+cb.info.title+" ("+cb.info.mime+", ~"+lib.formatSize(cb.size)+") from "+cbgu['nick']+" ("+cbgu['username']+"@"+cbgu['hostname']+")"); }).on('error', (msg) => { e.reply(msg); }); @@ -82,10 +82,10 @@ module.exports = (lib) => { try { ytdl.downloadFromInfo(inf, { filter: (format) => { return format.container === 'webm'; } }) .on('response', (res) => { - if(res.headers['content-length'] > cfg.maxFileSize) { + if(res.headers['content-length'] > lib.cfg.main.maxFileSize) { res.destroy(); dat.end(); - cb({ success: false, file: tmpdest, msg: 'f0ck! your file is too big (~'+lib.formatSize(res.headers['content-length'])+'), max '+lib.formatSize(cfg.maxFileSize)+' allowed' }); + cb({ success: false, file: tmpdest, msg: 'f0ck! your file is too big (~'+lib.formatSize(res.headers['content-length'])+'), max '+lib.formatSize(lib.cfg.main.maxFileSize)+' allowed' }); } else { info = { @@ -111,10 +111,10 @@ module.exports = (lib) => { }); } else if(url.match(sc)) { // scdl - request('https://api.soundcloud.com/resolve.json?client_id=' + cfg.scclientid + '&url=' + url, (err, res, body) => { + request('https://api.soundcloud.com/resolve.json?client_id=' + lib.cfg.main.scclientid + '&url=' + url, (err, res, body) => { if(!err && res.statusCode === 200) { var data = JSON.parse(body); - request(data.stream_url + ((data.stream_url.indexOf('?') === -1)?'?':'&') + 'client_id=' + cfg.scclientid) + request(data.stream_url + ((data.stream_url.indexOf('?') === -1)?'?':'&') + 'client_id=' + lib.cfg.main.scclientid) .pipe(dat); info = { type: 'soundcloud', @@ -141,8 +141,8 @@ module.exports = (lib) => { var type = res.headers['content-type']; lib.log('MimeType: '+type); var length = res.headers['content-length']; - if(cfg.allowedMimes.hasOwnProperty(type)) { - if(data.length <= cfg.maxFileSize) { + if(lib.cfg.main.allowedMimes.hasOwnProperty(type)) { + if(data.length <= lib.cfg.main.maxFileSize) { var s = new Readable s.push(data); s.push(null); @@ -151,13 +151,13 @@ module.exports = (lib) => { type: 'other', title: path.parse(url).base, mime: type, - ext: cfg.allowedMimes[type], + ext: lib.cfg.main.allowedMimes[type], thumb: null }; } else { dat.end(); - cb({ success: false, file: tmpdest, msg: 'f0ck! your file is too big (~'+lib.formatSize(data.length)+'), max '+lib.formatSize(cfg.maxFileSize)+' allowed' }); + cb({ success: false, file: tmpdest, msg: 'f0ck! your file is too big (~'+lib.formatSize(data.length)+'), max '+lib.formatSize(lib.cfg.main.maxFileSize)+' allowed' }); } } else { @@ -176,8 +176,8 @@ module.exports = (lib) => { .on('finish', () => { var size = dat.bytesWritten; dat.end(); - if(size > cfg.maxFileSize) - cb({ success: false, file: tmpdest, msg: 'f0ck! your file is too big (~'+lib.formatSize(size)+'), max '+lib.formatSize(cfg.maxFileSize)+' allowed' }); + if(size > lib.cfg.main.maxFileSize) + cb({ success: false, file: tmpdest, msg: 'f0ck! your file is too big (~'+lib.formatSize(size)+'), max '+lib.formatSize(lib.cfg.main.maxFileSize)+' allowed' }); else { fs.stat('./b/' + tmpdest, (err, stat) => { if(!err && stat.isFile() && stat.size > 300) { @@ -186,13 +186,13 @@ module.exports = (lib) => { lib.checkRepostCheckSum(cbcs, (cbcrcs) => { if(cbcrcs === true) { var mime = fileType(readChunk.sync('./b/' + tmpdest, 0, 262)); - if(cfg.allowedMimes.hasOwnProperty(mime.mime) || info.type === 'soundcloud') + if(lib.cfg.main.allowedMimes.hasOwnProperty(mime.mime) || info.type === 'soundcloud') cb({ success: true, info: info, size: size, file: './b/' + tmpdest, checksum: cbcs }); else cb({ success: false, file: tmpdest, msg: 'lol, go f0ck yourself ('+mime+')' }); } else - cb({ success: false, file: tmpdest, msg: 'repost motherf0cker: '+cfg.url+'/'+cbcrcs }); + cb({ success: false, file: tmpdest, msg: 'repost motherf0cker: '+lib.cfg.main.url+'/'+cbcrcs }); }); }); } @@ -204,7 +204,7 @@ module.exports = (lib) => { }); } else - cb({ success: false, file: tmpdest, msg: 'repost motherf0cker: '+cfg.url+'/'+cbcr }); + cb({ success: false, file: tmpdest, msg: 'repost motherf0cker: '+lib.cfg.main.url+'/'+cbcr }); }); }; }; \ No newline at end of file diff --git a/src/websrv.js b/src/websrv.js index 82bba93..7f3b7e8 100644 --- a/src/websrv.js +++ b/src/websrv.js @@ -3,7 +3,7 @@ var http = require('http'); var path = require('path'); var swig = require('swig'); var urlm = require('url'); -var cfg = require('../cfg/websrv.json'); +//var cfg = require('../cfg/websrv.json'); var exec = require('child_process').exec; var templates = {}; @@ -16,13 +16,13 @@ function Websrv(tlib) { Websrv.prototype.getTpls(); http.createServer((req, res) => { - if(cfg.wlip.hasOwnProperty(req.connection.remoteAddress)) { + if(lib.cfg.websrv.wlip.hasOwnProperty(req.connection.remoteAddress)) { var filePath = '.' + req.url; var url = req.url.split("/")[1]; if(filePath == './') filePath = './index.html'; if(req.method == 'POST') { - if(filePath == './git' && req.headers['x-gitlab-token'] == cfg.gittoken) { + if(filePath == './git' && req.headers['x-gitlab-token'] == lib.cfg.websrv.gittoken) { var body = ''; req.on('data', (data) => { body += data; @@ -43,12 +43,12 @@ function Websrv(tlib) { catch(ex) { commit = body.commits[body.commits.length-2].message.replace('\n','').trim(); } - lib.bot.send( lib.cfg.debugchannel, eventname + ' from ' + autor + ' ('+commit+') in branch '+branch, 'n0xy' ); - if( lib.cfg.debugchannel === '#f0ck' ) { + lib.bot.send( lib.cfg.main.debugchannel, eventname + ' from ' + autor + ' ('+commit+') in branch '+branch, 'n0xy' ); + if( lib.cfg.main.debugchannel === '#f0ck' ) { exec('cd ../ & git pull', (error, stdout) => { if(error === null) { if(!lib.debug) - lib.bot.send(lib.cfg.debugchannel, 'git pull suxxessfully.', 'n0xy'); + lib.bot.send(lib.cfg.main.debugchannel, 'git pull suxxessfully.', 'n0xy'); else lib.log(stdout); } @@ -315,7 +315,7 @@ function Websrv(tlib) { res.writeHead(403); res.end('403 - forbidden'); } - }).listen(cfg.port); + }).listen(lib.cfg.websrv.port); } Websrv.prototype.getTpls = () => { From 0826223d5c7c08f0974a2149f0b456fd90070998 Mon Sep 17 00:00:00 2001 From: Flummi Date: Mon, 7 Nov 2016 02:15:08 +0100 Subject: [PATCH 5/7] bugfix --- src/lib.js | 45 ++-- src/websrv.js | 561 +++++++++++++++++++++++++------------------------- 2 files changed, 307 insertions(+), 299 deletions(-) diff --git a/src/lib.js b/src/lib.js index dd92cea..932e168 100644 --- a/src/lib.js +++ b/src/lib.js @@ -40,8 +40,8 @@ module.exports = Lib; function Lib() { //self.cfg = require('../cfg/main.json'); self.cfg = {}; - self.getcfg((cfg) => { - self.cfg = cfg; + self.getcfg('main', cfg => { + self.cfg.main = cfg; self.debug = self.cfg.main.debug; self.loadIRC(); self.loadUser((cb) => console.log((cb)?"Admins wurden geladen":"Admins konnten nicht geladen werden")); @@ -234,15 +234,17 @@ self.levelToModes = level => { return ""; }; -self.getcfg = (cb) => { - var out = { +self.getcfg = (kat, cb) => { + // main, websrv, irc + /*var out = { irc: {}, main: {}, websrv: {} - }; - self.sql.query("select * from `f0ck`.`cfg`", (err, rows) => { + };*/ + var out = {}; + self.sql.query("select * from `f0ck`.`cfg` where `class` = ?", kat, (err, rows) => { rows.forEach(e => { - out[e.class][e.key] = ((type, value) => { + out[e.key] = ((type, value) => { switch(type) { case 'string': return value; @@ -264,19 +266,22 @@ self.getcfg = (cb) => { }; self.loadIRC = () => { - self.cfg.irc.forEach(e => { - self.bot.add({ - "name": e.name, - "host": e.host, - "port": e.port, - "ssl": e.ssl, - "ssl_allow_invalid": e.ssl_allow_invalid, - "pass": e.pass, - "nick": e.nick, - "username": e.username, - "realname": e.realname, - "throttling": e.throttling + self.getcfg('irc', cfg => { + self.cfg.irc = cfg; + self.cfg.irc.forEach(e => { + self.bot.add({ + "name": e.name, + "host": e.host, + "port": e.port, + "ssl": e.ssl, + "ssl_allow_invalid": e.ssl_allow_invalid, + "pass": e.pass, + "nick": e.nick, + "username": e.username, + "realname": e.realname, + "throttling": e.throttling + }); + console.log("Server "+e.name+" wurde geladen"); }); - console.log("Server "+e.name+" wurde geladen"); }); }; \ No newline at end of file diff --git a/src/websrv.js b/src/websrv.js index 7f3b7e8..b734b0c 100644 --- a/src/websrv.js +++ b/src/websrv.js @@ -13,309 +13,312 @@ module.exports = Websrv; function Websrv(tlib) { this.lib = lib = tlib; - Websrv.prototype.getTpls(); - - http.createServer((req, res) => { - if(lib.cfg.websrv.wlip.hasOwnProperty(req.connection.remoteAddress)) { - var filePath = '.' + req.url; - var url = req.url.split("/")[1]; - if(filePath == './') - filePath = './index.html'; - if(req.method == 'POST') { - if(filePath == './git' && req.headers['x-gitlab-token'] == lib.cfg.websrv.gittoken) { - var body = ''; - req.on('data', (data) => { - body += data; - if(body.length > 1e6) - req.connection.destroy(); - }); - req.on('end', () => { - body = JSON.parse(body); - var eventname = body.event_name; - var autor = body.user_name; - var branch = body.ref.split('/')[2]; - - if(branch === "master") { - var commit = ""; - try { - commit = body.commits[body.commits.length-1].message.replace('\n','').trim(); + lib.getcfg('websrv', cfg => { + lib.cfg.websrv = cfg; + Websrv.prototype.getTpls(); + + http.createServer((req, res) => { + if(lib.cfg.websrv.wlip.hasOwnProperty(req.connection.remoteAddress)) { + var filePath = '.' + req.url; + var url = req.url.split("/")[1]; + if(filePath == './') + filePath = './index.html'; + if(req.method == 'POST') { + if(filePath == './git' && req.headers['x-gitlab-token'] == lib.cfg.websrv.gittoken) { + var body = ''; + req.on('data', (data) => { + body += data; + if(body.length > 1e6) + req.connection.destroy(); + }); + req.on('end', () => { + body = JSON.parse(body); + var eventname = body.event_name; + var autor = body.user_name; + var branch = body.ref.split('/')[2]; + + if(branch === "master") { + var commit = ""; + try { + commit = body.commits[body.commits.length-1].message.replace('\n','').trim(); + } + catch(ex) { + commit = body.commits[body.commits.length-2].message.replace('\n','').trim(); + } + lib.bot.send( lib.cfg.main.debugchannel, eventname + ' from ' + autor + ' ('+commit+') in branch '+branch, 'n0xy' ); + if( lib.cfg.main.debugchannel === '#f0ck' ) { + exec('cd ../ & git pull', (error, stdout) => { + if(error === null) { + if(!lib.debug) + lib.bot.send(lib.cfg.main.debugchannel, 'git pull suxxessfully.', 'n0xy'); + else + lib.log(stdout); + } + }); + } } - catch(ex) { - commit = body.commits[body.commits.length-2].message.replace('\n','').trim(); - } - lib.bot.send( lib.cfg.main.debugchannel, eventname + ' from ' + autor + ' ('+commit+') in branch '+branch, 'n0xy' ); - if( lib.cfg.main.debugchannel === '#f0ck' ) { - exec('cd ../ & git pull', (error, stdout) => { - if(error === null) { - if(!lib.debug) - lib.bot.send(lib.cfg.main.debugchannel, 'git pull suxxessfully.', 'n0xy'); - else - lib.log(stdout); - } - }); - } - } - }); - res.writeHead(200, { 'Content-Type': 'text/html' }); - res.end('muh', 'utf-8'); + }); + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end('muh', 'utf-8'); + } + else { + res.writeHead(403); + } } else { - res.writeHead(403); - } - } - else { - var extname = String(path.extname(filePath)).toLowerCase(); - var contentType = 'text/html'; - var mimeTypes = { - '.html': 'text/html', - '.js': 'text/javascript', - '.css': 'text/css', - '.png': 'image/png', - '.jpg': 'image/jpg', - '.gif': 'image/gif', - '.mp3': 'audio/mpeg', - '.mp4': 'video/mp4', - '.webm': 'video/webm', - '.ogg': 'audio/ogg' - }; - if(filePath == "./index.html") { // mainpage - var tpl = swig.compile(templates.index); - var data = { - items: [], - last: 10000 + var extname = String(path.extname(filePath)).toLowerCase(); + var contentType = 'text/html'; + var mimeTypes = { + '.html': 'text/html', + '.js': 'text/javascript', + '.css': 'text/css', + '.png': 'image/png', + '.jpg': 'image/jpg', + '.gif': 'image/gif', + '.mp3': 'audio/mpeg', + '.mp4': 'video/mp4', + '.webm': 'video/webm', + '.ogg': 'audio/ogg' }; - lib.sql.query("select `id`,`mime` from `f0ck`.`items` order by `id` desc limit 100", (err, rows, fields) => { - rows.forEach((e,i,a) => { - data.items.push({ "id": e.id, "mime": e.mime }); - data.last = e.id; - }); - res.writeHead(200, { 'Content-Type': 'text/html' }); - res.end(tpl(data), 'utf-8'); - }); - } - else if(Number.isInteger(parseInt(url))) { // itempage - var query = "select * from `f0ck`.`items` where `id` = ? limit 1; " // get item - + "select `id` from `f0ck`.`items` where `id` = (select min(`id`) from `f0ck`.`items` where `id` > ?); " // get previous item - + "select `id` from `f0ck`.`items` where `id` = (select max(`id`) from `f0ck`.`items` where `id` < ?)"; // get next item - lib.sql.query(query, [url, url, url], (err, rows, fields) => { - var tpl = swig.compile(templates.item); + if(filePath == "./index.html") { // mainpage + var tpl = swig.compile(templates.index); var data = { - id: '', - username: '', - item: '', - src: '', - dest: '', - mime: '', - size: '', - userchannel: '', - usernetwork: '', - thumb: null, - next: null, - prev: null + items: [], + last: 10000 }; - if(rows[0].length) { - var e = rows[0][0]; - switch(e.mime) { - case "image/png": - case "image/jpeg": - case "image/gif": - data.item = 'image'; - break; - case "video/webm": - case "video/mp4": - data.item = 'video'; - break; - case "audio/mpeg": - case "audio/ogg": - data.item = 'audio'; - break; - } - data.id = e.id; - data.username = e.username; - - data.srcurl = e.src; - data.src = urlm.parse(e.src).hostname; - data.thumb = (e.thumb != '' && e.thumb.match(/sndcdn\.com/i))?e.thumb:null; - - data.dest = e.dest; - data.mime = e.mime; - data.size = lib.formatSize(e.size); - data.userchannel = e.userchannel; - data.usernetwork = e.usernetwork; - data.timestamp = new Date(e.stamp * 1000).toString(); - if(rows[1].length) - data.next = rows[1][0].id; - if(rows[2].length) - data.prev = rows[2][0].id; - } - res.writeHead(200, { 'Content-Type': 'text/html' }); - res.end(tpl(data), 'utf-8'); - }); - } - else if(filePath == "./random") { - lib.sql.query("select `id` from `f0ck`.`items` order by rand() limit 1", (err, rows, fields) => { - res.writeHead(301, { - 'Cache-Control': 'no-cache, public', - 'Location': '/' + rows[0].id - }); - res.end(); - }); - } - else if(filePath == "./how") { - var tpl = swig.compile(templates.how); - res.writeHead(200, { 'Content-Type': 'text/html' }); - res.end(tpl(), 'utf-8'); - } - else if(filePath == "./contact") { - var tpl = swig.compile(templates.contact); - res.writeHead(200, { 'Content-Type': 'text/html' }); - res.end(tpl(), 'utf-8'); - } - else if(filePath == "./scripts") { - var tpl = swig.compile(templates.scripts); - res.writeHead(200, { 'Content-Type': 'text/html' }); - res.end(tpl(), 'utf-8'); - } - else if(filePath.match(/^\.\/(b|s|t)\/.*/)) { // file - contentType = mimeTypes[extname]; - switch(contentType) { - case "video/webm": - case "video/mp4": - case "audio/mpeg": - case "audio/ogg": - var start = 0; - var end = 0; - var range = req.headers['range']; - var stat = fs.statSync(filePath); - if(range != null) { - start = parseInt(range.slice(range.indexOf('bytes=')+6, range.indexOf('-'))); - end = parseInt(range.slice(range.indexOf('-')+1, range.length)); - } - if(isNaN(end) || end == 0) end = stat.size-1; - if(start > end) return; - res.writeHead(206, { - 'Connection':'close', - 'Content-Type':contentType, - 'Content-Length':end - start, - 'Content-Range':'bytes '+start+'-'+end+'/'+stat.size, - 'Transfer-Encoding':'chunked' - }); - var stream = fs.createReadStream(filePath, { flags: 'r', start: start, end: end}); - stream.pipe(res); - break; - default: - fs.readFile(filePath, (error, content) => { - if(error) { - if(error.code == 'ENOENT') { - res.writeHead(200, { 'Content-Type': contentType }); - res.end('404 - f0ck you', 'utf-8'); - } - else { - res.writeHead(500); - res.end('Sorry, check with the site admin for error: '+error.code+' ..\n'); - res.end(); - } - } - else { - res.writeHead(200, { 'Content-Type': contentType, 'Content-Length': content.length, 'Cache-Control': 'max-age=2592000, public' }); - res.end(content, 'utf-8'); - } - }); - break; - } - fs.readFile(filePath, (error, content) => { - if(error) { - if(error.code == 'ENOENT') { - res.writeHead(200, { 'Content-Type': contentType }); - res.end('404 - f0ck you', 'utf-8'); - } - else { - res.writeHead(500); - res.end('Sorry, check with the site admin for error: '+error.code+' ..\n'); - res.end(); - } - } - }); - } - else if(filePath.match(/^\.\/api/i)) { // api - var url = filePath.split('/'); - if(url[2] === undefined) { // Mainpage - var query = "select * from `f0ck`.`items`"; - lib.sql.query(query, (err, rows, fields) => { - var items = []; + lib.sql.query("select `id`,`mime` from `f0ck`.`items` order by `id` desc limit 100", (err, rows, fields) => { rows.forEach((e,i,a) => { - items.push({ - 'id': e.id, - 'mime': e.mime - }); + data.items.push({ "id": e.id, "mime": e.mime }); + data.last = e.id; }); res.writeHead(200, { 'Content-Type': 'text/html' }); - res.end(JSON.stringify(items), 'utf-8'); + res.end(tpl(data), 'utf-8'); }); } - else if(url[2] == "p" && Number.isInteger(parseInt(url[3]))) { // pagination - var eps = 50; - var id = url[3]; - - lib.sql.query("select * from `f0ck`.`items` where `id` < ? order by `id` desc limit ?", [id, eps], (err, rows, fields) => { - var items = { - "items": [], - "last": id - }; - rows.forEach((e,i,a) => { - items.items.push({ - 'id': e.id, - 'mime': e.mime - }); - items.last = e.id; - }); - res.writeHead(200, { 'Content-Type': 'text/html' }); - res.end(JSON.stringify(items), 'utf-8'); - }); - } - else if(Number.isInteger(parseInt(url[2]))) { // Item + else if(Number.isInteger(parseInt(url))) { // itempage var query = "select * from `f0ck`.`items` where `id` = ? limit 1; " // get item - + "select `id` from `f0ck`.`items` where `id` = (select min(`id`) from `f0ck`.`items` where `id` > ?); " // get previous item - + "select `id` from `f0ck`.`items` where `id` = (select max(`id`) from `f0ck`.`items` where `id` < ?)"; // get next item - lib.sql.query(query, [url[2], url[2], url[2]], (err, rows, fields) => { - var data; + + "select `id` from `f0ck`.`items` where `id` = (select min(`id`) from `f0ck`.`items` where `id` > ?); " // get previous item + + "select `id` from `f0ck`.`items` where `id` = (select max(`id`) from `f0ck`.`items` where `id` < ?)"; // get next item + lib.sql.query(query, [url, url, url], (err, rows, fields) => { + var tpl = swig.compile(templates.item); + var data = { + id: '', + username: '', + item: '', + src: '', + dest: '', + mime: '', + size: '', + userchannel: '', + usernetwork: '', + thumb: null, + next: null, + prev: null + }; if(rows[0].length) { var e = rows[0][0]; - data = { - id: e.id, - username: e.username, - src: e.src, - dest: e.dest, - mime: e.mime, - size: e.size, - userchannel: e.userchannel, - usernetwork: e.usernetwork, - next: null, - prev: null - }; + switch(e.mime) { + case "image/png": + case "image/jpeg": + case "image/gif": + data.item = 'image'; + break; + case "video/webm": + case "video/mp4": + data.item = 'video'; + break; + case "audio/mpeg": + case "audio/ogg": + data.item = 'audio'; + break; + } + data.id = e.id; + data.username = e.username; + + data.srcurl = e.src; + data.src = urlm.parse(e.src).hostname; + data.thumb = (e.thumb != '' && e.thumb.match(/sndcdn\.com/i))?e.thumb:null; + + data.dest = e.dest; + data.mime = e.mime; + data.size = lib.formatSize(e.size); + data.userchannel = e.userchannel; + data.usernetwork = e.usernetwork; + data.timestamp = new Date(e.stamp * 1000).toString(); if(rows[1].length) data.next = rows[1][0].id; if(rows[2].length) data.prev = rows[2][0].id; } - else - data = { error: 'nope' }; res.writeHead(200, { 'Content-Type': 'text/html' }); - res.end(JSON.stringify(data), 'utf-8'); + res.end(tpl(data), 'utf-8'); }); } - } - else { // errorpage - res.writeHead(404); - res.end('404 - f0ck you', 'utf-8'); + else if(filePath == "./random") { + lib.sql.query("select `id` from `f0ck`.`items` order by rand() limit 1", (err, rows, fields) => { + res.writeHead(301, { + 'Cache-Control': 'no-cache, public', + 'Location': '/' + rows[0].id + }); + res.end(); + }); + } + else if(filePath == "./how") { + var tpl = swig.compile(templates.how); + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(tpl(), 'utf-8'); + } + else if(filePath == "./contact") { + var tpl = swig.compile(templates.contact); + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(tpl(), 'utf-8'); + } + else if(filePath == "./scripts") { + var tpl = swig.compile(templates.scripts); + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(tpl(), 'utf-8'); + } + else if(filePath.match(/^\.\/(b|s|t)\/.*/)) { // file + contentType = mimeTypes[extname]; + switch(contentType) { + case "video/webm": + case "video/mp4": + case "audio/mpeg": + case "audio/ogg": + var start = 0; + var end = 0; + var range = req.headers['range']; + var stat = fs.statSync(filePath); + if(range != null) { + start = parseInt(range.slice(range.indexOf('bytes=')+6, range.indexOf('-'))); + end = parseInt(range.slice(range.indexOf('-')+1, range.length)); + } + if(isNaN(end) || end == 0) end = stat.size-1; + if(start > end) return; + res.writeHead(206, { + 'Connection':'close', + 'Content-Type':contentType, + 'Content-Length':end - start, + 'Content-Range':'bytes '+start+'-'+end+'/'+stat.size, + 'Transfer-Encoding':'chunked' + }); + var stream = fs.createReadStream(filePath, { flags: 'r', start: start, end: end}); + stream.pipe(res); + break; + default: + fs.readFile(filePath, (error, content) => { + if(error) { + if(error.code == 'ENOENT') { + res.writeHead(200, { 'Content-Type': contentType }); + res.end('404 - f0ck you', 'utf-8'); + } + else { + res.writeHead(500); + res.end('Sorry, check with the site admin for error: '+error.code+' ..\n'); + res.end(); + } + } + else { + res.writeHead(200, { 'Content-Type': contentType, 'Content-Length': content.length, 'Cache-Control': 'max-age=2592000, public' }); + res.end(content, 'utf-8'); + } + }); + break; + } + fs.readFile(filePath, (error, content) => { + if(error) { + if(error.code == 'ENOENT') { + res.writeHead(200, { 'Content-Type': contentType }); + res.end('404 - f0ck you', 'utf-8'); + } + else { + res.writeHead(500); + res.end('Sorry, check with the site admin for error: '+error.code+' ..\n'); + res.end(); + } + } + }); + } + else if(filePath.match(/^\.\/api/i)) { // api + var url = filePath.split('/'); + if(url[2] === undefined) { // Mainpage + var query = "select * from `f0ck`.`items`"; + lib.sql.query(query, (err, rows, fields) => { + var items = []; + rows.forEach((e,i,a) => { + items.push({ + 'id': e.id, + 'mime': e.mime + }); + }); + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(JSON.stringify(items), 'utf-8'); + }); + } + else if(url[2] == "p" && Number.isInteger(parseInt(url[3]))) { // pagination + var eps = 50; + var id = url[3]; + + lib.sql.query("select * from `f0ck`.`items` where `id` < ? order by `id` desc limit ?", [id, eps], (err, rows, fields) => { + var items = { + "items": [], + "last": id + }; + rows.forEach((e,i,a) => { + items.items.push({ + 'id': e.id, + 'mime': e.mime + }); + items.last = e.id; + }); + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(JSON.stringify(items), 'utf-8'); + }); + } + else if(Number.isInteger(parseInt(url[2]))) { // Item + var query = "select * from `f0ck`.`items` where `id` = ? limit 1; " // get item + + "select `id` from `f0ck`.`items` where `id` = (select min(`id`) from `f0ck`.`items` where `id` > ?); " // get previous item + + "select `id` from `f0ck`.`items` where `id` = (select max(`id`) from `f0ck`.`items` where `id` < ?)"; // get next item + lib.sql.query(query, [url[2], url[2], url[2]], (err, rows, fields) => { + var data; + if(rows[0].length) { + var e = rows[0][0]; + data = { + id: e.id, + username: e.username, + src: e.src, + dest: e.dest, + mime: e.mime, + size: e.size, + userchannel: e.userchannel, + usernetwork: e.usernetwork, + next: null, + prev: null + }; + if(rows[1].length) + data.next = rows[1][0].id; + if(rows[2].length) + data.prev = rows[2][0].id; + } + else + data = { error: 'nope' }; + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(JSON.stringify(data), 'utf-8'); + }); + } + } + else { // errorpage + res.writeHead(404); + res.end('404 - f0ck you', 'utf-8'); + } } } - } - else { - res.writeHead(403); - res.end('403 - forbidden'); - } - }).listen(lib.cfg.websrv.port); + else { + res.writeHead(403); + res.end('403 - forbidden'); + } + }).listen(lib.cfg.websrv.port); + }); } Websrv.prototype.getTpls = () => { From a87015a31f014e7cf3f15da6ab3cbe381508ae28 Mon Sep 17 00:00:00 2001 From: Flummi Date: Mon, 7 Nov 2016 02:18:31 +0100 Subject: [PATCH 6/7] bugfix --- src/lib.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.js b/src/lib.js index 932e168..8bf4d35 100644 --- a/src/lib.js +++ b/src/lib.js @@ -268,7 +268,7 @@ self.getcfg = (kat, cb) => { self.loadIRC = () => { self.getcfg('irc', cfg => { self.cfg.irc = cfg; - self.cfg.irc.forEach(e => { + self.cfg.irc.irccfg.forEach(e => { self.bot.add({ "name": e.name, "host": e.host, From 1e95e22c1dc099bbcce5d47b38bb3cf57a358e3a Mon Sep 17 00:00:00 2001 From: Flummi Date: Mon, 7 Nov 2016 02:28:40 +0100 Subject: [PATCH 7/7] cleanup --- src/lib.js | 13 ------------- src/trigger/parser.js | 1 - src/websrv.js | 1 - 3 files changed, 15 deletions(-) diff --git a/src/lib.js b/src/lib.js index 8bf4d35..42158fd 100644 --- a/src/lib.js +++ b/src/lib.js @@ -5,7 +5,6 @@ var exec = require('child_process').exec; var crypto = require('crypto'); var request = require('request'); var mysql = require('mysql'); -//var irccfg = require('../cfg/irc.json'); var self = Lib.prototype; self.bot = require('coffea')(); @@ -38,7 +37,6 @@ haDC(); module.exports = Lib; function Lib() { - //self.cfg = require('../cfg/main.json'); self.cfg = {}; self.getcfg('main', cfg => { self.cfg.main = cfg; @@ -48,14 +46,8 @@ function Lib() { self.loadTrigger(); self.loadEvents(); }); - - /*self.debug = self.cfg.debug; - self.loadUser((cb) => console.log((cb)?"Admins wurden geladen":"Admins konnten nicht geladen werden")); - self.loadTrigger(); - self.loadEvents();*/ } - self.getUserlevel = (user, network, channel, cb) => { self.bot.whois(user, network, (err, data) => { self.bot.names(channel, network, (names) => { @@ -236,11 +228,6 @@ self.levelToModes = level => { self.getcfg = (kat, cb) => { // main, websrv, irc - /*var out = { - irc: {}, - main: {}, - websrv: {} - };*/ var out = {}; self.sql.query("select * from `f0ck`.`cfg` where `class` = ?", kat, (err, rows) => { rows.forEach(e => { diff --git a/src/trigger/parser.js b/src/trigger/parser.js index 3e323a2..d5c76cf 100644 --- a/src/trigger/parser.js +++ b/src/trigger/parser.js @@ -7,7 +7,6 @@ var fileType = require('file-type'); var request = require('request'); var ytdl = require('ytdl-core'); var Readable = require('stream').Readable; -//var cfg = require('../../cfg/main.json'); module.exports = (lib) => { lib.trigger.add({ diff --git a/src/websrv.js b/src/websrv.js index b734b0c..fe6b4ba 100644 --- a/src/websrv.js +++ b/src/websrv.js @@ -3,7 +3,6 @@ var http = require('http'); var path = require('path'); var swig = require('swig'); var urlm = require('url'); -//var cfg = require('../cfg/websrv.json'); var exec = require('child_process').exec; var templates = {};