Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/**
* Name: FriendMe
* Version: v2.0
* Author: t7ru [[User:Gabonnie]]
* Description: A simple system to add friends to your Fandom profile!
*/
(() => {
"use strict";
// prevents the script from reruns
if (window.friendsModuleLoaded) return;
window.friendsModuleLoaded = true;
const API_BASE_URL = "https://services.toru.ca";
const namespaces = [2, 500, 1200, 3]; // User, Message Wall, User blog, User talk
const currentNamespace = mw.config.get("wgNamespaceNumber");
const profileUserId = Number(mw.config.get("profileUserId"));
const currentUserId = mw.config.get("wgUserId");
if (!namespaces.includes(currentNamespace) || !profileUserId) return;
// logic (a bunch of helpers and whatnot)
const parseFriendIds = (userData) => {
const friends = (userData && userData.friends) || [];
return friends.map((id) => id.toString()).filter((id) => /^\d+$/.test(id));
};
const apiRequest = (endpoint, method = "GET", data = null) => {
return $.ajax({
url: `${API_BASE_URL}${endpoint}`,
type: method,
contentType: data ? "application/json" : undefined,
data: data ? JSON.stringify(data) : undefined,
xhrFields: {
withCredentials: false
}
});
};
const fetchFriendsData = (friendIds) => {
if (!friendIds.length) return $.Deferred().resolve({
users: {}
});
const query = friendIds.map((id) => `id=${id}`).join("&");
return apiRequest(`/user/bulk?${query}`);
};
const updateFriendButton = (profileData, currentUserData) => {
const $btn = $(".friends-action-btn");
if (!$btn.length) return;
const theirFriendIds = parseFriendIds(profileData);
const myFriendIds = parseFriendIds(currentUserData);
const theyHaveMe = theirFriendIds.includes(currentUserId.toString());
const iHaveThem = myFriendIds.includes(profileUserId.toString());
let config = {
text: "Send Friend Request",
action: "send",
class: ""
};
if (iHaveThem && theyHaveMe) {
config = {
text: "Remove Friend",
action: "remove",
class: "wds-is-secondary"
};
} else if (iHaveThem) {
config = {
text: "Cancel Friend Request",
action: "cancel",
class: "wds-is-secondary"
};
} else if (theyHaveMe) {
config = {
text: "Accept Friend Request",
action: "accept",
class: ""
};
}
$btn.text(config.text)
.attr("data-action", config.action)
.removeClass("wds-is-secondary")
.addClass(config.class)
.prop("disabled", false);
};
const handleFriendButtonClick = function(e) {
e.preventDefault();
const $btn = $(this);
const action = $btn.attr("data-action");
$btn.prop("disabled", true).text("Processing...");
apiRequest(`/user/${currentUserId}`).done((currentUserData) => {
let myFriendIds = parseFriendIds(currentUserData);
if (action === "send" || action === "accept") {
if (!myFriendIds.includes(profileUserId.toString())) {
myFriendIds.push(profileUserId.toString());
}
} else {
myFriendIds = myFriendIds.filter((id) => id !== profileUserId.toString());
}
apiRequest(`/user/${currentUserId}`, "PATCH", {
friends: myFriendIds
})
.done(() => location.reload())
.fail(() => $btn.prop("disabled", false).text("Error - Try Again"));
});
};
// build rail module
const createFriendsModule = (friendsData) => {
const users = friendsData.users || {};
const mutualFriends = Object.keys(users)
.map(id => users[id])
.filter(user => parseFriendIds(user).includes(profileUserId.toString()));
let html = `
<section class="rail-module" id="friends-module">
<h2 class="rail-module__header has-icon">
<svg class="wds-icon wds-icon-small" width="18" height="18" viewBox="0 0 18 18"><path d="M12 8.25c1.245 0 2.243-1.005 2.243-2.25S13.245 3.75 12 3.75c-1.245 0-2.25 1.005-2.25 2.25S10.755 8.25 12 8.25zm-6 0c1.245 0 2.243-1.005 2.243-2.25S7.245 3.75 6 3.75c-1.245 0-2.25 1.005-2.25 2.25S4.755 8.25 6 8.25zm0 1.5c-1.748 0-5.25.878-5.25 2.625V14.25h10.5v-1.875c0-1.747-3.502-2.625-5.25-2.625zm6 0c-.218 0-.465.015-.728.038.87.63 1.478 1.478 1.478 2.587v1.875h4.5v-1.875c0-1.747-3.502-2.625-5.25-2.625z"/></svg>
Friends
</h2>`;
if (mutualFriends.length === 0) {
html += '<div class="friends-empty-container"><p class="friends-empty">No friends added yet.</p></div>';
} else {
html += '<ul class="rail-module__list friends-items">';
mutualFriends.forEach((user) => {
const name = user.username || "Unknown User";
const avatar = user.avatar || "https://vignette.wikia.nocookie.net/messaging/images/1/19/Avatar.jpg";
html += `
<li class="friends-item">
<a href="/wiki/User:${encodeURIComponent(name)}" class="friends-link">
<img src="${avatar}" alt="${name}" class="friends-avatar" />
<span class="friends-username">${mw.html.escape(name)}</span>
</a>
</li>`;
});
html += "</ul>";
}
if (currentUserId && currentUserId !== profileUserId) {
html += '<hr><div class="friends-action-container"><button class="wds-button wds-is-secondary friends-action-btn">Loading...</button></div>';
}
return html + "</section>";
};
// initilization
const init = () => {
$.when(
apiRequest(`/user/${profileUserId}`),
currentUserId ? apiRequest(`/user/${currentUserId}`) : $.Deferred().resolve(null)
).done((profileRes, currentRes) => {
const profileData = profileRes[0];
const currentUserData = currentRes ? currentRes[0] : null;
fetchFriendsData(parseFriendIds(profileData)).done((friendsData) => {
const moduleHtml = createFriendsModule(friendsData);
mw.hook("fandom.rightrail.loaded").add(() => {
const $rail = $("#WikiaRail");
if (!$rail.length) return;
$rail.prepend(moduleHtml);
if (currentUserId && currentUserId !== profileUserId) {
updateFriendButton(profileData, currentUserData);
$(document).on("click", ".friends-action-btn", handleFriendButtonClick);
}
});
});
});
};
importArticle({
type: "style",
article: "u:dev:MediaWiki:FriendMe.css"
});
if (document.readyState === "loading") {
$(document).ready(init);
} else {
init();
}
})();