Skip to content

Commit

Permalink
meow
Browse files Browse the repository at this point in the history
  • Loading branch information
yofukashino committed Apr 26, 2024
1 parent 4b7d827 commit 3869c44
Show file tree
Hide file tree
Showing 14 changed files with 159 additions and 128 deletions.
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"lint:fix": "pnpm run prettier:fix && pnpm run eslint:fix"
},
"keywords": [],
"author": "",
"author": "Nanakusa",
"license": "ISC",
"devDependencies": {
"@types/node": "^18.16.5",
Expand All @@ -34,7 +34,6 @@
"eslint-plugin-react": "^7.32.2",
"prettier": "^2.8.8",
"replugged": "^4.7.8",
"type-fest": "^3.10.0",
"typescript": "^5.0.4"
}
}
12 changes: 0 additions & 12 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

99 changes: 63 additions & 36 deletions src/Components/Icons.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,67 @@
import { React } from "replugged/common";
export const play = ({
children,
...props
}: React.SVGProps<SVGSVGElement> & { children?: React.ReactNode }): React.ReactElement => (
<svg
{...props}
viewBox="0 0 24 24"
width={props.width ?? "24"}
height={props.height ?? "24"}
style={props.style}>
<path
style={{
fill: "currentColor",
}}
d="M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm8.856-3.845A1.25 1.25 0 0 0 9 9.248v5.504a1.25 1.25 0 0 0 1.856 1.093l5.757-3.189a.75.75 0 0 0 0-1.312l-5.757-3.189Z"
/>
{children}
</svg>
);

export const play = React.memo((props: React.ComponentProps<"svg">): React.ReactElement => {
return (
<svg width="24" height="24" viewBox="0 0 24 24" {...props}>
<path
d="M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm8.856-3.845A1.25 1.25 0 0 0 9 9.248v5.504a1.25 1.25 0 0 0 1.856 1.093l5.757-3.189a.75.75 0 0 0 0-1.312l-5.757-3.189Z"
fill="currentColor"
/>
</svg>
);
});
export const queue = ({
children,
...props
}: React.SVGProps<SVGSVGElement> & { children?: React.ReactNode }): React.ReactElement => (
<svg
{...props}
viewBox="0 0 24 24"
width={props.width ?? "24"}
height={props.height ?? "24"}
style={props.style}>
<path
style={{
fill: "currentColor",
}}
d="M5 4C5 2.89543 5.89543 2 7 2H12.5001L12.5 6.74998C12.5 7.71649 13.2835 8.50002 14.25 8.50002H19V11.1739C18.5185 11.0602 18.0163 11 17.5 11C13.9101 11 11 13.9101 11 17.5C11 18.0163 11.0602 18.5185 11.1739 19H7C5.89543 19 5 18.1046 5 17V4ZM8 5.75C8 6.16421 8.33579 6.5 8.75 6.5H10.25C10.6642 6.5 11 6.16421 11 5.75C11 5.33579 10.6642 5 10.25 5H8.75C8.33579 5 8 5.33579 8 5.75ZM8.75 8C8.33579 8 8 8.33579 8 8.75C8 9.16421 8.33579 9.5 8.75 9.5H10.25C10.6642 9.5 11 9.16421 11 8.75C11 8.33579 10.6642 8 10.25 8H8.75ZM8 11.75C8 12.1642 8.33579 12.5 8.75 12.5H10.25C10.6642 12.5 11 12.1642 11 11.75C11 11.3358 10.6642 11 10.25 11H8.75C8.33579 11 8 11.3358 8 11.75ZM12.8096 22C12.3832 21.5557 12.0194 21.051 11.7322 20.5H6.5C4.84315 20.5 3.5 19.1569 3.5 17.5V15.75C3.5 15.3358 3.16421 15 2.75 15C2.33579 15 2 15.3358 2 15.75V17.5C2 19.9853 4.01472 22 6.5 22H12.8096ZM18.9995 7.00002L14.0001 2.00159L14 6.75001C14 6.88808 14.1119 7.00002 14.25 7.00002H18.9995ZM23 17.5C23 14.4624 20.5376 12 17.5 12C14.4624 12 12 14.4624 12 17.5C12 20.5376 14.4624 23 17.5 23C20.5376 23 23 20.5376 23 17.5ZM18.0006 18L18.0011 20.5035C18.0011 20.7797 17.7773 21.0035 17.5011 21.0035C17.225 21.0035 17.0011 20.7797 17.0011 20.5035L17.0006 18H14.4956C14.2197 18 13.9961 17.7762 13.9961 17.5C13.9961 17.2239 14.2197 17 14.4956 17H17.0005L17 14.4993C17 14.2231 17.2239 13.9993 17.5 13.9993C17.7761 13.9993 18 14.2231 18 14.4993L18.0005 17H20.4966C20.7725 17 20.9961 17.2239 20.9961 17.5C20.9961 17.7762 20.7725 18 20.4966 18H18.0006ZM12.5001 2L13.9985 2.00003Z"
/>
{children}
</svg>
);

