NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name TWITTER DOWNLOAD GIF & VIDEO & ORIG IMAGES
// @namespace http://tampermonkey.net/
// @version 1.32
// @description Helps you download gifs, videos and original images from Twitter.
// @copyright 2021, Trixille/Vitaminiser (https://twitter.com/vitaminiser)
// @license Artistic-2.0
// @author https://twitter.com/vitaminiser
// @match https://twitter.com/*
// @match https://giphy.com/upload*
// @match https://ezgif.com/video-to-gif*
// @match https://imgflip.com/gif-maker*
// @match https://gifs.com/*
// @iconURL https://pbs.twimg.com/media/Exfb8J_XAAYp733?format=png
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
const DEBUG_SCRIPT = false;
const CSS = `
div.download-panel {
display:none;
flex-direction: column;
flex-wrap: nowrap;
position: absolute;
pointer-events: auto !important;
z-index: 60000;
right: 0px;
background-color: rgba(0, 0, 0, 0.5);
font-size: 12px;
color:white;
width:80px;
backdrop-filter:blur(3px);
border-top-right-radius: 15px;
}
div.download-panel > div.download-init-button {
cursor: pointer;
background-color: transparent;
font-size: 10px;
width: 100%;
display:flex;
flex-direction: row;
flex-wrap: nowrap;
align-items:center;
border: 1px solid rgba(255, 255, 255, 0.5);
user-select: none;
}
div.download-panel * {
cursor: pointer;
}
article div[data-testid="videoPlayer"]:hover div.download-panel,
img:hover + div.download-panel,
div.download-panel:hover{
display: flex;
}
div[data-download_image_button="true"]:hover >div>div>div>div{
background-color: rgba(255, 255, 255, 0.1);
}
div[data-download_image_button="true"]{
margin-left: 23px;
}
div[data-download_image_button="true"] * {
pointer-events: none;
}
div[data-has_panel] div.download-image-original {
display: none;
position:absolute;
bottom:0px;
right:0px;
background-color: rgba(0, 0, 0, 0.5);
color:white;
min-width: 20px;
min-height: 20px;
font-size: 16px !important;
align-items: baseline;
justify-content: flex-end;
border-top-left-radius: 20px;
border:1px solid white;
border-bottom-width: 0px;
border-right-width: 0px;
}
div[data-has_panel] div.download-image-original:after {
content: '⭳';
color:white;
}
div[data-has_panel]:hover div.download-image-original {
display: flex;
}
img + div.download-panel{
bottom:0px;
right:0px;
}
div.download-panel span {
font-size: 14px;
display: flex;
justify-content: center;
width: 16px;
pointer-events:none;
}
div.download-panel>div.download-init-button>button {
cursor: pointer;
background-color: transparent;
color: white;
border: 1px solid transparent;
font-size: 10px;
width: 58px;
display:flex;
flex-direction: row;
flex-wrap: nowrap;
pointer-events: none;
}
div.download-panel[data-collapsed="true"] div.download-links,
div.download-panel>div.download-init-button>a.download-video-link {
display: none;
}
article div[data-testid="videoPlayer"]:hover div.download-panel {
display: flex;
}
div.download-panel div.download-links {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
}
div.download-panel button {
cursor: pointer;
background-color: transparent;
width: 100%;
text-align: center;
display: block;
color: white;
font-size: 10px;
border: 1px solid rgba(255, 255, 255, 0.5);
padding: 1px;
margin-top: 1px;
text-decoration: none;
/*Firefox hover fix*/
max-height:15px;
overflow:hidden;
}
div.download-panel button:after {
content:attr(data-label);
display:flex;
width:100%;
background:transparent;
outline: 1px solid rgba(255, 255, 255, 0.1);
padding-left: 5px;
justify-content: center;
align-items: center;
padding-left: 0px;
}
div.download-panel button[data-label_hover]:hover:after {
content:attr(data-label_hover);
}
div.download-links button[class*='upload-']:after,
div.download-links button[data-label='as MP4']:after{
justify-content: center;
align-items: center;
padding-left: 0px;
}
div.download-panel div.download-links button:hover{
color: red;
border-color: red;
}
div.download-panel button.download-mp4[data-best_quality="true"] {
/*color: yellow;*/
font-weight: bold;
border-color:white;
}
div.download-panel div.download-links button[data-best_quality="true"]:hover:after{
content:'BEST BITRATE';
text-shadow: 0px 0px 1px 0px red;
}
div.download-panel div.download-links button.download-mp4[data-status="pending"]:after {
color: lime;
border-color: lime;
outline-color: lime;
content: 'DOWNLOADING';
}
.media-downloader-settings-panel {
display: none;
background-color:red;
}
.media-downloader-settings-panel {
flex-direction:column;
justify-content:center;
align-items:center;
position: fixed;
top:0px;
bottom:0px;
left:0px;
right:0px;
z-index: 30000000;
background-color:transparent;
pointer-events:none;
}
.media-downloader-settings-panel > div {
display:flex;
flex-direction:column;
justify-content:center;
align-items:center;
width:400px;
height:400px;
background-color: rgba(0,0,0,0.8);
pointer-events:all;
color:white;
border: 1px solid white;
backdrop-filter:blur(5px);
font-family: sans-serif;
}
.media-downloader-settings-panel fieldset {
display:flex;
flex-direction:column;
}
.media-downloader-settings-panel li {
list-style: none;
font-size: 12px;
padding: 4px 0px 4px 0px;
}
.media-downloader-settings-panel button{
background-color: transparent;
border: 1px dashed;
color: white;
width: 150px;
height: 40px;
margin: 20px;
cursor: pointer;
}
`;
const GIFUploader = {
'giphy': {
title: 'GIPHY',
url: 'https://giphy.com/upload',
selector: 'input[type="url"]',
trigger: function triggerChange(value, sel) {
let input = document.querySelector(sel);
input.value = value;
let event = document.createEvent('HTMLEvents');
event.initEvent('input', true, false);
input.dispatchEvent(event);
}
},
'ezgif': {
title: 'EzGif',
url: 'https://ezgif.com/video-to-gif',
selector: 'form.form input[name="new-image-url"]',
trigger: function triggerChange(value, sel) {
const input = document.querySelector(sel);
input.value = value;
document.querySelector('form.form').submit();
}
},
/*
'tenor' : {
title: 'Tenor',
url: 'https://tenor.com/gif-maker',
selector: 'input#upload_url',
trigger: function triggerChange(value, sel) {
const input = document.querySelector(sel);
input.value = value;
let event = new Event('change', { bubbles: true });
input.dispatchEvent( event );
}
},*/
'imgflip': {
title: 'ImgFlip',
url: 'https://imgflip.com/gif-maker',
selector: 'div#vgif-upload-panel input#url',
trigger: function triggerChange(value, sel) {
const input = document.querySelector(sel);
input.value = value;
const w = (unsafeWindow)?unsafeWindow:window;
let event = new w.Event('change', { bubbles: true });
input.dispatchEvent( event );
}
},
'gifs.com': {
title: 'Gifs.com',
url: 'https://gifs.com/',
selector: 'div#editor-input input#home-input',
trigger: function triggerChange(value, sel) {
debug(value,sel);
const input = document.querySelector(sel);
input.value = value;
document.querySelector('button#home-create').click();
}
},
get: function (name) {
return this[name.toLowerCase()];
},
props: function() {
let o = {};
for( let p in this ) {
if( this[p].url ) o[p] = this[p];
}
return o;
},
propNames: function() {
let o = {};
for( let p in this ) {
if( this[p].url ) o[p] = true;
}
return o;
},
setEnabled: function(name,bool) {
this.get(name).enabled = bool;
}
}
let SETTINGS = {};
//Start
if (window.location.hostname === "twitter.com") {
//Load Settings
try {
loadSettings().then( settings => {
SETTINGS = settings;
debug(SETTINGS);
//SETTINGS = null;
//GM_deleteValue('gif_hosts');
});
}
catch(e){
debug('Error loading settings ' + e );
}
//GM_addStyle(CSS);
sleepUntil('head',() => {
const style = document.querySelector('#download-panel-style') || el('style',document.head);
style.id = 'download-panel-style';
style.type = 'text/css';
style.appendChild(document.createTextNode(CSS));
},'',5000);
document.addEventListener('mouseover', function (e) {
const t = e.target;
let parent;
if (t.tagName === "IMG") {
const regexp = /https:\/\/pbs.twimg.com\/media\/(.+)\?format=([a-z]+)[\z]?&*/;
if (t.src.match(regexp)) {
const p = t.parentElement.parentElement;
if (!p.dataset.has_panel) {
//'\u2B73'
const div = el('div', p, 'download-image-original', '');
div.dataset.src = t.src;
p.dataset.has_panel = true;
div.onclick = imageButtonClick;
}
}
}
else if (e.target.classList == "css-1dbjc4n r-1p0dtai r-1loqt21 r-1d2f490 r-u8s1d r-zchlnj r-ipm5af") {
if (t.parentElement.dataset.has_panel) return;
if (videoEl(e.target) && !e.target.firstChild) {
parent = t.parentElement;
parent.dataset.download_panel_container = true;
let panel = el('div', parent, 'download-panel', '', panelClick);
const d = el('div', panel, 'download-init-button');
el('span', d, 'download-icon', '\u2B73');
el('button', d, 'download', 'Download');
parent.dataset.has_panel = true;
}
}
}, false);
}
else {
const url = new URL( window.location );
const media_url = url.searchParams.get('media_url');
const type = url.searchParams.get('type');
if(!media_url || !type ) return;
const site = GIFUploader.get(type);
sleepUntil( site.selector , site.trigger, media_url, 5000 );
}
function loadSettings() {
if ( !isExtension() ) {
let gif_hosts = {};
for( let p in GIFUploader ) {
gif_hosts[p] = true;
}
let value = GM_getValue('gif_hosts', JSON.stringify( gif_hosts ) );
value = JSON.parse(value);
return Promise.resolve(value);
}
else {
return new Promise( (resolve,reject) => {
chrome.storage.local.get('gif_hosts', function(result) {
debug('loadSettings result ' , result );
resolve( result.gif_hosts );
});
});
}
}
function createSettingsPanel() {
const panel = el('div',document.body,'media-downloader-settings-panel');
const inner = el('div',panel);
const fieldset_gifs = el('fieldset',inner);
el('legend', fieldset_gifs,'','Show GIF Hosts & Converters');
el('button',inner,'','Close', ( e ) => {
document.querySelector('.media-downloader-settings-panel').style.display = 'none';
});
//this.list = el('ul',fieldset,'');
for( let o in GIFUploader ) {
if( GIFUploader[o].url ) {
const li = el('li',fieldset_gifs,'');
const radio = el('input',li,'');
radio.type = 'checkbox';
radio.value = o;
radio.name = o;
//if(settings[o] === true) radio.checked = true;
radio.onchange = saveSettings;
const label = el('label',li,'',o);
label.for = o;
}
}
return panel;
}
function saveSettings( e ) {
const panel = document.querySelector('.media-downloader-settings-panel');
//const t = e.target;
//const name = t.name;
//debug( name );
const inputs = panel.querySelectorAll('input[type="checkbox"]');
//debug( inputs );
const propnames = GIFUploader.propNames();
SETTINGS = SETTINGS || {};
inputs.forEach( (input) => {
debug( input.name , input.checked );
//GIFUploader.setEnabled( input.name, Boolean( input.checked ) );
if( propnames[input.name] ) {
//propnames[input.name] = Boolean( input.checked );
//GIFUploader.setEnabled( input.name, Boolean( input.checked ));
SETTINGS[input.name] = Boolean( input.checked );
}
});
debug( 'new_settings', SETTINGS );
//this.settings = propnames;
if( !isExtension() ) {
GM_setValue('gif_hosts',JSON.stringify(SETTINGS));
}
else {
chrome.storage.local.set({'gif_hosts': SETTINGS}, function() {
chrome.storage.local.get('gif_hosts', res => {
console.log('Value is set to ' , res);
})
});
}
const b = document.querySelectorAll('div.download-links button[class*="upload-"]');
debug('existing_panels',b);
b.forEach( (btn) => {
const name = btn.className.toLowerCase().replace('upload-','');
debug(name);
if(SETTINGS[name] == false) btn.style.display = 'none';
else btn.style.display = 'block';
} );
}
function showSettingsPanel() {
Event('Settings Panel', 'Show Settings Panel');
const panel = document.querySelector('.media-downloader-settings-panel') || createSettingsPanel();
panel.style.display = "flex";
const inputs = panel.querySelectorAll('input');
inputs.forEach( ( input ) => {
if(SETTINGS && SETTINGS[input.value] == false) {
input.checked = false;
}
else {
input.checked = true;
}
});
}
function imageButtonClick(e) {
e.preventDefault();
e.stopPropagation();
const t = e.target;
function downloadImg(filename) {
if (!filename) return;
const url = 'https://pbs.twimg.com/media/' + filename;
getBlob(url + ':orig').then(blob => {
createDownloadLink(blob, filename, 'image');
Event('Image', 'Download Image');
});
}
if (e.target.dataset.filename) {
debug('DOWNLOADING FROM PRESAVED');
downloadImg(e.target.dataset.filename);
return;
}
const imgmatch = /https:\/\/pbs\.twimg\.com\/media\/(.+)\?format=([a-z]+).?/;
const res = t.dataset.src.match(imgmatch);
debug('SRC MARTCH', res);
if (res && res[1] && res[2]) {
const id = res[1];
const form = res[2];
debug('ID & FORMAT FOUND', id, form);
const filename = id + '.' + form;
e.target.dataset.filename = filename;
downloadImg(filename);
return;
}
}
async function fetchTweetInfo(tweetId) {
const url = "https://api.twitter.com/1.1/statuses/show.json?include_profile_interstitial_type=1&include_blocking=1&" +
"include_blocked_by=1&include_followed_by=1&include_want_retweets=1&include_mute_edge=1&include_can_dm=1&skip_status=1" +
"&cards_platform=Web-12&include_cards=1&include_ext_alt_text=true&include_reply_count=1" +
"&tweet_mode=extended&trim_user=false&include_ext_media_color=true&id=" + tweetId;
const headers = new Headers({
'Content-Type': 'application/json',
"Accept": '*/*',
"authorization": "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA",
"x-csrf-token": getCookie("ct0")
});
const json =
await fetch(url, {
method: 'GET',
credentials: 'include',
headers: headers
}).then(response => response.json());
return json;
}
function createDownloadLink(url, fileName, type) {
type = type || 'video';
fileName = fileName || type;
var a = document.getElementById('download-video');
if (!a) {
a = document.createElement('a');
a.setAttribute('id', 'download-video');
a.setAttribute('target', '_blank');
document.body.appendChild(a);
}
if (type === 'video') {
if (fileName.length < 1) fileName = 'video';
a.setAttribute('download', fileName + '.mp4');
}
else if (type === 'image') {
a.setAttribute('download', fileName);
}
a.setAttribute('href', url);
a.click();
setTimeout(() => {
URL.revokeObjectURL(url);
console.log('Object URL ' + url + ' released.');
}, 5000);
}
function panelClick(e) {
e.preventDefault();
let t = e.target;
let panel = t.closest('div.download-panel');
const fn = {
'download-init-button': () => {
panel = t.parentElement;
if (panel.children.length > 1) {
panel.dataset.collapsed = (panel.dataset.collapsed === 'true') ? false : true;
return;
}
const video = videoEl(t);
const type = getVideoType(video);
if (type === 'gif') getGIF(panel, video);
else if (type === 'video') getVideo(panel, video);
Event('Video&Gif Panel', 'Load Variants ' + type.toUpperCase());
},
'download-mp4': () => {
if (t.dataset.status === "pending") {
alert('This video is downloading.');
return;
}
const bl = getBlob(t.dataset.url);
e.target.dataset.status = "pending";
bl.then(blob => {
e.target.dataset.status = "done";
const title = createTitle(t);
createDownloadLink(blob, title, 'video');
Event('Download', 'Download MP4 ' + e.target.dataset.size_str);
});
}
}
const cls = t.className;
if (cls.indexOf('upload') > -1) {
upload(cls.replace('upload-', ''), t.dataset.url);
}
else if (fn[cls]) {
fn[cls]();
}
e.stopPropagation();
}
function isExtension() {
if( window.chrome && chrome.runtime && chrome.runtime.id )
return true;
else return false;
}
function upload(type, url) {
const obj = GIFUploader.get(type);
//If Using Extension Version
if ( isExtension() ) {
// Code running in a Chrome extension (content script, background page, etc.)
//obj.media = url;
//obj.trigger = obj.trigger.toString();
//obj.sleep = sleepUntil.toString()
const selector = GIFUploader.get(type).selector;
const trigger = GIFUploader.get(type).trigger.toString();
debug( 'PREP', selector, trigger );
let fn = 'sleepUntil( site.selector , site.trigger, media_url, 5000 )';
fn = fn.replace('site.selector','\'' + selector +'\'');
fn = fn.replace('site.trigger',trigger);
fn = fn.replace('media_url','\'' + url +'\'');
const sleep = sleepUntil.toString() + '\n\n' + fn;
debug( sleep );
const data = {
url: GIFUploader.get(type).url,
text: sleep
}
//fn.replace( )
// + '\n\n(' + initSleep.toString() + ')();'
chrome.runtime.sendMessage({ action: "uploadGIF", data: data });
Event('GIF', 'Upload GIF to ' + type);
}
else {
//Using GreaseMonkey Version
window.open(obj.url+'?media_url='+url+'&type='+type);
}
}
async function sleepUntil( selector , changefn, media, timeoutMs) {
return new Promise((resolve, reject) => {
let timeWas = new Date();
let wait = setInterval(function () {
if ( document.querySelector( selector ) ) {
console.log("resolved after", new Date() - timeWas, "ms");
clearInterval(wait);
changefn( media, selector );
resolve();
} else if (new Date() - timeWas > timeoutMs) { // Timeout
console.log("rejected after", new Date() - timeWas, "ms");
clearInterval(wait);
reject();
}
}, 10);
});
}
function getGIF(panel, gif) {
debug('getGif');
panel.dataset.media_type = 'gif';
const div = el('div', panel, 'download-links');
const mp4 = btn('download-mp4', '', div);
mp4.dataset.url = gif.src;
mp4.dataset.label = 'as MP4';
createGIFLinks(gif.src, div);
panel.dataset.done = true;
return panel;
}
function createLinks(o) {
const vids = o.vars;
if (!vids) return;
const div = el('div', null, 'download-links');
vids.forEach(vid => {
let b = btn('download-mp4', '', div);
b.dataset.label = vid.size_str + ' MP4';
for (let p in vid) {
b.dataset[p] = vid[p];
}
});
//find smallest for gifs
o.vars.sort((a, b) => {
if (a.size.height < b.size.height) return -1;
});
debug('Smallest Video Size', o.vars[0].size_str);
createGIFLinks(o.vars[0].url, div);
return div;
}
function createGIFLinks(url, div) {
const buttons = [];
const names = GIFUploader.propNames();
for( let prop in names ) {
buttons.push( GIFUploader[prop].title );
}
if (url && div) {
buttons.forEach(b => {
const el = btn('upload-' + b, '', div);
el.dataset.url = url;
el.dataset.label = b;
if( SETTINGS && SETTINGS[b.toLowerCase()] == false)
el.style.display = 'none';
});
const settings_btn = btn('download-settings-button', '', div);
settings_btn.dataset.label = '⚙';
settings_btn.dataset.label_hover = '⚙ Settings';
settings_btn.onclick = showSettingsPanel;
return div;
}
}
function createTitle(button) {
const video = videoEl(button);
if (getVideoType(video) === 'gif') {
const label = video.getAttribute('aria-label');
debug('VIDEO EL create Title', label);
if (label) return label;
}
let title = '';
if (button.dataset.user) title = title + button.dataset.user;
if (button.dataset.id) title = title + '_status_' + button.dataset.id;
if (button.dataset.size_str) title = title + '_' + button.dataset.size_str;
return title;
}
function getVideoType(video) {
if (!video || video.tagName !== 'VIDEO')
return false;
let type;
if (video.src.includes('.mp4')) {
type = 'gif';
}
else if (video.src.includes('blob:')) {
type = 'video';
}
return type;
}
function getVideo(panel, video) {
panel.dataset.done = true;
fetchTweetInfo(getTweetId(video)).then(json => {
debug('RESPONSE_2', json);
const o = getMediaSources(json, isQuote(video));
const oo = o;
oo.orig_vars = null;
panel.dataset.json = JSON.stringify(oo);
panel.dataset.tweet_id = o.id;
panel.dataset.user = o.user;
panel.appendChild(createLinks(o));
panel.dataset.done = true;
});
}
async function getBlob(url) {
let blob = await fetch(url).then(r => r.blob());
blob = blob.slice(0, blob.size, "application/octet-stream");
const bloburl = URL.createObjectURL(blob);
return bloburl;
}
function getTweetId(elem) {
var article_href = elem.closest('article').querySelector('a[href*="/status/"]').href;
var regexp = /\/status\/(\d+)/;
return article_href.match(regexp)[1];
}
function getMediaSources(json, isQuote) {
var video_variants;
let VIDEOS;
let ORIG;
let TWEET = json;
let o = {};
//debug( json.is_quote_status );
//get media in quoted tweet
if (isQuote && json.is_quote_status) {
debug('Quote', json.quoted_status.extended_entities);
VIDEOS = json.quoted_status.extended_entities.media[0].video_info.variants;
TWEET = json.quoted_status;
}
else if (!isQuote) {
debug('Not A Quote', json.extended_entities);
VIDEOS = json.extended_entities.media[0].video_info.variants;
}
let MEDIA = TWEET.extended_entities.media[0];
debug('MEDIA', MEDIA);
if (MEDIA.type != 'video' && MEDIA.type != 'animated_gif') return 'Media is not a video';
o.original_size = MEDIA.original_info;
o.original_str = o.original_size.width + 'x' + o.original_size.height;
o.id = MEDIA.id_str;
//o.aspect_ratio = MEDIA.video_info.aspect_ratio;
//o.aspect_ratio_str = MEDIA.video_info.aspect_ratio[0]+':'+MEDIA.video_info.aspect_ratio[1];
//o.duration_ms = MEDIA.video_info.duration_millis;
//o.duration_s = (o.duration_ms / 1000) % 60;
o.user = TWEET.user.screen_name;
o.orig_vars = MEDIA.video_info.variants;
o.vars = [];
const regexp = /\/vid\/([\dx]+)\//;
o.orig_vars.forEach(video => {
const m = video.url.match(regexp);
if (video.content_type == "video/mp4") {
let v = {};
v.url = video.url.split('?')[0];
if (m && m[1]) {
v.size_str = m[1];
v.size = {};
v.size.w = parseInt(m[1].split('x')[0]);
v.size.h = parseInt(m[1].split('x')[1]);
}
else v.size_str = o.original_str;
v.is_original_size = false;
if (v.size_str == o.original_str) v.is_original_size = true;
v.bitrate = video.bitrate;
v.id = o.id;
v.user = o.user;
o.vars.push(v);
}
});
debug('vars', o.vars);
o.vars =
o.vars.sort((x, y) => {
if (x.bitrate < y.bitrate) return -1;
if (x.bitrate > y.bitrate) return 1;
return 0;
});
o.vars[o.vars.length - 1].best_quality = true;
return o;
}
function getCookie(cname) {
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
function isQuote(video) {
return video.closest('div[role="link"]') ? true : false;
}
function el(tag, parent, cls, text, click) {
let e = document.createElement(tag);
if (cls) e.setAttribute('class', cls);
if (parent) parent.appendChild(e);
if (text) e.innerText = text;
if (click) e.addEventListener('click', click, false);
return e;
}
function btn(cls, text, parent) {
var btn = document.createElement('button');
if (cls) btn.setAttribute('class', cls);
if (text) btn.innerText = text;
if (parent) parent.appendChild(btn);
return btn;
}
function videoEl(elem) {
return elem.closest('div[data-testid="videoPlayer"]').querySelector('video');
}
function Event(category, name) {
if (window.chrome && chrome.runtime && chrome.runtime.id) {
chrome.runtime.sendMessage({ action: "trackEvent", data: Array.from(arguments) });
}
}
function debug() {
const DEBUG_ = ( isExtension() ) ? DEBUG : DEBUG_SCRIPT;
if (DEBUG_) {
console.log.apply(null, arguments);
}
}
function yell(str) {
const DEBUG_ = ( isExtension() ) ? DEBUG : DEBUG_SCRIPT;
if (DEBUG_) {
if (typeof str === "object") {
str = JSON.stringify(str);
}
alert(str);
}
}
})();