chore: move website and guide out of packages

This commit is contained in:
iCrawl
2022-10-10 01:22:48 +02:00
parent 0a9d57b011
commit 3ed668e539
175 changed files with 428 additions and 487 deletions

View File

@@ -0,0 +1,3 @@
export function DocsLink() {
return null;
}

View File

@@ -0,0 +1,10 @@
import { FiExternalLink } from 'react-icons/fi';
export function ExternalLink({ href, title }: { href: string; title: string }) {
return (
<a className="text-blurple inline-flex place-items-center gap-2 text-sm font-semibold" href={href}>
<p>{title}</p>
<FiExternalLink size={18} />
</a>
);
}

View File

@@ -0,0 +1,70 @@
import { Button } from 'ariakit/button';
import { useState, useEffect } from 'react';
import { FiCommand } from 'react-icons/fi';
import { VscColorMode, VscGithubInverted, VscMenu, VscSearch } from 'react-icons/vsc';
import { useMedia } from 'react-use';
import { Sidebar } from './Sidebar.jsx';
import type { MDXPage } from './SidebarItems.jsx';
export function Navbar({ pages }: { pages?: MDXPage[] | undefined }) {
const matches = useMedia('(min-width: 992px)', false);
const [opened, setOpened] = useState(false);
useEffect(() => {
if (matches) {
setOpened(false);
}
}, [matches]);
return (
<>
<header className="dark:bg-dark-600 dark:border-dark-100 bg-light-600 border-light-800 fixed top-0 left-0 z-20 w-full border-b">
<div className="h-18 block px-6">
<div className="flex h-full flex-row place-content-between place-items-center">
<Button
aria-label="Menu"
className="focus:ring-width-2 focus:ring-blurple flex h-6 w-6 transform-gpu cursor-pointer select-none appearance-none place-items-center rounded border-0 bg-transparent p-0 text-sm font-semibold leading-none no-underline outline-0 focus:ring active:translate-y-px lg:hidden"
onClick={() => setOpened((open) => !open)}
>
<VscMenu size={24} />
</Button>
<div className="hidden md:flex md:flex-row">Placeholder</div>
<div className="flex flex-row place-items-center gap-4">
<Button
as="div"
className="dark:bg-dark-800 focus:ring-width-2 focus:ring-blurple rounded bg-white px-4 py-2.5 outline-0 focus:ring"
// onClick={() => dialog?.toggle()}
>
<div className="flex flex-row place-items-center gap-4">
<VscSearch size={18} />
<span className="opacity-65">Search...</span>
<div className="opacity-65 flex flex-row place-items-center gap-2">
<FiCommand size={18} /> K
</div>
</div>
</Button>
<Button
aria-label="GitHub"
as="a"
className="focus:ring-width-2 focus:ring-blurple flex h-6 w-6 transform-gpu cursor-pointer select-none appearance-none place-items-center rounded rounded-full border-0 bg-transparent p-0 text-sm font-semibold leading-none no-underline outline-0 focus:ring active:translate-y-px"
href="https://github.com/discordjs/discord.js"
rel="noopener noreferrer"
target="_blank"
>
<VscGithubInverted size={24} />
</Button>
<Button
aria-label="Toggle theme"
className="focus:ring-width-2 focus:ring-blurple flex h-6 w-6 transform-gpu cursor-pointer select-none appearance-none place-items-center rounded-full rounded border-0 bg-transparent p-0 text-sm font-semibold leading-none no-underline outline-0 focus:ring active:translate-y-px"
// onClick={() => toggleTheme()}
>
<VscColorMode size={24} />
</Button>
</div>
</div>
</div>
</header>
<Sidebar opened={opened} pages={pages} />
</>
);
}

View File

@@ -0,0 +1,72 @@
import type { MarkdownHeading } from 'astro';
import { useEffect, useMemo, useState } from 'react';
import { Scrollbars } from 'react-custom-scrollbars-2';
import { VscListSelection } from 'react-icons/vsc';
import { useLocation } from 'react-use';
const LINK_HEIGHT = 30;
const INDICATOR_SIZE = 10;
const INDICATOR_OFFSET = (LINK_HEIGHT - INDICATOR_SIZE) / 2;
export function Outline({ headings }: { headings: MarkdownHeading[] }) {
const state = useLocation();
const [active, setActive] = useState(0);
const headingItems = useMemo(
() =>
headings.map((heading, idx) => (
<a
className={`dark:border-dark-100 border-light-800 pl-6.5 focus:ring-width-2 focus:ring-blurple ml-[10px] border-l p-[5px] text-sm outline-0 focus:rounded focus:border-0 focus:ring ${
idx === active
? 'bg-blurple text-white'
: 'dark:hover:bg-dark-200 dark:active:bg-dark-100 hover:bg-light-700 active:bg-light-800'
}`}
href={`#${heading.slug}`}
key={heading.slug}
style={{ paddingLeft: `${heading.depth * 14}px` }}
title={heading.text}
>
<span className="line-clamp-1">{heading.text}</span>
</a>
)),
[headings, active],
);
useEffect(() => {
const idx = headings.findIndex((heading) => heading.slug === state.hash?.slice(1));
if (idx >= 0) {
setActive(idx);
}
}, [state, headings]);
return (
<Scrollbars
autoHide
hideTracksWhenNotNeeded
renderThumbVertical={(props) => <div {...props} className="dark:bg-dark-100 bg-light-900 z-30 rounded" />}
renderTrackVertical={(props) => (
<div {...props} className="absolute top-0.5 right-0.5 bottom-0.5 z-30 w-1.5 rounded" />
)}
universal
>
<div className="flex flex-col break-all p-3 pb-8">
<div className="mt-4 ml-2 flex flex-row gap-2">
<VscListSelection size={25} />
<span className="font-semibold">Contents</span>
</div>
<div className="mt-4 ml-2 flex flex-col gap-2">
<div className="relative flex flex-col">
<div
className="bg-blurple absolute h-[10px] w-[10px] rounded-full border-2 border-black dark:border-white"
style={{
left: INDICATOR_SIZE / 2 + 0.5,
transform: `translateY(${active * LINK_HEIGHT + INDICATOR_OFFSET}px)`,
}}
/>
{headingItems}
</div>
</div>
</div>
</Scrollbars>
);
}

