1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2025-05-14 11:34:08 +02:00
TweetDuck/Resources/Scripts/code.js

466 lines
13 KiB
JavaScript

(function($, TD){
if ($ === null){
console.error("Missing jQuery");
}
if (!("$TD" in window)){
console.error("Missing $TD");
}
if (!("$TDX" in window)){
console.error("Missing $TDX");
}
//
// Variable: Array of functions called after the website app is loaded.
//
let onAppReady = [];
//
// Variable: DOM object containing the main app element.
//
const app = typeof $ === "function" && $(document.body).children(".js-app");
//
// Function: Prepends code at the beginning of a function. If the prepended function returns true, execution of the original function is cancelled.
//
const prependToFunction = function(func, extension){
return function(){
return extension.apply(this, arguments) === true ? undefined : func.apply(this, arguments);
};
};
//
// Function: Appends code at the end of a function.
//
const appendToFunction = function(func, extension){
return function(){
const res = func.apply(this, arguments);
extension.apply(this, arguments);
return res;
};
};
//
// Function: Triggers an internal debug crash when something is missing.
//
const crashDebug = function(message){
console.error(message);
debugger;
if ("$TD" in window){
$TD.crashDebug(message);
}
}
//
// Function: Throws if an object does not have a specified property.
//
const ensurePropertyExists = function(obj, ...chain){
for(let index = 0; index < chain.length; index++){
if (!obj.hasOwnProperty(chain[index])){
throw "Missing property " + chain[index] + " in chain [obj]." + chain.join(".");
}
obj = obj[chain[index]];
}
};
//
// Function: Returns true if an object has a specified property, otherwise returns false with a debug-only error message.
//
const checkPropertyExists = function(obj, ...chain){
try{
ensurePropertyExists(obj, ...chain);
return true;
}catch(err){
crashDebug(err);
return false;
}
};
//
// Function: Throws if an element does not have a registered jQuery event.
//
const ensureEventExists = function(element, eventName){
if (!(eventName in $._data(element, "events"))){
throw "Missing jQuery event " + eventName + " in " + element.cloneNode().outerHTML;
}
};
//
// Function: Returns a jQuery object but also shows a debug-only error message if no elements are found.
//
const $$ = function(selector, context){
const result = $(selector, context);
if (!result.length){
crashDebug("No elements were found for selector " + selector);
}
return result;
};
//
// Function: Executes a function inside a try-catch to stop it from crashing everything.
//
const execSafe = function(func, fail){
try{
func();
}catch(err){
crashDebug("Caught error in function " + func.name)
fail && fail();
}
};
//
// Function: Returns an object containing data about the column below the cursor.
//
const getHoveredColumn = function(){
const hovered = document.querySelectorAll(":hover");
for(let index = hovered.length - 1; index >= 0; index--){
const ele = hovered[index];
if (ele.tagName === "SECTION" && ele.classList.contains("js-column")){
const obj = TD.controller.columnManager.get(ele.getAttribute("data-column"));
if (obj){
return { ele, obj };
}
}
}
return null;
};
//
// Function: Returns an object containing data about the tweet below the cursor.
//
const getHoveredTweet = function(){
const hovered = document.querySelectorAll(":hover");
for(let index = hovered.length - 1; index >= 0; index--){
const ele = hovered[index];
if (ele.tagName === "ARTICLE" && ele.classList.contains("js-stream-item") && ele.hasAttribute("data-account-key")){
const column = getHoveredColumn();
if (column){
const wrap = column.obj.findChirp(ele.getAttribute("data-key"));
const obj = column.obj.findChirp(ele.getAttribute("data-tweet-id")) || wrap;
if (obj){
return { ele, obj, wrap, column };
}
}
}
}
return null;
};
//
// Function: Retrieves a property of an element with a specified class.
//
const getClassStyleProperty = function(cls, property){
const column = document.createElement("div");
column.classList.add(cls);
column.style.display = "none";
document.body.appendChild(column);
const value = window.getComputedStyle(column).getPropertyValue(property);
document.body.removeChild(column);
return value;
};
//
// Block: Fix columns missing any identifiable attributes to allow individual styles.
//
execSafe(function setupColumnAttrIdentifiers(){
$(document).on("uiColumnRendered", function(e, data){
const icon = data.$column.find(".column-type-icon").first();
return if icon.length !== 1;
const name = Array.prototype.find.call(icon[0].classList, cls => cls.startsWith("icon-"));
return if !name;
data.$column.attr("data-td-icon", name);
data.column._tduck_icon = name;
});
});
//
// Block: Add TweetDuck buttons to the settings menu.
//
onAppReady.push(function setupSettingsDropdown(){
document.querySelector("[data-action='settings-menu']").addEventListener("click", function(){
setTimeout(function(){
const menu = document.querySelector(".js-dropdown-content ul");
return if !menu;
const dividers = menu.querySelectorAll(":scope > li.drp-h-divider");
const target = dividers[dividers.length - 1];
target.insertAdjacentHTML("beforebegin", '<li class="is-selectable" data-tweetduck><a href="#" data-action>TweetDuck</a></li>');
const button = menu.querySelector("[data-tweetduck]");
button.querySelector("a").addEventListener("click", function(){
$TD.openContextMenu();
});
button.addEventListener("mouseenter", function(){
button.classList.add("is-selected");
});
button.addEventListener("mouseleave", function(){
button.classList.remove("is-selected");
})
}, 0);
});
});
//
// Block: Hook into settings object to detect when the settings change, and update html attributes and notification layout.
//
execSafe(function hookTweetDeckSettings(){
ensurePropertyExists(TD, "settings", "getFontSize");
ensurePropertyExists(TD, "settings", "setFontSize");
ensurePropertyExists(TD, "settings", "getTheme");
ensurePropertyExists(TD, "settings", "setTheme");
const doc = document.documentElement;
const refreshSettings = function(){
const fontSizeName = TD.settings.getFontSize();
const themeName = TD.settings.getTheme();
const tags = [
"<html " + Array.prototype.map.call(doc.attributes, ele => `${ele.name}="${ele.value}"`).join(" ") + "><head>"
];
for(let ele of document.head.querySelectorAll("link[rel='stylesheet']:not([data-td-exclude-notification]),meta[charset]")){
tags.push(ele.outerHTML);
}
tags.push("<style type='text/css'>body { background: " + getClassStyleProperty("column-panel", "background-color") + " !important }</style>");
doc.setAttribute("data-td-font", fontSizeName);
doc.setAttribute("data-td-theme", themeName);
$TD.loadNotificationLayout(fontSizeName, tags.join(""));
};
TD.settings.setFontSize = appendToFunction(TD.settings.setFontSize, function(name){
setTimeout(refreshSettings, 0);
});
TD.settings.setTheme = appendToFunction(TD.settings.setTheme, function(name){
setTimeout(refreshSettings, 0);
});
onAppReady.push(refreshSettings);
});
//
// Block: Setup CSS injections.
//
execSafe(function setupStyleInjection(){
const createStyle = function(id, styles){
const ele = document.createElement("style");
ele.id = id;
ele.innerText = styles;
document.head.appendChild(ele);
};
window.TDGF_injectBrowserCSS = function(styles){
if (!document.getElementById("tweetduck-browser-css")){
createStyle("tweetduck-browser-css", styles);
}
};
window.TDGF_reinjectCustomCSS = function(styles){
const prev = document.getElementById("tweetduck-custom-css");
if (prev){
prev.remove();
}
if (styles && styles.length){
createStyle("tweetduck-custom-css", styles);
}
};
});
//
// Block: Setup custom sound notification.
//
window.TDGF_setSoundNotificationData = function(custom, volume){
const audio = document.getElementById("update-sound");
audio.volume = volume / 100;
const sourceId = "tduck-custom-sound-source";
let source = document.getElementById(sourceId);
if (custom && !source){
source = document.createElement("source");
source.id = sourceId;
source.src = "https://ton.twimg.com/tduck/updatesnd";
audio.prepend(source);
}
else if (!custom && source){
audio.removeChild(source);
}
audio.load();
};
//
// Block: Hook into composer event.
//
execSafe(function hookComposerEvents(){
$(document).on("uiDrawerActive uiRwebComposerOptOut", function(e, data){
return if e.type === "uiDrawerActive" && data.activeDrawer !== "compose";
setTimeout(function(){
$(document).trigger("tduckOldComposerActive");
}, 0);
});
});
//
// Block: Setup a top tier account bamboozle scheme.
//
execSafe(function setupAccountLoadHook(){
const realDisplayName = "TweetDuck";
const realAvatar = "https://ton.twimg.com/tduck/avatar";
const accountId = "957608948189880320";
if (checkPropertyExists(TD, "services", "TwitterUser", "prototype", "fromJSONObject")){
const prevFunc = TD.services.TwitterUser.prototype.fromJSONObject;
TD.services.TwitterUser.prototype.fromJSONObject = function(){
const obj = prevFunc.apply(this, arguments);
if (obj.id === accountId){
obj.name = realDisplayName;
obj.emojifiedName = realDisplayName;
obj.profileImageURL = realAvatar;
obj.url = "https://tweetduck.chylex.com";
if (obj.entities && obj.entities.url){
obj.entities.url.urls = [{
url: obj.url,
expanded_url: obj.url,
display_url: "tweetduck.chylex.com",
indices: [ 0, 23 ]
}];
}
}
return obj;
};
}
if (checkPropertyExists(TD, "services", "TwitterClient", "prototype", "typeaheadSearch")){
const prevFunc = TD.services.TwitterClient.prototype.typeaheadSearch;
TD.services.TwitterClient.prototype.typeaheadSearch = function(data, onSuccess, onError){
if (data.query && data.query.toLowerCase().endsWith("tweetduck")){
data.query = "TryMyAwesomeApp";
}
return prevFunc.call(this, data, function(result){
for(let user of result.users){
if (user.id_str === accountId){
user.name = realDisplayName;
user.profile_image_url = realAvatar;
user.profile_image_url_https = realAvatar;
break;
}
}
onSuccess.apply(this, arguments);
}, onError);
};
}
});
//
// Block: Work around clipboard HTML formatting.
//
document.addEventListener("copy", function(){
window.setTimeout($TD.fixClipboard, 0);
});
//
// Block: Fix OS name and add ID to the document for priority CSS selectors.
//
(function(){
const doc = document.documentElement;
if (checkPropertyExists(TD, "util", "getOSName")){
TD.util.getOSName = function(){
return "windows";
};
doc.classList.remove("os-");
doc.classList.add("os-windows");
}
doc.id = "tduck";
})();
//
// Block: Disable TweetDeck metrics.
//
if (checkPropertyExists(TD, "metrics")){
const noop = function(){};
TD.metrics.inflate = noop;
TD.metrics.inflateMetricTriple = noop;
TD.metrics.log = noop;
TD.metrics.makeKey = noop;
TD.metrics.send = noop;
}
onAppReady.push(function disableMetrics(){
const data = $._data(window);
delete data.events["metric"];
delete data.events["metricsFlush"];
});
//
// Block: Import scripts.
//
#import "scripts/browser.globals.js"
#import "scripts/browser.features.js"
#import "scripts/browser.tweaks.js"
//
// Block: Register the TD.ready event, finish initialization, and load plugins.
//
$(document).one("TD.ready", function(){
onAppReady.forEach(func => execSafe(func));
onAppReady = null;
if (window.TD_PLUGINS){
window.TD_PLUGINS.onReady();
}
});
//
// Block: Ensure window.jQuery is available.
//
window.jQuery = $;
//
// Block: Skip the initial pre-login page.
//
if (checkPropertyExists(TD, "controller", "init", "showLogin")){
TD.controller.init.showLogin = function(){
location.href = "https://twitter.com/login?hide_message=true&redirect_after_login=https%3A%2F%2Ftweetdeck.twitter.com%2F%3Fvia_twitter_login%3Dtrue";
};
}
})(window.$ || null, window.TD || {});