export const queue = React.memo((props: React.ComponentProps<"svg">): React.ReactElement => {
return (
<svg width="24" height="24" viewBox="0 0 24 24" {...props}>
<path
d="M5 4C5 2.89543 5.89543 2 7 2H12.5001L12.5 6.74998C12.5 7.71649 13.2835 8.50002 14.25 8.50002H19V11.1739C18.5185 11.0602 18.0163 11 17.5 11C13.9101 11 11 13.9101 11 17.5C11 18.0163 11.0602 18.5185 11.1739 19H7C5.89543 19 5 18.1046 5 17V4ZM8 5.75C8 6.16421 8.33579 6.5 8.75 6.5H10.25C10.6642 6.5 11 6.16421 11 5.75C11 5.33579 10.6642 5 10.25 5H8.75C8.33579 5 8 5.33579 8 5.75ZM8.75 8C8.33579 8 8 8.33579 8 8.75C8 9.16421 8.33579 9.5 8.75 9.5H10.25C10.6642 9.5 11 9.16421 11 8.75C11 8.33579 10.6642 8 10.25 8H8.75ZM8 11.75C8 12.1642 8.33579 12.5 8.75 12.5H10.25C10.6642 12.5 11 12.1642 11 11.75C11 11.3358 10.6642 11 10.25 11H8.75C8.33579 11 8 11.3358 8 11.75ZM12.8096 22C12.3832 21.5557 12.0194 21.051 11.7322 20.5H6.5C4.84315 20.5 3.5 19.1569 3.5 17.5V15.75C3.5 15.3358 3.16421 15 2.75 15C2.33579 15 2 15.3358 2 15.75V17.5C2 19.9853 4.01472 22 6.5 22H12.8096ZM18.9995 7.00002L14.0001 2.00159L14 6.75001C14 6.88808 14.1119 7.00002 14.25 7.00002H18.9995ZM23 17.5C23 14.4624 20.5376 12 17.5 12C14.4624 12 12 14.4624 12 17.5C12 20.5376 14.4624 23 17.5 23C20.5376 23 23 20.5376 23 17.5ZM18.0006 18L18.0011 20.5035C18.0011 20.7797 17.7773 21.0035 17.5011 21.0035C17.225 21.0035 17.0011 20.7797 17.0011 20.5035L17.0006 18H14.4956C14.2197 18 13.9961 17.7762 13.9961 17.5C13.9961 17.2239 14.2197 17 14.4956 17H17.0005L17 14.4993C17 14.2231 17.2239 13.9993 17.5 13.9993C17.7761 13.9993 18 14.2231 18 14.4993L18.0005 17H20.4966C20.7725 17 20.9961 17.2239 20.9961 17.5C20.9961 17.7762 20.7725 18 20.4966 18H18.0006ZM12.5001 2L13.9985 2.00003Z"
fill="currentColor"
/>
</svg>
);
});