View File

@@ -0,0 +1,3 @@
export function ResultingCode() {
return null;
}

View File

@@ -0,0 +1,24 @@
import { Scrollbars } from 'react-custom-scrollbars-2';
import type { MDXPage } from './SidebarItems.jsx';
export function Sidebar({ pages, opened }: { opened: boolean; pages?: MDXPage[] | undefined }) {
return (
<nav
className={`h-[calc(100vh - 73px)] dark:bg-dark-600 dark:border-dark-100 border-light-800 fixed top-[73px] left-0 bottom-0 z-20 w-full border-r bg-white ${
opened ? 'block' : 'hidden'
} lg:w-76 lg:max-w-76 lg:block`}
>
<Scrollbars
autoHide
hideTracksWhenNotNeeded
renderThumbVertical={(props) => <div {...props} className="dark:bg-dark-100 bg-light-900 z-30 rounded" />}
renderTrackVertical={(props) => (
<div {...props} className="absolute top-0.5 right-0.5 bottom-0.5 z-30 w-1.5 rounded" />
)}
universal
>
{pages ?? null}
</Scrollbars>
</nav>
);
}

View File

@@ -0,0 +1,51 @@
import { Section } from '@discordjs/ui';
import type { MDXInstance } from 'astro';
import { useEffect, useMemo, useState } from 'react';
import { useLocation } from 'react-use';
export type MDXPage = MDXInstance<{ category: string; title: string }>;
export function SidebarItems({ pages }: { pages: MDXPage[] }) {
const state = useLocation();
const [active, setActive] = useState<string | undefined>('');
const categories = useMemo(
() =>
pages.reduce<Record<string, MDXPage[]>>((acc, page) => {
if (acc[page.frontmatter.category]) {
acc[page.frontmatter.category]?.push(page);
} else {
acc[page.frontmatter.category] = [page];
}
return acc;
}, {}),
[pages],
);
useEffect(() => {
setActive(state.pathname);
}, [state]);
return Object.keys(categories).map((category, idx) => (
<Section key={idx} title={category}>
{categories[category]?.map((member, index) => (
<a
className={`dark:border-dark-100 border-light-800 focus:ring-width-2 focus:ring-blurple ml-5 flex flex-col border-l p-[5px] pl-6 outline-0 focus:rounded focus:border-0 focus:ring ${
(member.url || '/') === active
? 'bg-blurple text-white'
: 'dark:hover:bg-dark-200 dark:active:bg-dark-100 hover:bg-light-700 active:bg-light-800'
}`}
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
href={member.url || '/'}
key={index}
title={member.frontmatter.title}
>
<div className="flex flex-row place-items-center gap-2 lg:text-sm">
<span className="truncate">{member.frontmatter.title}</span>
</div>
</a>
)) ?? null}
</Section>
));
}

View File

