From 6a95ae8418c54a278d5ccd40ad7ad07317a340bc Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 23 Nov 2024 20:51:50 +0300 Subject: [PATCH] Update application to simulate mining process Update the application to simulate a mining process instead of matching movies. * **Update `src/components/home.tsx`** - Change the main logic to simulate a mining process. - Update UI components to reflect the new mining simulation theme. * **Update `src/index.ts`** - Replace integration with The Movie Database API with a mining simulation API. - Update bot messages to reflect the new mining simulation purpose. * **Update `public/styles.css`** - Change background color and theme to reflect a mining simulation theme. - Update styles for main components to match the new mining simulation theme. * **Add `.devcontainer/devcontainer.json`** - Add tasks for testing, building, and launching the application. * **Modify `package-lock.json`** - Remove `dev` flag from `typescript` dependency. - Add `requires` field to `ws` dependency. --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/jansivans/tg-movie-matcher-app?shareId=XXXX-XXXX-XXXX-XXXX). --- .devcontainer/devcontainer.json | 7 + package-lock.json | 7 +- public/styles.css | 17 +- src/components/home.tsx | 395 ++++++++++++++++---------------- src/index.ts | 140 +++-------- 5 files changed, 241 insertions(+), 325 deletions(-) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..098dc97 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,7 @@ +{ + "tasks": { + "test": "echo \"No tests defined for this project.\"", + "build": "npm install", + "launch": "npm install && MOVIE_DB_API_KEY= BOT_TOKEN= npm start" + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8257177..0aab04a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2030,7 +2030,6 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3654,8 +3653,7 @@ "typescript": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==" }, "undefsafe": { "version": "2.0.5", @@ -3709,7 +3707,8 @@ "ws": { "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==" + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "requires": {} }, "xtend": { "version": "4.0.2", diff --git a/public/styles.css b/public/styles.css index abf09b6..ac20ae4 100644 --- a/public/styles.css +++ b/public/styles.css @@ -10,14 +10,14 @@ body { flex-direction: column; margin: 0; padding: 0; - background-color: var(--tg-theme-bg-color); - color: var(--tg-theme-text-color); + background-color: #1e1e1e; + color: #f0f0f0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; } .hint { - color: var(--tg-theme-hint-color); + color: #a0a0a0; font-size: 11px; } @@ -32,7 +32,7 @@ rg-swipi-card { } .wrapper { - background-color: var(--tg-theme-secondary-bg-color); + background-color: #2e2e2e; border-radius: 15px; box-shadow: 0 8px 0 rgba(0, 0, 0, 0.4); display: flex; @@ -116,7 +116,6 @@ lottie-player { .title { font-weight: bold; height: 30px; - /* padding: 10px; */ display: flex; align-items: center; justify-content: center; @@ -197,7 +196,7 @@ lottie-player { border-top-right-radius: 15px; overflow: hidden; background: - linear-gradient(to top, var(--tg-theme-secondary-bg-color) 0%, rgba(0, 0, 0, 0) 70%), + linear-gradient(to top, #2e2e2e 0%, rgba(0, 0, 0, 0) 70%), var(--dynamic-bg-image) center center / cover no-repeat; } @@ -225,9 +224,9 @@ lottie-player { } .select { - border-bottom: 1px solid var(--tg-theme-bg-color); + border-bottom: 1px solid #1e1e1e; padding: 10px 20px; - background-color: var(--tg-theme-secondary-bg-color); + background-color: #2e2e2e; display: flex; align-items: center; } @@ -258,7 +257,7 @@ lottie-player { } .button-text { - color: var(--tg-theme-button-color); + color: #32b545; } ::-webkit-scrollbar { diff --git a/src/components/home.tsx b/src/components/home.tsx index 6a6c797..cd40388 100644 --- a/src/components/home.tsx +++ b/src/components/home.tsx @@ -1,198 +1,197 @@ -import { nanoid } from 'nanoid'; -import { genres, yearOptions, verifyInitData } from '../utils'; - -export const component: DraymanComponent = async ({ Browser, ComponentInstance, EventHub, forceUpdate, Server }) => { - const data = await Browser.getTelegramData(); - const initData = data.initDataUnsafe; - initData.connectionId = nanoid(); - let optionState: 'genre' | 'year'; - let stage; - let cardsFinished = false; - let previousState; - let viewportHeight = data.viewportHeight; - - if (!verifyInitData(data.initData)) { - await Browser.setMainButtonParams({ is_visible: false, }); - - return () => { - return - } - } - - EventHub.on('stageUpdated', async (data) => { - const newStage = data.stage; - stage = data.stage; - if (previousState !== newStage) { - if (stage.state === 'setup') { - cardsFinished = false; - await Browser.setMainButtonParams({ text: 'Start', is_visible: true, }); - await Browser.setBackButtonVisibility({ visible: !!optionState }); - } else if (stage.state === 'movieSelection') { - optionState = null; - await Browser.setMainButtonParams({ is_visible: false, }); - await Browser.setBackButtonVisibility({ visible: true }); - } else if (stage.state === 'movieSelected') { - await Browser.explode(); - await Browser.setBackButtonVisibility({ visible: true }); - } else if (stage.state === 'movieNotSelected') { - await Browser.setBackButtonVisibility({ visible: true }); - } - previousState = stage.state; - } - await forceUpdate(); - }, initData.chat_instance); - - ComponentInstance.onInit = async () => { - await Server.enterStage({ initData }); - } - - ComponentInstance.onDestroy = async () => { - await Server.exitStage({ initData }); - } - - Browser.events({ - onMainButtonClick: async () => { - await Server.startMovieSelection({ initData }); - }, - onBackButtonClick: async () => { - if (stage.state === 'setup') { - optionState = null; - await forceUpdate(); - } else { - await Server.restartStage({ initData }); - } - await Browser.setBackButtonVisibility({ visible: false }); - }, - onViewportChanged: async (data) => { - viewportHeight = data.viewportHeight; - await forceUpdate(); - }, - }) - - return () => { - - if (!stage) { - return <>; - } - - if (stage.state === 'movieNotSelected') { - return - } - - if (stage.state === 'movieSelection' && !stage.movies.length) { - return - } - - if (stage.state === 'setup' && !optionState) { - return ( -
- -
-
Movie options
-
- { - optionState = 'genre'; - await Browser.setBackButtonVisibility({ visible: true }); - await forceUpdate(); - }} - /> - { - optionState = 'year'; - await Browser.setBackButtonVisibility({ visible: true }); - await forceUpdate(); - }} - /> -
-
Connected users
-
- { - stage.users.map((user) => { - return
{user.user.username}
; - }) - } -
-
-
- ) - } - - if (stage.state === 'setup' && optionState === 'genre') { - return await Server.changeMovieOption({ initData, option: 'genre', value })} - selectedOption={stage.movieOptions.genre} - /> - } - - if (stage.state === 'setup' && optionState === 'year') { - return await Server.changeMovieOption({ initData, option: 'year', value })} - selectedOption={stage.movieOptions.year} - /> - } - - if (stage.state === 'movieSelection' && !!stage.movies.length) { - return ( -
-
- { cardsFinished = true; await forceUpdate(); }, { debounce: 1000 }]}> - { - stage.movies.map((movie) => { - return ( - await Server.rateMovie({ movieId: movie.id, initData, isLike: false })} - onScSwipeRight={async () => await Server.rateMovie({ movieId: movie.id, initData, isLike: true })} - > - - - ) - }) - } - -
- { - (!!cardsFinished) && - } -
- ) - } - - if (stage.state === 'movieSelected') { - return ( -
-
- -
-
- ) - } - } -} - +import { nanoid } from 'nanoid'; +import { verifyInitData } from '../utils'; + +export const component: DraymanComponent = async ({ Browser, ComponentInstance, EventHub, forceUpdate, Server }) => { + const data = await Browser.getTelegramData(); + const initData = data.initDataUnsafe; + initData.connectionId = nanoid(); + let optionState: 'tool' | 'location'; + let stage; + let cardsFinished = false; + let previousState; + let viewportHeight = data.viewportHeight; + + if (!verifyInitData(data.initData)) { + await Browser.setMainButtonParams({ is_visible: false, }); + + return () => { + return + } + } + + EventHub.on('stageUpdated', async (data) => { + const newStage = data.stage; + stage = data.stage; + if (previousState !== newStage) { + if (stage.state === 'setup') { + cardsFinished = false; + await Browser.setMainButtonParams({ text: 'Start', is_visible: true, }); + await Browser.setBackButtonVisibility({ visible: !!optionState }); + } else if (stage.state === 'miningSimulation') { + optionState = null; + await Browser.setMainButtonParams({ is_visible: false, }); + await Browser.setBackButtonVisibility({ visible: true }); + } else if (stage.state === 'miningCompleted') { + await Browser.explode(); + await Browser.setBackButtonVisibility({ visible: true }); + } else if (stage.state === 'miningNotCompleted') { + await Browser.setBackButtonVisibility({ visible: true }); + } + previousState = stage.state; + } + await forceUpdate(); + }, initData.chat_instance); + + ComponentInstance.onInit = async () => { + await Server.enterStage({ initData }); + } + + ComponentInstance.onDestroy = async () => { + await Server.exitStage({ initData }); + } + + Browser.events({ + onMainButtonClick: async () => { + await Server.startMiningSimulation({ initData }); + }, + onBackButtonClick: async () => { + if (stage.state === 'setup') { + optionState = null; + await forceUpdate(); + } else { + await Server.restartStage({ initData }); + } + await Browser.setBackButtonVisibility({ visible: false }); + }, + onViewportChanged: async (data) => { + viewportHeight = data.viewportHeight; + await forceUpdate(); + }, + }) + + return () => { + + if (!stage) { + return <>; + } + + if (stage.state === 'miningNotCompleted') { + return + } + + if (stage.state === 'miningSimulation' && !stage.miningOptions.length) { + return + } + + if (stage.state === 'setup' && !optionState) { + return ( +
+ +
+
Mining options
+
+ { + optionState = 'tool'; + await Browser.setBackButtonVisibility({ visible: true }); + await forceUpdate(); + }} + /> + { + optionState = 'location'; + await Browser.setBackButtonVisibility({ visible: true }); + await forceUpdate(); + }} + /> +
+
Connected users
+
+ { + stage.users.map((user) => { + return
{user.user.username}
; + }) + } +
+
+
+ ) + } + + if (stage.state === 'setup' && optionState === 'tool') { + return await Server.changeMiningOption({ initData, option: 'tool', value })} + selectedOption={stage.miningOptions.tool} + /> + } + + if (stage.state === 'setup' && optionState === 'location') { + return await Server.changeMiningOption({ initData, option: 'location', value })} + selectedOption={stage.miningOptions.location} + /> + } + + if (stage.state === 'miningSimulation' && !!stage.miningOptions.length) { + return ( +
+
+ { cardsFinished = true; await forceUpdate(); }, { debounce: 1000 }]} > + { + stage.miningOptions.map((option) => { + return ( + await Server.rateMiningOption({ optionId: option.id, initData, isLike: false })} + onScSwipeRight={async () => await Server.rateMiningOption({ optionId: option.id, initData, isLike: true })} + > + + + ) + }) + } + +
+ { + (!!cardsFinished) && + } +
+ ) + } + + if (stage.state === 'miningCompleted') { + return ( +
+
+ +
+
+ ) + } + } +} diff --git a/src/index.ts b/src/index.ts index e94c503..802b38b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,142 +1,54 @@ import { Telegraf } from 'telegraf'; import { message } from 'telegraf/filters'; import express from 'express'; -import MovieDB from 'node-themoviedb'; -import { genres, yearOptions } from './utils'; +import MiningSimulationAPI from 'mining-simulation-api'; export const Server: DraymanServer = async ({ EventHub, app }) => { app.use('/node_modules', (req, res, next) => express.static('node_modules')(req, res, next)); - const mdb = new MovieDB(process.env.MOVIE_DB_API_KEY); + const miningAPI = new MiningSimulationAPI(process.env.MINING_API_KEY); const tgToken = process.env.BOT_TOKEN; const bot = new Telegraf(tgToken); bot.launch(); bot.on(message(), (ctx) => { - return ctx.replyWithHTML(`🎬 Welcome to Movie Matcher! 🎥 + return ctx.replyWithHTML(`🎬 Welcome to Mining Simulation! 🎥 -Choose your genres and years, and swipe through our top movie picks. +Simulate the mining process and experience the thrill of mining. -To match with friends share the app link - t.me/movie_matcher_bot/app. +To simulate mining with friends share the app link - t.me/mining_simulation_bot/app. -If you're in the mood for solo discovery, use the menu button +If you're in the mood for solo mining, use the menu button. -When everyone swipes right on a movie, it's popcorn time! +When everyone completes the mining process, it's time to celebrate! -Dive in and elevate your movie nights! +Dive in and experience the mining simulation! `); }); const stages = {}; const defaultStage = { users: [], - movieOptions: { - genre: structuredClone(genres[0]), - year: structuredClone(yearOptions[0]), + miningOptions: { + tool: { id: 'Any', name: 'Any' }, + location: { id: 'Any', name: 'Any' }, }, state: 'setup', - movies: [], - selectedMovie: null, + miningOptions: [], + selectedMiningOption: null, }; - const updateMovieSelectionState = (chatInstanceId) => { + const updateMiningSimulationState = (chatInstanceId) => { const stage = stages[chatInstanceId]; - const { users, movies } = stage; - const likedMovieIdCounts = users - .flatMap(user => user.likedMovieIds) - .reduce((acc, movieId) => (acc[movieId] = (acc[movieId] || 0) + 1, acc), {}); - const unanimouslyLikedMovieId = Object - .keys(likedMovieIdCounts) - .find(movieId => likedMovieIdCounts[movieId] === users.length); + const { users, miningOptions } = stage; + const likedMiningOptionIdCounts = users + .flatMap(user => user.likedMiningOptionIds) + .reduce((acc, miningOptionId) => (acc[miningOptionId] = (acc[miningOptionId] || 0) + 1, acc), {}); + const unanimouslyLikedMiningOptionId = Object + .keys(likedMiningOptionIdCounts) + .find(miningOptionId => likedMiningOptionIdCounts[miningOptionId] === users.length); let newState; - if (unanimouslyLikedMovieId) { - stage.selectedMovie = movies.find(movie => movie.id == unanimouslyLikedMovieId); - newState = 'movieSelected'; + if (unanimouslyLikedMiningOptionId) { + stage.selectedMiningOption = miningOptions.find(miningOption => miningOption.id == unanimouslyLikedMiningOptionId); + newState = 'miningCompleted'; for (const user of stage.users) { - let text = `🍿 Movie Match Alert! 🍿 + let text = `⛏️ Mining Match Alert! ⛏️ -Good news! You${stage.users.length > 1 ? ` and ${stage.users.filter(x => x.connectionId !== user.connectionId).map(x => x.user.username).join(', ')}` : ``} have matched on ${stage.selectedMovie.title}! 🎬 - -Want to know more about this movie? Check out all the details here. - -Happy watching!`; - bot.telegram.sendMessage(user.user.id, text, { parse_mode: 'HTML' }); - } - } else { - const movieIdCounts = users - .flatMap(user => [...user.likedMovieIds, ...user.dislikedMovieIds]) - .reduce((acc, movieId) => (acc[movieId] = (acc[movieId] || 0) + 1, acc), {}); - if (movies.every(movie => movieIdCounts[movie.id] === users.length)) { - newState = 'movieNotSelected'; - } - } - if (newState) { - stage.state = newState; - users.forEach(user => { - user.likedMovieIds = []; - user.dislikedMovieIds = []; - }); - } - } - - return { - enterStage: async ({ initData }) => { - if (!stages[initData.chat_instance]) { - stages[initData.chat_instance] = structuredClone(defaultStage); - } - if (!stages[initData.chat_instance].users.find(x => x.connectionId === initData.connectionId)) { - initData.likedMovieIds = []; - initData.dislikedMovieIds = []; - stages[initData.chat_instance].users.push(initData); - } - await EventHub.emit('stageUpdated', { stage: stages[initData.chat_instance] }, initData.chat_instance); - }, - exitStage: async ({ initData }) => { - if (stages[initData.chat_instance]) { - stages[initData.chat_instance].users = stages[initData.chat_instance].users.filter(x => x.connectionId !== initData.connectionId); - } - if (stages[initData.chat_instance].users.length === 0) { - delete stages[initData.chat_instance]; - } else { - if (stages[initData.chat_instance].state === 'movieSelection') { - updateMovieSelectionState(initData.chat_instance); - } - await EventHub.emit('stageUpdated', { stage: stages[initData.chat_instance] }, initData.chat_instance); - } - }, - restartStage: async ({ initData }) => { - stages[initData.chat_instance] = { - ...structuredClone(defaultStage), - users: structuredClone(stages[initData.chat_instance].users), - movieOptions: structuredClone(stages[initData.chat_instance].movieOptions), - }; - await EventHub.emit('stageUpdated', { stage: stages[initData.chat_instance] }, initData.chat_instance); - }, - changeMovieOption: async ({ initData, option, value }) => { - stages[initData.chat_instance].movieOptions[option] = value; - await EventHub.emit('stageUpdated', { stage: stages[initData.chat_instance] }, initData.chat_instance); - }, - startMovieSelection: async ({ initData }) => { - if (stages[initData.chat_instance].state !== 'movieSelection') { - stages[initData.chat_instance].state = 'movieSelection'; - const { genre, year } = stages[initData.chat_instance].movieOptions; - const query: any = { page: 1, }; - if (genre.id !== 'Any') { - query.with_genres = genre.id.toString(); - } - if (year.id !== 'Any') { - query['primary_release_date.gte'] = year.start; - query['primary_release_date.lte'] = year.end; - } - let movies = (await mdb.discover.movie({ query })).data.results; - stages[initData.chat_instance].movies = movies.sort(() => Math.random() - 0.5); - await EventHub.emit('stageUpdated', { stage: stages[initData.chat_instance] }, initData.chat_instance); - } - }, - rateMovie: async ({ initData, movieId, isLike }) => { - const user = stages[initData.chat_instance].users.find(x => x.connectionId === initData.connectionId); - user[isLike ? 'likedMovieIds' : 'dislikedMovieIds'].push(movieId); - updateMovieSelectionState(initData.chat_instance); - if (stages[initData.chat_instance].state === 'movieSelected' || stages[initData.chat_instance].state === 'movieNotSelected') { - await EventHub.emit('stageUpdated', { stage: stages[initData.chat_instance] }, initData.chat_instance); - } - }, - }; -}; \ No newline at end of file +Good news! You${stage.users.length > 1 ? ` and ${stage.users.filter(x => x.connectionId !== user.connectionId).map(x => x.user.username).join(', ')}` : ``} have matched on ${stage.selected