export const noLive = React.memo((props: React.ComponentProps<"svg">): React.ReactElement => {
return (
<svg width="24" height="24" viewBox="0 0 24 24" {...props}>
<path
fill="currentColor"
d="M3.28 2.22a.75.75 0 1 0-1.06 1.06l2.202 2.203c-3.392 3.93-3.223 9.872.506 13.601a1 1 0 0 0 1.415-1.414A8.004 8.004 0 0 1 5.84 6.902l1.521 1.52a5.922 5.922 0 0 0 .533 7.763A1 1 0 0 0 9.31 14.77a3.922 3.922 0 0 1-.513-4.913l1.836 1.836a1.5 1.5 0 0 0 1.838 1.838l8.25 8.25a.75.75 0 0 0 1.06-1.061L3.28 2.22ZM19.028 15.846l1.461 1.462c2.414-3.861 1.943-9.012-1.415-12.37a1 1 0 1 0-1.414 1.415 8.006 8.006 0 0 1 1.368 9.493Z"
/>
<path
fill="currentColor"
d="m15.93 12.748 1.59 1.591a5.922 5.922 0 0 0-1.252-6.527 1 1 0 1 0-1.415 1.414 3.916 3.916 0 0 1 1.077 3.522Z"
/>
</svg>
);
});
export const noLive = ({
children,
...props
}: React.SVGProps<SVGSVGElement> & { children?: React.ReactNode }): React.ReactElement => (
<svg
{...props}
viewBox="0 0 24 24"
width={props.width ?? "24"}
height={props.height ?? "24"}
style={props.style}>
<path
style={{
fill: "currentColor",
}}
d="M3.28 2.22a.75.75 0 1 0-1.06 1.06l2.202 2.203c-3.392 3.93-3.223 9.872.506 13.601a1 1 0 0 0 1.415-1.414A8.004 8.004 0 0 1 5.84 6.902l1.521 1.52a5.922 5.922 0 0 0 .533 7.763A1 1 0 0 0 9.31 14.77a3.922 3.922 0 0 1-.513-4.913l1.836 1.836a1.5 1.5 0 0 0 1.838 1.838l8.25 8.25a.75.75 0 0 0 1.06-1.061L3.28 2.22ZM19.028 15.846l1.461 1.462c2.414-3.861 1.943-9.012-1.415-12.37a1 1 0 1 0-1.414 1.415 8.006 8.006 0 0 1 1.368 9.493Z"
/>
<path
style={{
fill: "currentColor",
}}
d="m15.93 12.748 1.59 1.591a5.922 5.922 0 0 0-1.252-6.527 1 1 0 1 0-1.415 1.414 3.916 3.916 0 0 1 1.077 3.522Z"
/>
{children}
</svg>
);

export default { play, queue, noLive };
4 changes: 2 additions & 2 deletions src/Components/MenuItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const noAccounts = (): React.ReactElement => {
);
};