@@ -0,0 +1,137 @@
---
import { Separator } from 'ariakit/separator';
import type { MarkdownLayoutProps } from 'astro';
import { ExternalLink } from './ExternalLink.jsx';
import { Navbar } from './Navbar.jsx';
import { Outline } from './Outline.jsx';
import { SidebarItems } from './SidebarItems.jsx';
import { generateGithubURL } from '~/util/url.js';
const pages = await Astro.glob<{ category: string; title: string }>('../pages/**/*.mdx');
type Props = MarkdownLayoutProps<{}>;
const { headings, url } = Astro.props;
---
<script>
window.addEventListener('load', () => {
const headings = document.querySelectorAll(
'div.level-h1 > h1, div.level-h2 > h2, div.level-h3 > h3, div.level-h4 > h4',
);
const headingsObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const location = window.location.toString().split('#')[0];
history.replaceState(null, '', location + '#' + entry.target.id);
}
});
},
{
root: null,
rootMargin: '-100px 0% -66%',
threshold: 1.0,
},
);
headings.forEach((heading) => headingsObserver.observe(heading));
});
</script>
<Navbar client:load>
<div class="flex flex-col gap-3 p-3 pb-32 lg:pb-12" slot="pages">
<SidebarItems client:load pages={pages} />
</div>
</Navbar>
<main class="pt-18 lg:pl-76 xl:pr-64">
<article class="dark:bg-dark-600 bg-light-600">
<div class="dark:bg-dark-800 relative z-10 min-h-[calc(100vh_-_70px)] bg-white p-6 pb-20 shadow">
<div class="prose max-w-full">
<slot />
</div>
<div
class="h-[calc(100vh - 72px)] dark:bg-dark-600 dark:border-dark-100 border-light-800 fixed top-[72px] right-0 bottom-0 z-20 hidden w-64 border-l bg-white pr-2 xl:block"
>
<Outline client:load headings={headings} />
</div>
<Separator className="my-5" />
<div class="flex place-content-end">
<ExternalLink client:load href={generateGithubURL(url!)} title="Edit this page on github" />
</div>
</div>
<div class="h-76 md:h-52"></div>
<footer
class="dark:bg-dark-600 h-76 lg:pl-84 bg-light-600 fixed bottom-0 left-0 right-0 md:h-52 md:pl-4 md:pr-16 xl:pr-76"
>
<div class="mx-auto flex max-w-6xl flex-col place-items-center gap-12 pt-12 lg:place-content-center">
<div class="flex w-full flex-col place-content-between place-items-center gap-12 md:flex-row md:gap-0">
<a
class="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
href="https://vercel.com/?utm_source=discordjs&utm_campaign=oss"
rel="noopener noreferrer"
target="_blank"
title="Vercel"
>
<img alt="Vercel" src="/powered-by-vercel.svg" />
</a>
<div class="flex flex-row gap-6 md:gap-12">
<div class="flex flex-col gap-2">
<div class="text-lg font-semibold">Community</div>
<div class="flex flex-col gap-1">
<a
class="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
href="https://discord.gg/djs"
rel="noopener noreferrer"
target="_blank"
>
Discord
</a>
<a
class="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
href="https://github.com/discordjs/discord.js/discussions"
rel="noopener noreferrer"
target="_blank"
>
GitHub discussions
</a>
</div>
</div>
<div class="flex flex-col gap-2">
<div class="text-lg font-semibold">Project</div>
<div class="flex flex-col gap-1">
<a
class="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
href="https://github.com/discordjs/discord.js"
rel="noopener noreferrer"
target="_blank"
>
discord.js
</a>
<a
class="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
href="https://discordjs.guide"
rel="noopener noreferrer"
target="_blank"
>
discord.js guide
</a>
<a
class="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
href="https://discord-api-types.dev"
rel="noopener noreferrer"
target="_blank"
>
discord-api-types
</a>
</div>
</div>
</div>
</div>
</div>
</footer>
</article>
<div>Test</div>
</main>

View File

@@ -0,0 +1,63 @@
---
import '../styles/main.css';
import '@code-hike/mdx/styles.css';
import '../styles/ch.css';
import type { MarkdownLayoutProps } from 'astro';
import SidebarLayout from '../components/SidebarLayout.astro';
import { DESCRIPTION } from '../util/constants.js';
type Props = MarkdownLayoutProps<{}>;
const props = Astro.props;
---
<html lang="en">
<head>
<link href="/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180" />
<link href="/favicon-32x32.png" rel="icon" sizes="32x32" type="image/png" />
<link href="/favicon-16x16.png" rel="icon" sizes="16x16" type="image/png" />
<link href="/site.webmanifest" rel="manifest" />
<link color="#090a16" href="/safari-pinned-tab.svg" rel="mask-icon" />
<meta content="light dark" name="color-scheme" />
<meta content="discord.js" name="apple-mobile-web-app-title" />
<meta content="discord.js" name="application-name" />
<meta content="#090a16" name="msapplication-TileColor" />
<meta content={DESCRIPTION} name="description" />
<meta content="discord.js" property="og:site_name" />
<meta content="website" property="og:type" />
<meta content="discord.js guide" property="og:title" />
<meta content={DESCRIPTION} name="og:description" />
<meta content="https://discordjs.dev/open-graph.png" property="og:image" />
<meta content="summary_large_image" name="twitter:card" />
<meta content="@iCrawlToGo" name="twitter:creator" />
<title>discord.js</title>
<meta content="minimum-scale=1, initial-scale=1, width=device-width" name="viewport" />
<meta content="#5865f2" name="theme-color" />
</head>
<body class="dark:bg-dark-800 bg-white">
<script is:inline>
function setTheme(prefersDarkMode, persistedColorPreference) {
if (persistedColorPreference === 'dark' || (prefersDarkMode && persistedColorPreference !== 'light')) {
document.documentElement.classList.toggle('dark', true);
} else {
document.documentElement.classList.toggle('dark', false);
}
}
(() => {
const prefersDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
const persistedColorPreference = localStorage.getItem('theme') || 'auto';
setTheme(prefersDarkMode, persistedColorPreference);
const listener =
window.matchMedia &&
window
.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', (ev) => setTheme(ev.matches, persistedColorPreference));
})();
</script>
<SidebarLayout {...props}>
<slot />
</SidebarLayout>
</body>
</html>

