-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapp.js
137 lines (117 loc) · 4.28 KB
/
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import { readFileSync, writeFile } from 'fs'
import { join, basename } from 'path'
import { parse as parseUrl } from 'url'
import { parse as parseQuery } from 'querystring'
import { hostname } from 'os'
import { createServer } from 'http'
import { sync as glob } from 'glob'
import { sync as mkdirp } from 'mkdirp'
import { pack, unpack } from 'msgpack'
import express from 'express'
import { Server as WebSocketServer } from 'ws'
import TelegramBot from 'node-telegram-bot-api'
import Emulator from './lib/emulator'
// == CONFIG ==
const token = process.env['NODE_BOT_TOKEN']
if (!token) {
console.error('NODE_BOT_TOKEN not found on environment variables')
process.exit(1)
}
const host = process.env['NODE_HOSTNAME'] || hostname()
console.log(`Host set to: ${ host }`)
const romsPath = process.env['NODE_ROMS_PATH'] || './roms'
console.log(`Roms directory set to: ${ romsPath }`)
mkdirp(romsPath) // Create directory if doesn't exist
// == EMULATORS ==
const roms = glob(join(romsPath, '*.gbc'), { absolute: true })
console.log(`found ${ roms.length } roms, setting up emulators`)
// Setup emulators hash for its usage by the app later
const emulators = roms.reduce((emulators, romPath) => {
const romName = basename(romPath, '.gbc')
const saves = glob(join(romsPath, `${romName}-*.sav`)).sort()
// Load rom or state depending of whats available
const emu = Emulator()
if (saves.length > 0) {
console.log(`found save state for ${ romName }, loading it.`)
const state = unpack(readFileSync(saves.pop())) // Take the latest available
emu.initWithState(state)
} else {
console.log(`didn't found a save state for ${ romName }, starting rom from scracth`)
const rom = readFileSync(romPath)
emu.initWithRom(rom)
}
// Periodically save a state of the game
setInterval(() => {
const snapshot = emu.snapshot()
if (snapshot) {
const stateFilename = join(romsPath, `${ romName }-${ Date.now() }.sav`)
console.log(`saving state for ${ romName } to file ${ basename(stateFilename) }`)
const state = pack(snapshot)
writeFile(stateFilename, state, (err) => { if (err) throw err })
}
}, 60000)
// Start the emulator for this rom
emu.run()
// Set the emulator on the hash
emulators[romName] = emu
return emulators
}, {})
// Store last frames emited by each emulator for client initialization
let lastFrames = {}
Object.keys(emulators).forEach(game => {
emulators[game].on('frame', frame => lastFrames[game] = frame)
})
// == WEB SERVER ==
// Setup game server
const app = express()
const server = createServer(app)
const wss = new WebSocketServer({ server })
// Helper function for broadcasting current client count to all clients
function wssBroadcastClientCount() {
wss.clients.forEach(client => client.send(String(wss.clients.length)))
}
wss.on('connection', (ws) => {
const url = ws.upgradeReq.url
const query = parseUrl(url).query
const queryParams = parseQuery(query)
const game = queryParams.game
const emu = emulators[game]
if (emu) {
// Send last frame if available (useful when static images)
if (lastFrames[game]) ws.send(lastFrames[game])
wssBroadcastClientCount()
const sendFrame = (frame => ws.send(frame))
emu.on('frame', sendFrame)
ws.on('message', key => emu.move(key))
ws.on('close', () => {
emu.removeListener('frame', sendFrame)
wssBroadcastClientCount()
})
} else {
console.error(`Couldn't find game ${ game }`)
}
})
app.use(express.static(__dirname + '/public'))
server.listen(3000, err => {
if (err)
console.error(err)
})
// == TELEGRAM BOT ==
const games = roms.map(rom => basename(rom, '.gbc'))
const bot = new TelegramBot(token, { polling: true })
// Send all available games on each request
bot.on('inline_query', msg => {
const result = games.map(game => (
{
id: String(Date.now()),
type: 'game',
game_short_name: game
}
))
bot.answerInlineQuery(msg.id, result)
})
// Send individual game urls
bot.on('callback_query', msg => {
const url = `http://${ host }/?game=${ msg.game_short_name }`
bot.answerCallbackQuery(msg.id, '', false, { url })
})