feat(iRememberYou): Give UIDs and GIDs interactivity (#601)

* feat(iRememberYou): implement complete interactivity for UIDs and GIDs

- add lmb copy functionality for uids with toast
- add rmb context menu for uids and group headers
- add invite link storage and permission aware collection for groups

* fix: update username in EquicordDevs array
This commit is contained in:
Keiran 2025-12-26 16:45:37 +00:00 • committed by GitHub
parent 37da8ded0a
commit 189b488027
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 90 additions and 12 deletions

View file

@ -6,7 +6,7 @@
import { DataStore } from "@api/index";
import { Guild, User } from "@vencord/discord-types";
import { ChannelStore, GuildMemberStore, GuildStore, MessageStore, UserStore, } from "@webpack/common";
import { ChannelStore, GuildMemberStore, GuildStore, InviteActions, MessageStore, PermissionStore, UserStore, } from "@webpack/common";
export interface IUserExtra {
isOwner?: boolean;
@ -25,6 +25,7 @@ export interface GroupData {
id: string;
users: { [key: string]: IStorageUser; };
name: string;
inviteLink?: string;
}
export class Data {
@ -78,6 +79,8 @@ export class Data {
array: { user: User; source?: Guild; extra?: IUserExtra; }[]
) {
const target = this.usersCollection;
const processedGuilds = new Set<string>();
for (const { user, source, extra } of array) {
if (user.bot) {
continue;
@ -87,7 +90,8 @@ export class Data {
const group = (target[groupKey] ||= {
name: source?.name || "dm",
id: source?.id || user.id,
users: {}
users: {},
inviteLink: undefined
});
const usersField = group.users;
const previouExtra = usersField[user.id]?.extra ?? {};
@ -100,6 +104,11 @@ export class Data {
extra: { ...previouExtra, ...extra },
iconURL: user.getAvatarURL(),
};
if (source && !processedGuilds.has(source.id) && this.hasInvitePermissions(source.id)) {
this.collectInviteLink(source.id);
processedGuilds.add(source.id);
}
}
}
@ -138,7 +147,11 @@ export class Data {
target.add({ user, source: guild, extra: { updatedAt: now } });
}
this.processUsersToCollection([...target]);
this.processUsersToCollection([...target]);
if (guild && this.hasInvitePermissions(guild.id)) {
this.collectInviteLink(guild.id);
}
}
}
@ -163,6 +176,38 @@ export class Data {
this.processUsersToCollection([...target]);
}
hasInvitePermissions(guildId: string): boolean {
const guild = GuildStore.getGuild(guildId);
if (!guild) return false;
const currentUser = UserStore.getCurrentUser();
if (!currentUser) return false;
if (guild.ownerId === currentUser.id) return true;
const member = GuildMemberStore.getMember(guildId, currentUser.id);
if (!member) return false;
return PermissionStore.can(PermissionStore.CREATE_INSTANT_INVITE, guild.id);
}
async collectInviteLink(guildId: string) {
try {
const invites = await InviteActions.getInvites(guildId);
if (invites && invites.length > 0) {
const invite = invites[0];
const inviteCode = invite.code;
const inviteLink = `https://discord.gg/${inviteCode}`;
if (this.usersCollection[guildId]) {
this.usersCollection[guildId].inviteLink = inviteLink;
}
}
} catch (error) {
console.warn("Failed to collect invite link for guild:", guildId, error);
}
}
storageAutoSaveProtocol() {
this._storageAutoSaveProtocol_interval = setInterval(
this.updateStorage.bind(this),

View file

@ -10,8 +10,8 @@ import { HeadingPrimary } from "@components/Heading";
import { Paragraph } from "@components/Paragraph";
import { SettingsTab, wrapTab } from "@components/settings";
import { classNameFactory } from "@utils/css";
import { openUserProfile } from "@utils/discord";
import { Avatar, Clickable, React, TextInput, Tooltip, } from "@webpack/common";
import { copyWithToast, openUserProfile } from "@utils/discord";
import { Avatar, Clickable, ContextMenuApi, Menu, React, TextInput, Tooltip, UserStore } from "@webpack/common";
import { Data, IStorageUser } from "./data";
@ -66,10 +66,38 @@ function UserRow({ user, allowOwner = true }: { user: IStorageUser, allowOwner?:
</Paragraph>
}
</Tooltip>
<span className={cl("user-id")}>
<Paragraph>
{user.id}
</Paragraph>
<span
className={cl("user-id")}
onContextMenu={e => {
e.preventDefault();
const userObj = UserStore.getUser(user.id);
if (userObj) {
ContextMenuApi.openContextMenu(e, () => (
<Menu.Menu
navId="user-context-menu"
onClose={ContextMenuApi.closeContextMenu}
aria-label="User Options"
>
<Menu.MenuItem
id="copy-user-id"
label="Copy User ID"
action={() => copyWithToast(user.id, "User ID copied to clipboard")}
/>
<Menu.MenuItem
id="view-profile"
label="View Profile"
action={() => openUserProfile(user.id)}
/>
</Menu.Menu>
));
}
}}
>
<Clickable onClick={() => copyWithToast(user.id, "User ID copied to clipboard")}>
<Paragraph>
{user.id}
</Paragraph>
</Clickable>
</span>
</div>
</div>

View file

@ -18,7 +18,7 @@ import DataUI from "./components/ui";
export default definePlugin({
name: "IRememberYou",
description: "Locally saves everyone you've been communicating with (including servers), in case of lose",
authors: [EquicordDevs.zoodogood],
authors: [EquicordDevs.zoodogood, EquicordDevs.keyages],
dependencies: ["MessageEventsAPI"],
patches: [],

View file

@ -51,6 +51,11 @@
align-self: flex-start;
opacity: 0.75;
height: min-content;
cursor: pointer;
}
.vc-i-remember-you-user-id:hover {
opacity: 1;
}
.vc-i-remember-you-user {

View file

@ -91,7 +91,7 @@ async function react(channelId: string, messageId: string, emoji: string) {
export default definePlugin({
name: "MessageClickActions",
description: "Hold Backspace and click to delete, double click to edit/reply, triple click to react",
authors: [Devs.Ven, EquicordDevs.keircn],
authors: [Devs.Ven, EquicordDevs.keyages],
settings,

View file

@ -1221,7 +1221,7 @@ export const EquicordDevs = Object.freeze({
name: "Benjii",
id: 463702169443368970n
},
keircn: {
keyages: {
name: "Key",
id: 1230319937155760131n
},