View File

@@ -0,0 +1,244 @@
---
layout: '../../layouts/SidebarLayout.astro'
title: Creating commands
category: Creating your bot
---
import { CH } from '@code-hike/mdx/components';
import { Alert, DiscordMessages, DiscordMessage } from '@discordjs/ui';
import { DocsLink } from '../../components/DocsLink.jsx';
import { ResultingCode } from '../../components/ResultingCode.jsx';
# Creating commands
<Alert title="Tip" type="success">
This page is a follow-up and bases its code on [the previous page](/creating-your-bot/).
</Alert>
<DiscordMessages rounded>
<DiscordMessage
interaction={{
author: {
avatar: 'https://cdn.discordapp.com/avatars/81440962496172032/81c0df2befe565b05018da6b026babb0.webp?size=160',
username: 'Crawl',
},
command: 'ping',
}}
author={{
avatar: 'https://cdn.discordapp.com/avatars/81440962496172032/81c0df2befe565b05018da6b026babb0.webp?size=160',
username: 'Crawl',
time: 'Today at 21:00',
}}
>
Pong!
</DiscordMessage>
</DiscordMessages>
Discord allows developers to register [slash commands](https://discord.com/developers/docs/interactions/application-commands), which provide users a first-class way of interacting directly with your application. Before being able to reply to a command, you must first register it.
## Registering commands
This section will cover only the bare minimum to get you started, but you can refer to our [in-depth page on registering slash commands](/interactions/slash-commands.md#registering-slash-commands) for further details. It covers guild commands, global commands, options, option types, and choices.
### Command deployment script
Create a `deploy-commands.js` file in your project directory. This file will be used to register and update the slash commands for your bot application.
Since commands only need to be registered once, and updated when the definition (description, options etc) is changed, it's not necessary to connect a whole client to the gateway or do this on every `ready` event. As such, a standalone script using the lighter REST manager is preferred.
Below is a deployment script you can use. Focus on these variables:
- `clientId`: Your application's client id
- `guildId`: Your development server's id
- `commands`: An array of commands to register. The [slash command builder](/popular-topics/builders.md#slash-command-builders) from `discord.js` is used to build the data for your commands
<Alert title="Tip" type="success">
In order to get your application's client id, go to [Discord Developer
Portal](https://discord.com/developers/applications) and choose your application. Find the id under "Application ID"
in General Information subpage. To get guild id, open Discord and go to your settings. On the "Advanced" page, turn on
"Developer Mode". This will enable a "Copy ID" button in the context menu when you right-click on a server icon, a
user's profile, etc.
</Alert>
<CH.Code client:load>
```js deploy-commands.js mark=4,6:10
const { REST, SlashCommandBuilder, Routes } = require('discord.js');
const { clientId, guildId, token } = require('./config.json');
const commands = [
new SlashCommandBuilder().setName('ping').setDescription('Replies with pong!'),
new SlashCommandBuilder().setName('server').setDescription('Replies with server info!'),
new SlashCommandBuilder().setName('user').setDescription('Replies with user info!'),
].map((command) => command.toJSON());
const rest = new REST({ version: '10' }).setToken(token);
rest
.put(Routes.applicationGuildCommands(clientId, guildId), { body: commands })
.then((data) => console.log(`Successfully registered ${data.length} application commands.`))
.catch(console.error);
```
---
```json config.json mark=2:3
{
"clientId": "123456789012345678",
"guildId": "876543210987654321",
"token": "your-token-goes-here"
}
```
</CH.Code>
Once you fill in these values, run `node deploy-commands.js` in your project directory to register your commands to a single guild. It's also possible to [register commands globally](/interactions/slash-commands.md#global-commands).
<Alert title="Tip" type="success">
You only need to run `node deploy-commands.js` once. You should only run it again if you add or edit existing
commands.
</Alert>
## Replying to commands
Once you've registered your commands, you can listen for interactions via <DocsLink path="class/Client?scrollTo=e-interactionCreate" /> in your `index.js` file.
You should first check if an interaction is a chat input command via <DocsLink path="class/Interaction?scrollTo=isChatInputCommand" type="method">`.isChatInputCommand()`</DocsLink>, and then check the <DocsLink path="class/CommandInteraction?scrollTo=commandName">`.commandName`</DocsLink> property to know which command it is. You can respond to interactions with <DocsLink path="class/CommandInteraction?scrollTo=reply">`.reply()`</DocsLink>.
<CH.Code client:load>
```js mark=5:16
client.once('ready', () => {
console.log('Ready!');
});
client.on('interactionCreate', async (interaction) => {
if (!interaction.isChatInputCommand()) return;
const { commandName } = interaction;
if (commandName === 'ping') {
await interaction.reply('Pong!');
} else if (commandName === 'server') {
await interaction.reply('Server info.');
} else if (commandName === 'user') {
await interaction.reply('User info.');
}
});
client.login(token);
```
</CH.Code>
### Server info command
Note that servers are referred to as "guilds" in the Discord API and discord.js library. `interaction.guild` refers to the guild the interaction was sent in (a <DocsLink path="class/Guild" /> instance), which exposes properties such as `.name` or `.memberCount`.
<CH.Code client:load>
```js focus=7
client.on('interactionCreate', async (interaction) => {
if (!interaction.isChatInputCommand()) return;
const { commandName } = interaction;
if (commandName === 'ping') {
await interaction.reply('Pong!');
} else if (commandName === 'server') {
await interaction.reply(`Server name: ${interaction.guild.name}\nTotal members: ${interaction.guild.memberCount}`);
} else if (commandName === 'user') {
await interaction.reply('User info.');
}
});
```
</CH.Code>
<DiscordMessages rounded>
<DiscordMessage
interaction={{
author: {
avatar: 'https://cdn.discordapp.com/avatars/81440962496172032/81c0df2befe565b05018da6b026babb0.webp?size=160',
username: 'Crawl',
},
command: 'server',
}}
author={{
avatar: 'https://cdn.discordapp.com/avatars/81440962496172032/81c0df2befe565b05018da6b026babb0.webp?size=160',
username: 'Crawl',
time: 'Today at 21:00',
}}
>
<p>Server name: discord.js Guide</p>
<p>Total members: 2</p>
</DiscordMessage>
</DiscordMessages>
You could also display the date the server was created, or the server's verification level. You would do those in the same manner use `interaction.guild.createdAt` or `interaction.guild.verificationLevel`, respectively.
<Alert title="Tip" type="success">
Refer to the <DocsLink path="class/Guild" /> documentation for a list of all the available properties and methods!
</Alert>
### User info command
A "user" refers to a Discord user. `interaction.user` refers to the user the interaction was sent by (a <DocsLink path="class/User" /> instance), which exposes properties such as `.tag` or `.id`.
<CH.Code client:load>
```js focus=9
client.on('interactionCreate', async (interaction) => {
if (!interaction.isChatInputCommand()) return;
const { commandName } = interaction;
if (commandName === 'ping') {
await interaction.reply('Pong!');
} else if (commandName === 'server') {
await interaction.reply(`Server name: ${interaction.guild.name}\nTotal members: ${interaction.guild.memberCount}`);
} else if (commandName === 'user') {
await interaction.reply(`Your tag: ${interaction.user.tag}\nYour id: ${interaction.user.id}`);
}
});
```
</CH.Code>
<DiscordMessages rounded>
<DiscordMessage
interaction={{
author: {
avatar: 'https://cdn.discordapp.com/avatars/81440962496172032/81c0df2befe565b05018da6b026babb0.webp?size=160',
username: 'Crawl',
},
command: 'user',
}}
author={{
avatar: 'https://cdn.discordapp.com/avatars/81440962496172032/81c0df2befe565b05018da6b026babb0.webp?size=160',
username: 'Crawl',
time: 'Today at 21:00',
}}
>
<p>Your tag: User#0001</p>
<p>Your id: 123456789012345678</p>
</DiscordMessage>
</DiscordMessages>
<Alert title="Tip" type="success">
Refer to the <DocsLink path="class/User" /> documentation for a list of all the available properties and methods!
</Alert>
And there you have it!
## The problem with `if`/`else if`
If you don't plan on making more than a couple commands, then using an `if`/`else if` chain is fine; however, this isn't always the case. Using a giant `if`/`else if` chain will only hinder your development process in the long run.
Here's a small list of reasons why you shouldn't do so:
- Takes longer to find a piece of code you want;
- Easier to fall victim to [spaghetti code](https://en.wikipedia.org/wiki/Spaghetti_code);
- Difficult to maintain as it grows;
- Difficult to debug;
- Difficult to organize;
- General bad practice.
Next, we'll be diving into something called a "command handler" code that makes handling commands easier and much more efficient. This allows you to move your commands into individual files.
## Resulting code
<ResultingCode />

View File

@@ -0,0 +1,194 @@
---
layout: '../../layouts/SidebarLayout.astro'
title: Initial files
category: Creating your bot
---
import { CH } from '@code-hike/mdx/components';
import { Alert, Section } from '@discordjs/ui';
import { DocsLink } from '../../components/DocsLink.jsx';
import { ResultingCode } from '../../components/ResultingCode.jsx';
# Initial files
Once you [add your bot to a server](/preparations/adding-your-bot-to-servers.md), the next step is to start coding and get it online! Let's start by creating a config file for your client token and a main file for your bot application.
## Creating configuration files
As explained in the ["What is a token, anyway?"](/preparations/setting-up-a-bot-application.md#what-is-a-token-anyway) section, your token is essentially your bot's password, and you should protect it as best as possible. This can be done through a `config.json` file or by using environment variables.
Open your application in the [Discord Developer Portal](https://discord.com/developers/applications) and go to the "Bot" page to copy your token.
### Using `config.json`
Storing data in a `config.json` file is a common way of keeping your sensitive values safe. Create a `config.json` file in your project directory and paste in your token. You can access your token inside other files by using `require()`.
<CH.Code client:load>
```json config.json
{
"token": "your-token-goes-here"
}
```
---
```js Usage
const { token } = require('./config.json');
console.log(token);
```
</CH.Code>
<Alert title="Caution" type="danger">
If you're using Git, you should not commit this file and should [ignore it via
`.gitignore`](/creating-your-bot/#git-and-gitignore).
</Alert>
### Using environment variables
Environment variables are special values for your environment (e.g., terminal session, Docker container, or environment variable file). You can pass these values into your code's scope so that you can use them.
One way to pass in environment variables is via the command line interface. When starting your app, instead of `node index.js`, use `TOKEN=your-token-goes-here node index.js`. You can repeat this pattern to expose other values as well.
You can access the set values in your code via the `process.env` global variable, accessible in any file. Note that values passed this way will always be strings and that you might need to parse them to a number, if using them to do calculations.
<CH.Code client:load>
```shellscript Command line
A=123 B=456 DISCORD_TOKEN=your-token-goes-here node index.js
```
---
```js Usage
console.log(process.env.A);
console.log(process.env.B);
console.log(process.env.DISCORD_TOKEN);
```
</CH.Code>
#### Using dotenv
Another common approach is storing these values in a `.env` file. This spares you from always copying your token into the command line. Each line in a `.env` file should hold a `KEY=value` pair.
You can use the [`dotenv` package](https://www.npmjs.com/package/dotenv) for this. Once installed, require and use the package to load your `.env` file and attach the variables to `process.env`:
<CH.Code client:load>
```shellscript npm
npm install dotenv
```
```shellscript yarn
yarn add dotenv
```
```shellscript pnpm
pnpm add dotenv
```
---
```text .env
A=123
B=456
DISCORD_TOKEN=your-token-goes-here
```
---
```js Usage
const dotenv = require('dotenv');
dotenv.config();
console.log(process.env.A);
console.log(process.env.B);
console.log(process.env.DISCORD_TOKEN);
```
</CH.Code>
<Alert title="Caution" type="danger">
If you're using Git, you should not commit this file and should [ignore it via
`.gitignore`](/creating-your-bot/#git-and-gitignore).
</Alert>
<Section client:load title="Online editors (Glitch, Heroku, Replit, etc.)" defaultClosed padded background gutter>
While we generally do not recommend using online editors as hosting solutions, but rather invest in a proper virtual private server, these services do offer ways to keep your credentials safe as well! Please see the respective service's documentation and help articles for more information on how to keep sensitive values safe:
- Glitch: [Storing secrets in .env](https://glitch.happyfox.com/kb/article/18)
- Heroku: [Configuration variables](https://devcenter.heroku.com/articles/config-vars)
- Replit: [Secrets and environment variables](https://docs.replit.com/repls/secrets-environment-variables)
</Section>
### Git and `.gitignore`
Git is a fantastic tool to keep track of your code changes and allows you to upload progress to services like [GitHub](https://github.com/), [GitLab](https://about.gitlab.com/), or [Bitbucket](https://bitbucket.org/product). While this is super useful to share code with other developers, it also bears the risk of uploading your configuration files with sensitive values!
You can specify files that Git should ignore in its versioning systems with a `.gitignore` file. Create a `.gitignore` file in your project directory and add the names of the files and folders you want to ignore:
<CH.Code client:load>
```
node_modules
.env
config.json
```
</CH.Code>
<Alert title="Tip" type="success">
Aside from keeping credentials safe, `node_modules` should be included here. Since this directory can be restored
based on the entries in your `package.json` and `package-lock.json` files by running `npm install`, it does not need
to be included in Git. You can specify quite intricate patterns in `.gitignore` files, check out the [Git
documentation on `.gitignore`](https://git-scm.com/docs/gitignore) for more information!
</Alert>
## Creating the main file
Open your code editor and create a new file. We suggest that you save the file as `index.js`, but you may name it whatever you wish.
Here's the base code to get you started:
<CH.Code client:load>
```js
// Require the necessary discord.js classes
const { Client, GatewayIntentBits } = require('discord.js');
const { token } = require('./config.json');
// Create a new client instance
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
// When the client is ready, run this code (only once)
client.once('ready', () => {
console.log('Ready!');
});
// Login to Discord with your client's token
client.login(token);
```
</CH.Code>
This is how you create a client instance for your Discord bot and login to Discord. The `GatewayIntentBits.Guilds` intents option is necessary for your client to work properly, as it ensures that the caches for guilds, channels and roles are populated and available for internal use.
Intents also define which events Discord should send to your bot, and you may wish to enable more than just the minimum. You can read more about the other intents on the [Intents topic](/popular-topics/intents).
Open your terminal and run `node index.js` to start the process. If you see "Ready!" after a few seconds, you're good to go!
<Alert title="Tip" type="success">
You can open your `package.json` file and edit the `"main": "index.js"` field to point to your main file. You can then
run `node .` in your terminal to start the process! After closing the process with `Ctrl + C`, you can press the up
arrow on your keyboard to bring up the latest commands you've run. Pressing up and then enter after closing the
process is a quick way to start it up again.
</Alert>
## Resulting code
<ResultingCode path="creating-your-bot/initial-files" />

View File

@@ -0,0 +1,36 @@
---
layout: '../layouts/SidebarLayout.astro'
title: Introduction
category: Home
---
# Introduction
If you're reading this, it probably means you want to learn how to make a bot with discord.js. Awesome! You've come to the right place.
This guide will teach you things such as:
- How to get a bot [up and running](/preparations/) from scratch;
- How to properly [create](/creating-your-bot/), [organize](/creating-your-bot/command-handling.md), and expand on your commands;
- In-depth explanations and examples regarding popular topics (e.g. [reactions](/popular-topics/reactions.md), [embeds](/popular-topics/embeds.md), [canvas](/popular-topics/canvas.md));
- Working with databases (e.g. [sequelize](/sequelize/) and [keyv](/keyv/));
- Getting started with [sharding](/sharding/);
- And much more.
This guide will also cover subjects like common errors and how to solve them, keeping your code clean, setting up a proper development environment, etc.
Sounds good? Great! Let's get started, then.
## Before you begin...
Alright, making a bot is cool and all, but there are some prerequisites to it. To create a bot with discord.js, you should have a fairly decent grasp of JavaScript itself.
While you _can_ make a bot with very little JavaScript and programming knowledge, trying to do so without understanding the language first will only hinder you. You may get stuck on many uncomplicated issues, struggle with solutions to incredibly easy problems, and all-in-all end up frustrated. Sounds pretty annoying.
If you don't know JavaScript but would like to learn about it, here are a few links to help get you started:
- [Eloquent JavaScript, a free online book](http://eloquentjavascript.net/)
- [JavaScript.info, a modern javascript tutorial](https://javascript.info/)
- [Codecademy's interactive JavaScript course](https://www.codecademy.com/learn/introduction-to-javascript)
- [Nodeschool, for both JavaScript and Node.js lessons](https://nodeschool.io/)
- [MDN's JavaScript guide and full documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
- [Google, your best friend](https://google.com)
Take your pick, learn some JavaScript, and once you feel like you're confident enough to make a bot, come back and get started!

View File

@@ -0,0 +1,20 @@
---
layout: '../layouts/SidebarLayout.astro'
title: Requesting more content
category: Home
---
import { Alert } from '@discordjs/ui';
# Requesting more content
Since this guide is made specifically for the discord.js community, we want to be sure to provide the most relevant and up-to-date content. We will, of course, make additions to the current pages and add new ones as we see fit, but fulfilling requests is how we know we're providing content you all want the most.
Requests may be as simple as "add an example to the [frequently asked questions](/popular-topics/faq.html) page", or as elaborate as "add a page regarding [sharding](/sharding/)". We'll do our best to fulfill all requests, as long as they're reasonable.
To make a request, simply head over to [the repo's issue tracker](https://github.com/discordjs/guide/issues) and [create a new issue](https://github.com/discordjs/guide/issues/new)! Title it appropriately, and let us know exactly what you mean inside the issue description. Make sure that you've looked around the site before making a request; what you want to request might already exist!
<Alert title="Tip" type="success">
Remember that you can always [fork the repo](https://github.com/discordjs/guide) and [make a pull
request](https://github.com/discordjs/guide/pulls) if you want to add anything to the guide yourself!
</Alert>

View File

@@ -0,0 +1,93 @@
---
title: Test
category: Test
---
import { DiscordMessages, DiscordMessage, DiscordMessageEmbed } from '@discordjs/ui';
<DiscordMessages>
<DiscordMessage
reply={{
author: {
avatar: 'https://cdn.discordapp.com/avatars/81440962496172032/81c0df2befe565b05018da6b026babb0.webp?size=160',
username: 'Crawl',
},
content: 'Test',
}}
author={{
avatar: 'https://cdn.discordapp.com/avatars/81440962496172032/81c0df2befe565b05018da6b026babb0.webp?size=160',
username: 'Crawl',
time: 'Today at 21:00',
}}
>
1234
</DiscordMessage>
<DiscordMessage
author={{
avatar: 'https://cdn.discordapp.com/avatars/81440962496172032/81c0df2befe565b05018da6b026babb0.webp?size=160',
username: 'Crawl',
time: 'Today at 21:00',
}}
followUp
time="21:02"
>
1234
</DiscordMessage>
</DiscordMessages>
<DiscordMessages>
<DiscordMessage
reply={{
author: {
avatar: 'https://cdn.discordapp.com/avatars/81440962496172032/81c0df2befe565b05018da6b026babb0.webp?size=160',
username: 'Crawl',
},
content: 'Test',
}}
author={{
avatar: 'https://cdn.discordapp.com/avatars/81440962496172032/81c0df2befe565b05018da6b026babb0.webp?size=160',
username: 'Crawl',
time: 'Today at 21:00',
}}
>
1234
</DiscordMessage>
</DiscordMessages>
<DiscordMessages>
<DiscordMessage
reply={{
author: {
avatar: 'https://cdn.discordapp.com/avatars/81440962496172032/81c0df2befe565b05018da6b026babb0.webp?size=160',
username: 'Crawl',
},
content: 'Test',
}}
author={{
avatar: 'https://cdn.discordapp.com/avatars/81440962496172032/81c0df2befe565b05018da6b026babb0.webp?size=160',
username: 'Crawl',
time: 'Today at 21:00',
}}
>
<>
<DiscordMessageEmbed
author={{
avatar: 'https://cdn.discordapp.com/avatars/81440962496172032/81c0df2befe565b05018da6b026babb0.webp?size=160',
username: 'Crawl',
}}
/>
<DiscordMessageEmbed title={{ title: 'Title' }} />
<DiscordMessageEmbed footer={{ content: 'Footer' }} />
<DiscordMessageEmbed
author={{
avatar: 'https://cdn.discordapp.com/avatars/81440962496172032/81c0df2befe565b05018da6b026babb0.webp?size=160',
username: 'Crawl',
}}
title={{ title: 'Title' }}
footer={{ content: 'Footer' }}
>
Test
</DiscordMessageEmbed>
</>
</DiscordMessage>
</DiscordMessages>

View File

@@ -0,0 +1,73 @@
---
layout: '../layouts/SidebarLayout.astro'
title: What's new
category: Home
---
import { DiscordMessages, DiscordMessage } from '@discordjs/ui';
# What's new
<DiscordMessages rounded>
<DiscordMessage
interaction={{
author: {
avatar: 'https://cdn.discordapp.com/avatars/81440962496172032/81c0df2befe565b05018da6b026babb0.webp?size=160',
username: 'Crawl',
},
command: 'upgrade',
}}
author={{
avatar: 'https://cdn.discordapp.com/avatars/474807795183648809/7f239a0776ff928b2182906a2b3743c9.webp?size=160',
bot: true,
username: 'Guide Bot',
time: 'Today at 21:00',
}}
>
discord.js v14 has released and the guide has been updated!
<br />
This includes additions and changes made in Discord, such as slash commands and message components.
</DiscordMessage>
</DiscordMessages>
## Site
- Upgraded to [VuePress v2](https://v2.vuepress.vuejs.org/)
- New theme made to match the [discord.js documentation site](https://discord.js.org/)
- Discord message components upgraded to [@discord-message-components/vue](https://github.com/Danktuary/discord-message-components/blob/main/packages/vue/README.md)
- Many fixes in code blocks, grammar, consistency, etc.
## Pages
All content has been updated to use discord.js v14 syntax. The v13 version of the guide can be found at [https://v13.discordjs.guide/](https://v13.discordjs.guide/).
### New
- [Updating from v13 to v14](/additional-info/changes-in-v14.md): A list of the changes from discord.js v13 to v14
- [Slash commands](/interactions/slash-commands.md): Registering, replying to slash commands and permissions
- [Buttons](/interactions/buttons.md): Building, sending, and receiving buttons
- [Select menus](/interactions/select-menus.md): Building, sending, and receiving select menus
- [Threads](/popular-topics/threads.md): Creating and managing threads
- [Builders](/popular-topics/builders.md): A collection of builders to use with your bot
### Updated
- Commando: Replaced with [Sapphire](https://sapphirejs.dev/docs/Guide/getting-started/getting-started-with-sapphire)
- [Voice](/voice/): Rewritten to use the [`@discordjs/voice`](https://github.com/discordjs/discord.js/tree/main/packages/voice) package
- [Command handling](/creating-your-bot/command-handling.md/): Updated to use slash commands
- Obsolete sections removed
- `client.on('message')` snippets updated to `client.on('interactionCreate')`
- [Message content will become a new privileged intent on August 31, 2022](https://support-dev.discord.com/hc/en-us/articles/4404772028055)
<DiscordMessages rounded>
<DiscordMessage
author={{
avatar: 'https://cdn.discordapp.com/avatars/474807795183648809/7f239a0776ff928b2182906a2b3743c9.webp?size=160',
bot: true,
username: 'Guide Bot',
time: 'Today at 21:00',
}}
>
Thank you to all of those that contributed to the development of discord.js and the guide!
</DiscordMessage>
</DiscordMessages>

View File

@@ -0,0 +1,3 @@
.ch-frame-buttons {
display: none;
}

View File

@@ -0,0 +1,16 @@
@import url('https://rsms.me/inter/inter.css');
:root {
font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';
}
@supports (font-variation-settings: normal) {
:root {
font-family: 'Inter var', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
}
}

View File

@@ -0,0 +1,3 @@
export const DESCRIPTION = 'Imagine a guide... that explores the many possibilities for your discord.js bot.';
export const GITHUB_BASE_PAGES_PATH = 'https://github.com/discordjs/discord.js/tree/main/packages/guide/src/pages';

View File

@@ -0,0 +1,5 @@
import { GITHUB_BASE_PAGES_PATH } from './constants.js';
export function generateGithubURL(pageURL: string) {
return `${GITHUB_BASE_PAGES_PATH}${pageURL}.mdx`;
}