mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-05-17 20:34:09 +02:00
469 lines
15 KiB
JavaScript
469 lines
15 KiB
JavaScript
//
|
|
// Block: Paste images when tweeting.
|
|
//
|
|
onAppReady.push(function supportImagePaste(){
|
|
const uploader = $._data(document, "events")["uiComposeAddImageClick"][0].handler.context;
|
|
|
|
app.delegate(".js-compose-text,.js-reply-tweetbox,.td-detect-image-paste", "paste", function(e){
|
|
for(let item of e.originalEvent.clipboardData.items){
|
|
if (item.type.startsWith("image/")){
|
|
if (!$(this).closest(".rpl").find(".js-reply-popout").click().length){ // popout direct messages
|
|
return if $(".js-add-image-button").is(".is-disabled"); // tweetdeck does not check upload count properly
|
|
}
|
|
|
|
uploader.addFilesToUpload([ item.getAsFile() ]);
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
//
|
|
// Block: Allow changing first day of week in date picker.
|
|
//
|
|
if (checkPropertyExists($, "tools", "dateinput", "conf", "firstDay")){
|
|
$.tools.dateinput.conf.firstDay = $TDX.firstDayOfWeek;
|
|
|
|
onAppReady.push(function setupDatePickerFirstDayCallback(){
|
|
window.TDGF_registerPropertyUpdateCallback(function($TDX){
|
|
$.tools.dateinput.conf.firstDay = $TDX.firstDayOfWeek;
|
|
});
|
|
});
|
|
}
|
|
|
|
//
|
|
// Block: Override language used for translations.
|
|
//
|
|
if (checkPropertyExists(TD, "languages", "getSystemLanguageCode")){
|
|
const prevFunc = TD.languages.getSystemLanguageCode;
|
|
|
|
TD.languages.getSystemLanguageCode = function(returnShortCode){
|
|
return returnShortCode ? ($TDX.translationTarget || "en") : prevFunc.apply(this, arguments);
|
|
};
|
|
}
|
|
|
|
//
|
|
// Block: Add missing languages for Bing Translator (Bengali, Icelandic, Tagalog, Tamil, Telugu, Urdu).
|
|
//
|
|
execSafe(function addMissingTranslationLanguages(){
|
|
ensurePropertyExists(TD, "languages", "getSupportedTranslationSourceLanguages");
|
|
|
|
const newCodes = [ "bn", "is", "tl", "ta", "te", "ur" ];
|
|
const codeSet = new Set(TD.languages.getSupportedTranslationSourceLanguages());
|
|
|
|
for(const lang of newCodes){
|
|
codeSet.add(lang);
|
|
}
|
|
|
|
const codeList = [...codeSet];
|
|
|
|
TD.languages.getSupportedTranslationSourceLanguages = function(){
|
|
return codeList;
|
|
};
|
|
});
|
|
|
|
//
|
|
// Block: Bypass t.co when clicking/dragging links, media, and in user profiles.
|
|
//
|
|
execSafe(function setupShortenerBypass(){
|
|
$(document.body).delegate("a[data-full-url]", "click auxclick", function(e){
|
|
// event.which seems to be borked in auxclick
|
|
// tweet links open directly in the column
|
|
if ((e.button === 0 || e.button === 1) && $(this).attr("rel") !== "tweet"){
|
|
$TD.openBrowser($(this).attr("data-full-url"));
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
|
|
$(document.body).delegate("a[data-full-url]", "dragstart", function(e){
|
|
const url = $(this).attr("data-full-url");
|
|
const data = e.originalEvent.dataTransfer;
|
|
|
|
data.clearData();
|
|
data.setData("text/uri-list", url);
|
|
data.setData("text/plain", url);
|
|
data.setData("text/html", `<a href="${url}">${url}</a>`);
|
|
});
|
|
|
|
if (checkPropertyExists(TD, "services", "TwitterStatus", "prototype", "_generateHTMLText")){
|
|
TD.services.TwitterStatus.prototype._generateHTMLText = prependToFunction(TD.services.TwitterStatus.prototype._generateHTMLText, function(){
|
|
const card = this.card;
|
|
const entities = this.entities;
|
|
return if !(card && entities);
|
|
|
|
const urls = entities.urls;
|
|
return if !(urls && urls.length);
|
|
|
|
const shortUrl = card.url;
|
|
const urlObj = entities.urls.find(obj => obj.url === shortUrl && obj.expanded_url);
|
|
|
|
if (urlObj){
|
|
const expandedUrl = urlObj.expanded_url;
|
|
card.url = expandedUrl;
|
|
|
|
const values = card.binding_values;
|
|
|
|
if (values && values.card_url){
|
|
values.card_url.string_value = expandedUrl;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
if (checkPropertyExists(TD, "services", "TwitterMedia", "prototype", "fromMediaEntity")){
|
|
const prevFunc = TD.services.TwitterMedia.prototype.fromMediaEntity;
|
|
|
|
TD.services.TwitterMedia.prototype.fromMediaEntity = function(){
|
|
const obj = prevFunc.apply(this, arguments);
|
|
const e = arguments[0];
|
|
|
|
if (e.expanded_url){
|
|
if (obj.url === obj.shortUrl){
|
|
obj.shortUrl = e.expanded_url;
|
|
}
|
|
|
|
obj.url = e.expanded_url;
|
|
}
|
|
|
|
return obj;
|
|
};
|
|
}
|
|
|
|
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);
|
|
const e = arguments[0].entities;
|
|
|
|
if (e && e.url && e.url.urls && e.url.urls.length && e.url.urls[0].expanded_url){
|
|
obj.url = e.url.urls[0].expanded_url;
|
|
}
|
|
|
|
return obj;
|
|
};
|
|
}
|
|
});
|
|
|
|
//
|
|
// Block: Fix youtu.be previews not showing up for https links.
|
|
//
|
|
execSafe(function fixYouTubePreviews(){
|
|
ensurePropertyExists(TD, "services", "TwitterMedia");
|
|
|
|
const media = TD.services.TwitterMedia;
|
|
|
|
ensurePropertyExists(media, "YOUTUBE_TINY_RE");
|
|
ensurePropertyExists(media, "YOUTUBE_LONG_RE");
|
|
ensurePropertyExists(media, "YOUTUBE_RE");
|
|
ensurePropertyExists(media, "SERVICES", "youtube");
|
|
|
|
media.YOUTUBE_TINY_RE = new RegExp(media.YOUTUBE_TINY_RE.source.replace("http:", "https?:"));
|
|
media.YOUTUBE_RE = new RegExp(media.YOUTUBE_LONG_RE.source + "|" + media.YOUTUBE_TINY_RE.source);
|
|
media.SERVICES["youtube"] = media.YOUTUBE_RE;
|
|
});
|
|
|
|
//
|
|
// Block: Refocus the textbox after switching accounts.
|
|
//
|
|
onAppReady.push(function setupAccountSwitchRefocus(){
|
|
const refocusInput = function(){
|
|
document.querySelector(".js-docked-compose .js-compose-text").focus();
|
|
};
|
|
|
|
const accountItemClickEvent = function(e){
|
|
setTimeout(refocusInput, 0);
|
|
};
|
|
|
|
$(document).on("tduckOldComposerActive", function(e){
|
|
$$(".js-account-list", ".js-docked-compose").delegate(".js-account-item", "click", accountItemClickEvent);
|
|
});
|
|
});
|
|
|
|
//
|
|
// Block: Fix docked composer not re-focusing after Alt+Tab & image upload.
|
|
//
|
|
onAppReady.push(function fixDockedComposerRefocus(){
|
|
$(document).on("tduckOldComposerActive", function(e){
|
|
const ele = $$(".js-compose-text", ".js-docked-compose");
|
|
const node = ele[0];
|
|
|
|
let cancelBlur = false;
|
|
|
|
ele.on("blur", function(e){
|
|
cancelBlur = true;
|
|
setTimeout(function(){ cancelBlur = false; }, 0);
|
|
});
|
|
|
|
window.TDGF_prioritizeNewestEvent(node, "blur");
|
|
|
|
node.blur = prependToFunction(node.blur, function(){
|
|
return cancelBlur;
|
|
});
|
|
});
|
|
|
|
ensureEventExists(document, "uiComposeImageAdded");
|
|
|
|
$(document).on("uiComposeImageAdded", function(){
|
|
document.querySelector(".js-docked-compose .js-compose-text").focus();
|
|
});
|
|
});
|
|
|
|
//
|
|
// Block: Fix DM reply input box not getting focused after opening a conversation.
|
|
//
|
|
if (checkPropertyExists(TD, "components", "ConversationDetailView", "prototype", "showChirp")){
|
|
TD.components.ConversationDetailView.prototype.showChirp = appendToFunction(TD.components.ConversationDetailView.prototype.showChirp, function(){
|
|
return if !$TDX.focusDmInput;
|
|
|
|
setTimeout(function(){
|
|
document.querySelector(".js-reply-tweetbox").focus();
|
|
}, 100);
|
|
});
|
|
}
|
|
|
|
//
|
|
// Block: Hold Shift to restore cleared column.
|
|
//
|
|
execSafe(function supportShiftToClearColumn(){
|
|
ensurePropertyExists(TD, "vo", "Column", "prototype", "clear");
|
|
|
|
let holdingShift = false;
|
|
|
|
const updateShiftState = (pressed) => {
|
|
if (pressed != holdingShift){
|
|
holdingShift = pressed;
|
|
$("button[data-action='clear']").children("span").text(holdingShift ? "Restore" : "Clear");
|
|
}
|
|
};
|
|
|
|
const resetActiveFocus = () => {
|
|
document.activeElement.blur();
|
|
};
|
|
|
|
document.addEventListener("keydown", function(e){
|
|
if (e.shiftKey && (document.activeElement === null || !("value" in document.activeElement))){
|
|
updateShiftState(true);
|
|
}
|
|
});
|
|
|
|
document.addEventListener("keyup", function(e){
|
|
if (!e.shiftKey){
|
|
updateShiftState(false);
|
|
}
|
|
});
|
|
|
|
TD.vo.Column.prototype.clear = prependToFunction(TD.vo.Column.prototype.clear, function(){
|
|
window.setTimeout(resetActiveFocus, 0); // unfocuses the Clear button, otherwise it steals keyboard input
|
|
|
|
if (holdingShift){
|
|
this.model.setClearedTimestamp(0);
|
|
this.reloadTweets();
|
|
return true;
|
|
}
|
|
});
|
|
});
|
|
|
|
//
|
|
// Block: Make temporary search column appear as the first one and clear the input box.
|
|
//
|
|
execSafe(function setupSearchColumnHook(){
|
|
ensurePropertyExists(TD, "controller", "columnManager", "_columnOrder");
|
|
ensurePropertyExists(TD, "controller", "columnManager", "move");
|
|
|
|
$(document).on("uiSearchNoTemporaryColumn", function(e, data){
|
|
if (data.query && data.searchScope !== "users" && !data.columnKey){
|
|
if ($TDX.openSearchInFirstColumn){
|
|
const order = TD.controller.columnManager._columnOrder;
|
|
|
|
if (order.length > 1){
|
|
const columnKey = order[order.length - 1];
|
|
|
|
order.splice(order.length - 1, 1);
|
|
order.splice(1, 0, columnKey);
|
|
TD.controller.columnManager.move(columnKey, "left");
|
|
}
|
|
}
|
|
|
|
if (!("tduckResetInput" in data)){
|
|
$(".js-app-search-input").val("");
|
|
$(".js-perform-search").blur();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
//
|
|
// Block: Reorder search results to move accounts above hashtags.
|
|
//
|
|
onAppReady.push(function reorderSearchResults(){
|
|
const container = $(".js-search-in-popover");
|
|
const hashtags = $$(".js-typeahead-topic-list", container);
|
|
|
|
$$(".js-typeahead-user-list", container).insertBefore(hashtags);
|
|
hashtags.addClass("list-divider");
|
|
});
|
|
|
|
//
|
|
// Block: Revert Like/Follow dialogs being closed after clicking an action.
|
|
//
|
|
execSafe(function setupLikeFollowDialogRevert(){
|
|
const prevSetTimeout = window.setTimeout;
|
|
|
|
const overrideState = function(){
|
|
return if !$TDX.keepLikeFollowDialogsOpen;
|
|
|
|
window.setTimeout = function(func, timeout){
|
|
return timeout !== 500 && prevSetTimeout.apply(this, arguments);
|
|
};
|
|
};
|
|
|
|
const restoreState = function(context, key){
|
|
window.setTimeout = prevSetTimeout;
|
|
|
|
if ($TDX.keepLikeFollowDialogsOpen && key in context.state){
|
|
context.state[key] = false;
|
|
}
|
|
};
|
|
|
|
$(document).on("uiShowFavoriteFromOptions", function(){
|
|
$(".js-btn-fav", ".js-modal-inner").each(function(){
|
|
let event = $._data(this, "events").click[0];
|
|
let handler = event.handler;
|
|
|
|
event.handler = function(){
|
|
overrideState();
|
|
handler.apply(this, arguments);
|
|
restoreState($._data(document, "events").dataFavoriteState[0].handler.context, "stopSubsequentLikes");
|
|
};
|
|
});
|
|
});
|
|
|
|
$(document).on("uiShowFollowFromOptions", function(){
|
|
$(".js-component", ".js-modal-inner").each(function(){
|
|
let event = $._data(this, "events").click[0];
|
|
let handler = event.handler;
|
|
let context = handler.context;
|
|
|
|
event.handler = function(){
|
|
overrideState();
|
|
handler.apply(this, arguments);
|
|
restoreState(context, "stopSubsequentFollows");
|
|
};
|
|
});
|
|
});
|
|
});
|
|
|
|
//
|
|
// Block: Fix broken horizontal scrolling of column container when holding Shift.
|
|
//
|
|
if (checkPropertyExists(TD, "ui", "columns", "setupColumnScrollListeners")){
|
|
TD.ui.columns.setupColumnScrollListeners = appendToFunction(TD.ui.columns.setupColumnScrollListeners, function(column){
|
|
const ele = document.querySelector(".js-column[data-column='" + column.model.getKey() + "']");
|
|
return if ele == null;
|
|
|
|
$(ele).off("onmousewheel").on("mousewheel", ".scroll-v", function(e){
|
|
e.stopImmediatePropagation();
|
|
});
|
|
|
|
window.TDGF_prioritizeNewestEvent(ele, "mousewheel");
|
|
});
|
|
}
|
|
|
|
//
|
|
// Block: Fix DM image previews and GIF thumbnails not loading due to new URLs.
|
|
//
|
|
if (checkPropertyExists(TD, "services", "TwitterMedia", "prototype", "getTwitterPreviewUrl")){
|
|
const prevFunc = TD.services.TwitterMedia.prototype.getTwitterPreviewUrl;
|
|
|
|
TD.services.TwitterMedia.prototype.getTwitterPreviewUrl = function(){
|
|
const url = prevFunc.apply(this, arguments);
|
|
|
|
if (url.startsWith("https://ton.twitter.com/1.1/ton/data/dm/") || url.startsWith("https://pbs.twimg.com/tweet_video_thumb/")){
|
|
const format = url.match(/\?.*format=(\w+)/);
|
|
|
|
if (format && format.length === 2){
|
|
const fix = `.${format[1]}?`;
|
|
|
|
if (!url.includes(fix)){
|
|
return url.replace("?", fix);
|
|
}
|
|
}
|
|
}
|
|
|
|
return url;
|
|
};
|
|
}
|
|
|
|
//
|
|
// Block: Fix DMs not being marked as read when replying to them.
|
|
//
|
|
execSafe(function markRepliedDMsAsRead(){
|
|
ensurePropertyExists(TD, "controller", "clients", "getClient");
|
|
ensurePropertyExists(TD, "services", "Conversations", "prototype", "getConversation");
|
|
|
|
$(document).on("dataDmSent", function(e, data){
|
|
const client = TD.controller.clients.getClient(data.request.accountKey);
|
|
return if !client;
|
|
|
|
const conversation = client.conversations.getConversation(data.request.conversationId);
|
|
return if !conversation;
|
|
|
|
conversation.markAsRead();
|
|
});
|
|
});
|
|
|
|
//
|
|
// Block: Limit amount of loaded DMs to avoid massive lag from re-opening them several times.
|
|
//
|
|
if (checkPropertyExists(TD, "services", "TwitterConversation", "prototype", "renderThread")){
|
|
const prevFunc = TD.services.TwitterConversation.prototype.renderThread;
|
|
|
|
TD.services.TwitterConversation.prototype.renderThread = function(){
|
|
const prevMessages = this.messages;
|
|
|
|
this.messages = prevMessages.slice(0, 100);
|
|
const result = prevFunc.apply(this, arguments);
|
|
this.messages = prevMessages;
|
|
|
|
return result;
|
|
};
|
|
}
|
|
|
|
//
|
|
// Block: Fix scheduled tweets not showing up sometimes.
|
|
//
|
|
execSafe(function fixScheduledTweets(){
|
|
ensurePropertyExists(TD, "controller", "columnManager", "getAll");
|
|
ensureEventExists(document, "dataTweetSent");
|
|
|
|
$(document).on("dataTweetSent", function(e, data){
|
|
if (data.response.state && data.response.state === "scheduled"){
|
|
const column = Object.values(TD.controller.columnManager.getAll()).find(column => column.model.state.type === "scheduled");
|
|
return if !column;
|
|
|
|
setTimeout(function(){
|
|
column.reloadTweets();
|
|
}, 1000);
|
|
}
|
|
});
|
|
});
|
|
|
|
//
|
|
// Block: Let's make retweets lowercase again.
|
|
//
|
|
window.TDGF_injectMustache("status/tweet_single.mustache", "replace", "{{_i}} Retweeted{{/i}}", "{{_i}} retweeted{{/i}}");
|
|
|
|
if (checkPropertyExists(TD, "services", "TwitterActionRetweet", "prototype", "generateText")){
|
|
TD.services.TwitterActionRetweet.prototype.generateText = appendToFunction(TD.services.TwitterActionRetweet.prototype.generateText, function(){
|
|
this.text = this.text.replace(" Retweeted", " retweeted");
|
|
this.htmlText = this.htmlText.replace(" Retweeted", " retweeted");
|
|
});
|
|
}
|
|
|
|
if (checkPropertyExists(TD, "services", "TwitterActionRetweetedInteraction", "prototype", "generateText")){
|
|
TD.services.TwitterActionRetweetedInteraction.prototype.generateText = appendToFunction(TD.services.TwitterActionRetweetedInteraction.prototype.generateText, function(){
|
|
this.htmlText = this.htmlText.replace(" Retweeted", " retweeted").replace(" Retweet", " retweet");
|
|
});
|
|
}
|