Skip to content

Commit b1cbf31

Browse files
authored
[FORUMS] Add button to automatically post an unapproved reply (#109)
* Allow specification of reload in setPostApprovalStatus * Separate XFToken into function * Basic untested edit function * Add silent and clearEdits * Find via CSRF vs. input * Prettify * First working version * Add warning for rich editor * Add separate feature checkbox
1 parent f053b63 commit b1cbf31

File tree

1 file changed

+150
-9
lines changed

1 file changed

+150
-9
lines changed

src/EGO Forum Enhancement.ts

Lines changed: 150 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// ==UserScript==
22
// @name EdgeGamers Forum Enhancement%RELEASE_TYPE%
33
// @namespace https://github.com/blankdvth/eGOScripts/blob/master/src/EGO%20Forum%20Enhancement.ts
4-
// @version 4.10.1
4+
// @version 4.11.1
55
// @description Add various enhancements & QOL additions to the EdgeGamers Forums that are beneficial for Leadership members.
66
// @author blank_dvth, Skle, MSWS, PixeL
77
// @match https://www.edgegamers.com/*
@@ -207,6 +207,18 @@ function setupForumsConfig() {
207207
type: "text",
208208
default: "",
209209
},
210+
"enable-post-unapprove-btn": {
211+
label: "Enable post & unapprove button",
212+
title: "Whether to add a button that will safely post an unapproved reply.",
213+
type: "checkbox",
214+
default: true,
215+
},
216+
"rich-override": {
217+
label: "Allow post & unapprove in rich editor (NOT SUPPORTED)",
218+
title: "The post & unapprove button in the rich editor is not supported, and will cause formatting issues in your message. If you want to use it anyway, check this box, no support will be provided.",
219+
type: "checkbox",
220+
default: false,
221+
},
210222
"move-to-completed-unchecked": {
211223
label: "Completed Forums Map",
212224
section: [
@@ -973,16 +985,14 @@ function addPostActionBarButtons() {
973985
async function setPostApprovalStatus(
974986
threadId: string,
975987
postId: string,
976-
approve: boolean
988+
approve: boolean,
989+
reload: boolean = true
977990
) {
978-
const xfTokenElement = document.querySelector(
979-
"input[name='_xfToken']"
980-
) as HTMLInputElement;
981-
if (!xfTokenElement) {
982-
console.error("Failed to find _xfToken input");
991+
const xfToken = getXFToken();
992+
if (!xfToken) {
993+
console.error("Failed to get XF token");
983994
return;
984995
}
985-
const xfToken = xfTokenElement.value;
986996

987997
// cookies cannot be set in the request unfortunately.
988998
document.cookie = `xf_inlinemod_post=${postId}; Path=/; Secure=true;`;
@@ -1037,7 +1047,51 @@ async function setPostApprovalStatus(
10371047
return;
10381048
}
10391049

1040-
window.location.reload();
1050+
if (reload) window.location.reload();
1051+
}
1052+
1053+
async function editPost(
1054+
threadId: string,
1055+
postId: string,
1056+
message: string,
1057+
silent: boolean = false,
1058+
clearEdits: boolean = false
1059+
) {
1060+
const xfToken = getXFToken();
1061+
if (!xfToken) {
1062+
console.error("Failed to get XF token");
1063+
return;
1064+
}
1065+
1066+
const formdata = new FormData();
1067+
formdata.append("_xfToken", xfToken);
1068+
formdata.append("_xfInlineEdit", "1");
1069+
formdata.append("_xfRequestUri", `/threads/${threadId}/`);
1070+
formdata.append("_xfWithData", "1");
1071+
formdata.append("_xfResponseType", "json");
1072+
formdata.append("message", message);
1073+
if (silent) formdata.append("silent", "1");
1074+
if (clearEdits) formdata.append("clear_edit", "1");
1075+
1076+
const response = await fetch(
1077+
`https://www.edgegamers.com/posts/${postId}/edit`,
1078+
{
1079+
method: "POST",
1080+
credentials: "same-origin",
1081+
body: formdata,
1082+
}
1083+
);
1084+
if (!response.ok) {
1085+
console.error("Failed to edit post");
1086+
return;
1087+
}
1088+
1089+
const data = await response.json();
1090+
if (data.status != "ok") {
1091+
console.error("Server rejected post edit");
1092+
console.log(data);
1093+
return;
1094+
}
10411095
}
10421096

10431097
/**
@@ -1242,6 +1296,10 @@ function getSteamID_F(unparsed_id: string): Promise<string> {
12421296
});
12431297
}
12441298

1299+
function getXFToken() {
1300+
return document.getElementById("XF")?.dataset.csrf;
1301+
}
1302+
12451303
/**
12461304
* Add or append text to the post editor box
12471305
* @param text Text to add to post box
@@ -1998,6 +2056,8 @@ function handlePostBox(observer: MutationObserver) {
19982056
)
19992057
handleAutoCount();
20002058
handleCannedResponses();
2059+
if (GM_config.get("enable-post-unapprove-btn"))
2060+
handleUnapprovePost(postBox);
20012061
}
20022062

20032063
/**
@@ -2138,6 +2198,87 @@ function handleCannedResponses() {
21382198
});
21392199
}
21402200

2201+
/**
2202+
* Handles adding a post & unapprove button to the postbox
2203+
*/
2204+
function handleUnapprovePost(postBox: HTMLDivElement) {
2205+
if (
2206+
!document.querySelector("#xfBbCode-1")?.classList.contains("fr-active")
2207+
) {
2208+
if (GM_config.get("rich-override"))
2209+
console.warn(
2210+
"Post box is in rich editor mode, but rich editor override is enabled. The rich editor is NOT supported."
2211+
);
2212+
else return;
2213+
}
2214+
const threadId = getThreadId();
2215+
const threadBody = document.querySelector(
2216+
"div.block-body.js-replyNewMessageContainer"
2217+
) as HTMLDivElement;
2218+
const postButton = document.querySelector(
2219+
"button.button--icon--reply:not(#post-unapprove-button)"
2220+
) as HTMLButtonElement;
2221+
const postUnapproveButton = document.createElement("a"); // Using a so that it doesn't submit the form
2222+
postUnapproveButton.classList.add(
2223+
"button--primary",
2224+
"button",
2225+
"button--icon",
2226+
"button--icon--reply"
2227+
);
2228+
postUnapproveButton.style.marginRight = "4px";
2229+
postUnapproveButton.id = "post-unapprove-button";
2230+
postUnapproveButton.innerHTML = '<span class="button-text">UA</span>';
2231+
postUnapproveButton.title = "Post & Unapprove";
2232+
2233+
postUnapproveButton.addEventListener("click", function () {
2234+
const postBoxContent = getPostBox();
2235+
if (!postBoxContent) return;
2236+
editPostBox("🐰🥚", false);
2237+
postButton.click();
2238+
2239+
const observer = new MutationObserver((mutations) => {
2240+
mutations.every((mutation) => {
2241+
if (!mutation.addedNodes) return true;
2242+
for (let i = 0; i < mutation.addedNodes.length; i++) {
2243+
const node = mutation.addedNodes[i] as HTMLElement;
2244+
if (node.nodeName === "ARTICLE") {
2245+
const postId = node.dataset.content?.replaceAll(
2246+
"post-",
2247+
""
2248+
);
2249+
if (threadId && postId)
2250+
setPostApprovalStatus(
2251+
threadId,
2252+
postId,
2253+
false,
2254+
false
2255+
).then(() => {
2256+
editPost(
2257+
threadId,
2258+
postId,
2259+
postBoxContent,
2260+
true
2261+
).then(() => {
2262+
window.location.reload();
2263+
});
2264+
});
2265+
observer.disconnect();
2266+
return false;
2267+
}
2268+
}
2269+
});
2270+
});
2271+
observer.observe(threadBody, {
2272+
childList: true,
2273+
subtree: true,
2274+
attributes: false,
2275+
characterData: false,
2276+
});
2277+
});
2278+
2279+
postButton.parentElement?.insertBefore(postUnapproveButton, postButton);
2280+
}
2281+
21412282
/**
21422283
* Changes the target of the application links to open in a new tab
21432284
* @returns void

0 commit comments

Comments
 (0)