export const addToQueue = (SpotifyLinks: string[][]): React.ReactElement => {
export const queue = (SpotifyLinks: string[][]): React.ReactElement => {
if (SpotifyLinks.length === 1) {
const [, type, id, name] = SpotifyLinks[0];
return (
Expand Down Expand Up @@ -160,4 +160,4 @@ export const play = (SpotifyLinks: string[][]): React.ReactElement => {
);
};

export default { noAccounts, addToQueue, play };
export default { noAccounts, queue, play };
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Injector, Logger } from "replugged";
export const PluginLogger = Logger.plugin("Distify");
export const PluginInjector = new Injector();
export const PluginInjectorUtils = PluginInjector.utils;
import { applyInjections } from "./patches/index";
export const { utils: PluginInjectorUtils } = PluginInjector;
import Injections from "./injections/index";

export const start = (): void => {
applyInjections();
void Injections.applyInjections();
};

export const stop = (): void => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PluginInjector } from "../index";
import { ElementParser } from "../lib/requiredModules";
import Modules from "../lib/requiredModules";
export default (): void => {
PluginInjector.after(ElementParser, "sanitizeUrl", ([link]: [string], res) => {
PluginInjector.after(Modules.ElementParser, "sanitizeUrl", ([link]: [string], res) => {
const [_, type, id] =
link.match(/open.spotify.com\/(track|album|artist|playlist|user|episode)\/([^?]+)/) ?? [];
return !type || !id ? res : `spotify:${type}:${id}`;
Expand Down
38 changes: 38 additions & 0 deletions src/injections/MessageContextMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ContextMenu } from "replugged/components";
import { PluginInjectorUtils } from "../index";
import Modules from "../lib/requiredModules";
import MenuItems from "../Components/MenuItems";
import Utils from "../lib/utils";
import Types from "../types";

export default (): void => {
PluginInjectorUtils.addMenuItem(
Types.DefaultTypes.ContextMenuTypes.Message,
({ message }: { message: Types.Message }, menu: Types.MenuProps): React.ReactElement | void => {
const MenuGroup = menu?.children?.find?.((c) => c?.props?.id === "distify") ?? (
<ContextMenu.MenuGroup />
);
MenuGroup.props.id = "distify";
if (!menu?.children?.some?.((c) => c?.props?.id === "distify"))
menu?.children.splice(-1, 0, MenuGroup);
const SpotifyLinks = Array.from(
message.content.matchAll(
/open.spotify.com\/(album|track|playlist)\/([^?]+)/g,
) as IterableIterator<string[]>,
);
const SpotifyAccounts = Modules.ConnectedAccountsStore?.getAccounts().filter(
(a) => a.type === "spotify",
);
if (!SpotifyAccounts.length) {
MenuGroup.props.children = [MenuItems.noAccounts()];
}
const SpotifyMeta = Utils.mapMenuItems<string[][]>(SpotifyLinks, SpotifyAccounts, {
data: true,
});
if (SpotifyLinks.length <= 0) return;
if (SpotifyMeta) {
MenuGroup.props.children = [MenuItems.play(SpotifyMeta), MenuItems.queue(SpotifyMeta)];
}
},
);
};
3 changes: 2 additions & 1 deletion src/patches/Popover.tsx → src/injections/Popover.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { toast as ToastUtils } from "replugged/common";
import { PluginInjectorUtils, PluginLogger } from "../index";
import { ConnectedAccountsStore } from "../lib/requiredModules";
import Modules from "../lib/requiredModules";
import Utils from "../lib/utils";
import Icons from "../Components/Icons";
import Types from "../types";
export default (): void => {
const { ConnectedAccountsStore } = Modules;
PluginInjectorUtils.addPopoverButton((message: Types.Message) => {
const SpotifyLinks = Array.from(
message.content.matchAll(/open.spotify.com\/(album|track|playlist)\/([^?]+)/g) as string[][] &
Expand Down
13 changes: 13 additions & 0 deletions src/injections/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Modules from "../lib/requiredModules";
import injectElementParser from "./ElementParser";
import injectMessageContextMenu from "./MessageContextMenu";
import injectPopover from "./Popover";

export const applyInjections = async (): Promise<void> => {
await Modules.loadModules();
injectMessageContextMenu();
injectElementParser();
injectPopover();
};

export default { applyInjections };
15 changes: 11 additions & 4 deletions src/lib/requiredModules.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { webpack } from "replugged";
import Types from "../types";

export const ConnectedAccountsStore =
webpack.getByStoreName<Types.ConnectedAccountsStore>("ConnectedAccountsStore");
export const Modules: Types.Modules = {};

export const ElementParser = webpack.getByProps<Types.ElementParser>("sanitizeUrl", "sanitizeText");
Modules.loadModules = async (): Promise<void> => {
Modules.ElementParser ??= await webpack.waitForProps<Types.ElementParser>(
"sanitizeUrl",
"sanitizeText",
);
Modules.ConnectedAccountsStore ??=
webpack.getByStoreName<Types.ConnectedAccountsStore>("ConnectedAccountsStore");
Modules.SpotifyStore ??= webpack.getByStoreName<Types.SpotifyStore>("SpotifyStore");
};

export const SpotifyStore = webpack.getByStoreName<Types.SpotifyStore>("SpotifyStore");
export default Modules;
58 changes: 15 additions & 43 deletions src/lib/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { contextMenu as ContextMenuUtils, React } from "replugged/common";
import { ContextMenu } from "replugged/components";
import { PluginLogger } from "../index";
import { BASE_URL, BASE_URL_PLAYER } from "./consts";
import { ConnectedAccountsStore, SpotifyStore } from "./requiredModules";
import Modules from "./requiredModules";
import MenuItems from "../Components/MenuItems";
import Types from "../types";
export const customCacheSpotifyMeta = new Map<string, string[]>();
export const ensureSpotifyPlayer = (): Promise<{
socket?: Types.SpotifySocket;
device?: Types.SpotifyDevice;
}> => {
const { SpotifyStore } = Modules;
const activePlayer = SpotifyStore.getActiveSocketAndDevice();
if (activePlayer) return Promise.resolve(activePlayer);
const playableDevices = SpotifyStore.getPlayableComputerDevices();
Expand Down Expand Up @@ -58,8 +59,7 @@ export const error = async (res): Promise<Error> => {
export const play = async (type: string, id: string): Promise<void> => {
const { socket, device } = await ensureSpotifyPlayer();
if (!socket?.accessToken) {
PluginLogger.error("Please link your Spotify to Discord in Settings > Connections");
return;
throw new Error("Please link your Spotify to Discord in Settings > Connections");
}

const SpotifyResponse = await fetch(`${BASE_URL_PLAYER}/play?device_id=${device.id}`, {
Expand All @@ -83,8 +83,7 @@ export const play = async (type: string, id: string): Promise<void> => {
export const queue = async (type: string, id: string): Promise<void> => {
const { socket, device } = await ensureSpotifyPlayer();
if (!socket?.accessToken) {
PluginLogger.error("Please link your Spotify to Discord in Settings > Connections");
return;
throw new Error("Please link your Spotify to Discord in Settings > Connections");
}
const SpotifyResponse = await fetch(
`${BASE_URL_PLAYER}/queue?uri=${encodeURIComponent(`spotify:${type}:${id}`)}&device_id=${
Expand All @@ -104,7 +103,7 @@ export const queue = async (type: string, id: string): Promise<void> => {
throw await error(SpotifyResponse);
};

export const mapMenuItems = (
export const mapMenuItems = <R extends React.ReactElement[] | string[][]>(
SpotifyLinks: string[][],
SpotifyAccounts: Types.SpotifyAccounts[],
type:
Expand All @@ -114,7 +113,7 @@ export const mapMenuItems = (
queue: boolean;
play: boolean;
},
): React.ReactElement[] | string[][] => {
): R => {
const [SpotifyMeta, setSpotifyMeta] = React.useState(
SpotifyLinks.map(([_, type, id]) => {
if (customCacheSpotifyMeta.has(id)) {
Expand Down Expand Up @@ -161,18 +160,18 @@ export const mapMenuItems = (
}, []);
try {
if (SpotifyAccounts.length <= 0) {
return [MenuItems.noAccounts()];
return [MenuItems.noAccounts()] as R;
}

if ((type as { data: boolean }).data) {
return SpotifyMeta;
return SpotifyMeta as R;
}
return [
type.play && MenuItems.play(SpotifyMeta),
type.queue && MenuItems.addToQueue(SpotifyMeta),
];
type.queue && MenuItems.queue(SpotifyMeta),
] as R;
} catch {
return [];
return [] as R;
}
};

Expand All @@ -187,41 +186,14 @@ export const openContextMenu = (
): void => {
event.currentTarget = document.querySelector(`#distify-${type.play ? "play" : "queue"}`);
const MyContextMenu = (props) => {
const MappedItems = mapMenuItems(SpotifyLinks, SpotifyAccounts, type);
const MappedItems = mapMenuItems<React.ReactElement[]>(SpotifyLinks, SpotifyAccounts, type);
return (
<ContextMenu.ContextMenu {...props} navId={"tharki-distify"}>
{...MappedItems as React.ReactElement[]}
<ContextMenu.ContextMenu {...props} navId={"yofukashino-distify"}>
{...MappedItems}
</ContextMenu.ContextMenu>
);
};
ContextMenuUtils.open(event, (e) => <MyContextMenu {...e} onClose={ContextMenuUtils.close} />);
};

export const manipulateMenu = (
message: Types.Message,
menu: { children: React.ReactElement[] },
): React.ReactElement | void => {
const MenuGroup = menu?.children?.find?.((c) => c?.props?.id === "distify") ?? (
<ContextMenu.MenuGroup />
);
MenuGroup.props.id = "distify";
if (!menu?.children?.some?.((c) => c?.props?.id === "distify"))
menu?.children.splice(-1, 0, MenuGroup);
const SpotifyLinks = Array.from(
message.content.matchAll(/open.spotify.com\/(album|track|playlist)\/([^?]+)/g) as string[][] &
IterableIterator<RegExpMatchArray>,
);
const SpotifyAccounts = ConnectedAccountsStore.getAccounts().filter((a) => a.type === "spotify");
if (!SpotifyAccounts.length) {
MenuGroup.props.children = [MenuItems.noAccounts()];
}
const SpotifyMeta = mapMenuItems(SpotifyLinks, SpotifyAccounts, {
data: true,
}) as string[][];
if (SpotifyLinks.length <= 0) return;
if (SpotifyMeta) {
MenuGroup.props.children = [MenuItems.play(SpotifyMeta), MenuItems.addToQueue(SpotifyMeta)];
}
};

export default { error, play, queue, mapMenuItems, openContextMenu, manipulateMenu };
export default { error, play, queue, mapMenuItems, openContextMenu };
Loading

0 comments on commit 3869c44

Please sign in to comment.