Sur cette page
Un serveur de ciné-club, une bande d’amis, une communauté locale : une commande /showtimes qui répond avec ce qui passe aujourd’hui évite bien des allers-retours entre onglets. Ce tutoriel construit ce bot Discord en Node.js avec discord.js, en trois petits fichiers.
/showtimes city:Lyon version:VOSTLe bot répond par une carte listant les films à l’affiche aujourd’hui, les plus programmés d’abord, chacun avec ses cinémas et ses horaires.
Ce qu’il vous faut
- Node.js 20 ou plus récent.
- Une clé API Cinéma : créez un compte gratuit, confirmez votre adresse e-mail, puis créez une clé depuis le tableau de bord. L’offre gratuite donne 150 requêtes par semaine pour les tests et les projets non commerciaux, une par commande.
- Une application Discord : dans le portail développeur de Discord, créez une application, puis dans Bot copiez son jeton. Dans OAuth2 › URL Generator, cochez
botetapplications.commands, puis ouvrez l’URL générée pour ajouter le bot à votre serveur.
Préparer le projet
mkdir showtimes-bot && cd showtimes-bot
npm init -y
npm pkg set type=module
npm install discord.jsLes trois secrets vont dans des variables d’environnement, jamais dans le code :
export DISCORD_TOKEN="…" # Bot › Token
export DISCORD_APP_ID="…" # General Information › Application ID
export API_CINEMA_KEY="ac_live_…"Lire les séances : showtimes.js
Deux fonctions : l’une appelle l’API, l’autre transforme les séances en champs de carte (embed), un par film.
const API = 'https://api.api-cinema.com/v1'
/** Les prochaines séances du jour dans une ville (à partir de maintenant, à l’heure locale de chaque cinéma). */
export async function fetchShowtimes(city, version) {
const params = new URLSearchParams({ city, limit: '500' })
if (version)
params.set('version', version)
const res = await fetch(`${API}/showtimes?${params}`, {
headers: { Authorization: `Bearer ${process.env.API_CINEMA_KEY}` },
})
if (!res.ok)
throw new Error(`API Cinéma a répondu ${res.status}`)
const { data } = await res.json()
// ne garder que le premier jour : l’API donne les séances à venir, celles du jour d’abord
const day = data[0]?.startsAt.slice(0, 10)
return data.filter(s => s.startsAt.startsWith(day))
}
/** Un champ par film (le plus programmé d’abord) : chaque cinéma sur une ligne avec ses horaires. */
export function toFields(showtimes, maxFilms = 10) {
const films = new Map()
for (const s of showtimes) {
const film = films.get(s.movie.id) ?? { title: s.movie.title, cinemas: new Map(), count: 0 }
const times = film.cinemas.get(s.cinema.name) ?? []
times.push(s.startsAt.slice(11, 16) + (s.version === 'VF' || s.version === 'VO' ? '' : ` ${s.version}`))
film.cinemas.set(s.cinema.name, times)
film.count++
films.set(s.movie.id, film)
}
return [...films.values()]
.sort((a, b) => b.count - a.count)
.slice(0, maxFilms)
.map((film) => {
const lines = [...film.cinemas].map(([cinema, times]) => `**${cinema}** · ${times.join(', ')}`)
let value = lines.join('\n')
// un champ contient 1 024 caractères au plus
if (value.length > 1024)
value = `${value.slice(0, 1020).replace(/\n[^\n]*$/, '')}\n…`
return { name: film.title.slice(0, 256), value }
})
}Quelques détails :
- Sans
datenifrom, l’API renvoie les séances à partir de maintenant, à l’heure locale de chaque cinéma, triées par heure. Garder les lignes de la première date donne « le reste de la journée ». startsAtest l’heure du cinéma (2026-10-24T20:30:00) :slice(11, 16)donne directement20:30, quel que soit le fuseau du serveur du bot.- Les cartes Discord acceptent 25 champs et 1 024 caractères par champ : dix films et une coupe à la dernière ligne complète restent dans les clous.
Déclarer la commande : commands.js
import { SlashCommandBuilder } from 'discord.js'
export const showtimesCommand = new SlashCommandBuilder()
.setName('showtimes')
.setDescription('Les séances du jour dans une ville')
.addStringOption(option => option
.setName('city')
.setDescription('Ville, par exemple Lyon ou Chicago')
.setRequired(true))
.addStringOption(option => option
.setName('version')
.setDescription('Une seule version')
.addChoices(
{ name: 'VOST', value: 'VOST' },
{ name: 'VO', value: 'VO' },
{ name: 'VF', value: 'VF' },
))Enregistrez-la une fois auprès de Discord, dans register.js :
import { REST, Routes } from 'discord.js'
import { showtimesCommand } from './commands.js'
const rest = new REST().setToken(process.env.DISCORD_TOKEN)
await rest.put(Routes.applicationCommands(process.env.DISCORD_APP_ID), { body: [showtimesCommand.toJSON()] })
console.log('/showtimes enregistrée')node register.jsLe bot : bot.js
import { Client, EmbedBuilder, Events, GatewayIntentBits } from 'discord.js'
import { fetchShowtimes, toFields } from './showtimes.js'
const client = new Client({ intents: [GatewayIntentBits.Guilds] })
client.once(Events.ClientReady, c => console.log(`Connecté en tant que ${c.user.tag}`))
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand() || interaction.commandName !== 'showtimes')
return
const city = interaction.options.getString('city', true)
const version = interaction.options.getString('version')
// l’API répond en bien moins d’une seconde, mais Discord n’attend que 3 s une première réponse
await interaction.deferReply()
try {
const showtimes = await fetchShowtimes(city, version)
if (showtimes.length === 0) {
await interaction.editReply(`Aucune séance à venir trouvée à ${city}.`)
return
}
const embed = new EmbedBuilder()
.setTitle(`Séances à ${city}, ${showtimes[0].startsAt.slice(0, 10)}`)
.setColor(0xB8302F)
.addFields(toFields(showtimes))
.setFooter({ text: 'Données : API Cinéma' })
await interaction.editReply({ embeds: [embed] })
}
catch (error) {
console.error(error)
await interaction.editReply('Séances indisponibles pour le moment, réessayez plus tard.')
}
})
client.login(process.env.DISCORD_TOKEN)node bot.jsTapez /showtimes city:Lyon dans votre serveur. À Lyon, un après-midi de semaine, la première carte ressemble à ceci :
Les Misérables
Ugc Cine Cite Confluence · 13:30, 15:30, 17:00, 19:00, 20:30
Ugc Cine Cite Part-Dieu · 13:30, 15:30, 17:00, 19:00, 20:30
Pathe Bellecour · 14:05, 17:40, 20:00Ça marche de la même façon avec une ville américaine : /showtimes city:Chicago.
Pour aller plus loin
- Noms de villes.
citycompare exactement le nom de la commune : Paris exclut sa banlieue, New York exclut Brooklyn. Pour toute une agglomération, ajoutez des optionslatetlnget appelez l’API avecnear=lat,lng&radius_km=5à la place. - Un message quotidien. Plutôt qu’une commande, publiez le programme du lendemain dans un salon chaque soir : appelez
fetchShowtimesdepuis unsetIntervalou une tâche cron, puischannel.send({ embeds: [embed] }). - Un cache. Chaque commande coûte une requête. Sur un serveur actif, gardez la réponse de chaque ville dans une
Mappendant 15 minutes. - Affiches et liens.
s.movie.posterUrldonne l’affiche (embed.setThumbnail),s.bookingUrlla page de réservation de chaque séance.
Chaque paramètre et chaque champ sont dans la documentation ; le guide pour la France et le guide pour les États-Unis montrent les autres recherches.