diff --git a/.github/helper-bot/github-helper.js b/.github/helper-bot/github-helper.js
new file mode 100644
index 0000000..08759b2
--- /dev/null
+++ b/.github/helper-bot/github-helper.js
@@ -0,0 +1,59 @@
+if (!process.env.CI) {
+ // mock a bunch of things for testing locally -- https://github.com/actions/toolkit/issues/71
+ process.env.GITHUB_REPOSITORY = 'PrismarineJS/bedrock-protocol'
+ process.env.GITHUB_EVENT_NAME = 'issue_comment'
+ process.env.GITHUB_SHA = 'cb2fd97b6eae9f2c7fee79d5a86eb9c3b4ac80d8'
+ process.env.GITHUB_REF = 'refs/heads/master'
+ process.env.GITHUB_WORKFLOW = 'Issue comments'
+ process.env.GITHUB_ACTION = 'run1'
+ process.env.GITHUB_ACTOR = 'test-user'
+ module.exports = { getIssueStatus: () => ({}), updateIssue: () => {}, createIssue: () => {} }
+ return
+}
+
+// const { Octokit } = require('@octokit/rest') // https://github.com/octokit/rest.js
+const github = require('@actions/github')
+
+const token = process.env.GITHUB_TOKEN
+const octokit = github.getOctokit(token)
+const context = github.context
+
+async function getIssueStatus (title) {
+ // https://docs.github.com/en/rest/reference/search#search-issues-and-pull-requests
+ const existingIssues = await octokit.rest.search.issuesAndPullRequests({
+ q: `is:issue repo:${process.env.GITHUB_REPOSITORY} in:title ${title}`
+ })
+ // console.log('Existing issues', existingIssues)
+ const existingIssue = existingIssues.data.items.find(issue => issue.title === title)
+
+ if (!existingIssue) return {}
+
+ return { open: existingIssue.state === 'open', closed: existingIssue.state === 'closed', id: existingIssue.number }
+}
+
+async function updateIssue (id, payload) {
+ const issue = await octokit.rest.issues.update({
+ ...context.repo,
+ issue_number: id,
+ body: payload.body
+ })
+ console.log(`Updated issue ${issue.data.title}#${issue.data.number}: ${issue.data.html_url}`)
+}
+
+async function createIssue (payload) {
+ const issue = await octokit.rest.issues.create({
+ ...context.repo,
+ ...payload
+ })
+ console.log(`Created issue ${issue.data.title}#${issue.data.number}: ${issue.data.html_url}`)
+}
+
+async function close (id, reason) {
+ if (reason) await octokit.rest.issues.createComment({ ...context.repo, issue_number: id, body: reason })
+ const issue = await octokit.rest.issues.update({ ...context.repo, issue_number: id, state: 'closed' })
+ console.log(`Closed issue ${issue.data.title}#${issue.data.number}: ${issue.data.html_url}`)
+}
+
+if (process.env.CI) {
+ module.exports = { getIssueStatus, updateIssue, createIssue, close }
+}
diff --git a/.github/helper-bot/index.js b/.github/helper-bot/index.js
new file mode 100644
index 0000000..58d7856
--- /dev/null
+++ b/.github/helper-bot/index.js
@@ -0,0 +1,137 @@
+// Automatic version update checker for Minecraft bedrock edition.
+const fs = require('fs')
+const cp = require('child_process')
+const helper = require('./github-helper')
+const latestVesionEndpoint = 'https://itunes.apple.com/lookup?bundleId=com.mojang.minecraftpe'
+const changelogURL = 'https://feedback.minecraft.net/hc/en-us/sections/360001186971-Release-Changelogs'
+
+// Relevant infomation for us is:
+// "version": "1.17.10",
+// "currentVersionReleaseDate": "2021-07-13T15:35:49Z",
+// "releaseNotes": "What's new in 1.17.10:\nVarious bug fixes",
+
+function buildFirstIssue (title, result, externalPatches) {
+ let commitData = ''
+ let protocolVersion = '?'
+ const date = new Date(result.currentVersionReleaseDate).toUTCString()
+
+ for (const name in externalPatches) {
+ commitData += '### ' + name + '\n'
+ const [patches, diff] = externalPatches[name]
+ for (const [name, url] of patches) {
+ commitData += `${name}\n`
+ }
+ commitData += `\n**[See the diff between *${result.currentVersionReleaseDate}* and now](${diff})**\n`
+ }
+ try { protocolVersion = getProtocolVersion() } catch (e) { console.log(e) }
+
+ return {
+ title,
+ body: `
+A new Minecraft Bedrock version is available (as of ${date}), version **${result.version}**
+
+## Official Changelog
+* ${result.releaseNotes} *(via App Store)*
+* ${changelogURL}
+
+## 3rd party protocol patches
+${commitData}
+
+## Protocol Details
+(I will close this issue automatically if "${result.version}" is added to index.d.ts on "master" and there are no X's below)
+
+ | Name | ${result.version} |
+
| Protocol ID | ${protocolVersion} |
+
+
+
+-----
+
+🤖 I am a bot, I check for updates every 2 hours without a trigger. You can close this PR to prevent any further updates.
+ `
+ }
+}
+
+function getCommitsInRepo (repo, containing, since) {
+ const endpoint = `https://api.github.com/repos/${repo}/commits`
+ console.log('Getting', endpoint)
+ cp.execSync(`curl -L ${endpoint} -o commits.json`, { stdio: 'inherit', shell: true })
+ const commits = JSON.parse(fs.readFileSync('./commits.json', 'utf-8'))
+ const relevant = []
+ for (const commit of commits) {
+ if (commit.commit.message.includes(containing)) {
+ console.log('commit url', commit.html_url)
+ relevant.push([commit.commit.message, commit.html_url])
+ }
+ }
+ if (since) {
+ cp.execSync(`curl -L ${endpoint}?since=${since} -o commits.json`, { stdio: 'inherit', shell: true })
+ const commits = JSON.parse(fs.readFileSync('./commits.json', 'utf-8'))
+ const head = commits[0].sha
+ const tail = commits[commits.length - 1].sha
+ return [relevant, `https://github.com/${repo}/compare/${tail}..${head}`]
+ }
+ return [relevant]
+}
+
+function getProtocolVersion () {
+ if (!fs.existsSync('./ProtocolInfo.php')) cp.execSync('curl -LO https://raw.githubusercontent.com/pmmp/PocketMine-MP/stable/src/pocketmine/network/mcpe/protocol/ProtocolInfo.php', { stdio: 'inherit', shell: true })
+ const currentApi = fs.readFileSync('./ProtocolInfo.php', 'utf-8')
+ const [, latestProtocolVersion] = currentApi.match(/public const CURRENT_PROTOCOL = (\d+);/)
+ return latestProtocolVersion
+}
+
+async function fetchLatest () {
+ if (!fs.existsSync('./results.json')) cp.execSync(`curl -L ${latestVesionEndpoint} -o results.json`, { stdio: 'inherit', shell: true })
+ const json = require('./results.json')
+ const result = json.results[0]
+ // console.log(json)
+
+ if (!fs.existsSync('./index.d.ts')) cp.execSync('curl -LO https://raw.githubusercontent.com/PrismarineJS/bedrock-protocol/master/index.d.ts', { stdio: 'inherit', shell: true })
+ const currentApi = fs.readFileSync('./index.d.ts', 'utf-8')
+ const supportedVersions = currentApi.match(/type Version = ([^\n]+)/)[1].replace(/\||'/g, ' ').split(' ').map(k => k.trim()).filter(k => k.length)
+ console.log(supportedVersions)
+
+ let { version, currentVersionReleaseDate, releaseNotes } = result
+ console.log(version, currentVersionReleaseDate, releaseNotes)
+
+ const title = `Support Minecraft ${result.version}`
+
+ const issueStatus = await helper.getIssueStatus(title)
+
+ if (supportedVersions.includes(version)) {
+ if (issueStatus.open) {
+ helper.close(issueStatus.id, `Closing as ${version} is now supported`)
+ }
+ console.log('Latest version is supported.')
+ return
+ }
+
+
+ if (issueStatus.closed) {
+ // We already made an issue, but someone else already closed it, don't do anything else
+ console.log('I already made an issue, but it was closed')
+ return
+ }
+
+ version = version.replace('.0', '')
+ const issuePayload = buildFirstIssue(title, result, {
+ PocketMine: getCommitsInRepo('pmmp/PocketMine-MP', version, currentVersionReleaseDate),
+ gophertunnel: getCommitsInRepo('Sandertv/gophertunnel', version, currentVersionReleaseDate),
+ CloudburstMC: getCommitsInRepo('CloudburstMC/Protocol', version, currentVersionReleaseDate)
+ })
+
+ if (issueStatus.open) {
+ helper.updateIssue(issueStatus.id, issuePayload)
+ } else {
+ helper.createIssue(issuePayload)
+ }
+
+ fs.writeFileSync('./issue.md', issuePayload.body)
+ console.log('OK, wrote to ./issue.md', issuePayload)
+}
+
+fetchLatest()
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0a1ac07..580260c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,8 +14,6 @@ jobs:
strategy:
matrix:
node-version: [14.x]
- env:
- FORCE_BUILD: true
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
diff --git a/.github/workflows/update-helper.yml b/.github/workflows/update-helper.yml
new file mode 100644
index 0000000..83503ac
--- /dev/null
+++ b/.github/workflows/update-helper.yml
@@ -0,0 +1,24 @@
+name: Update Helper
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: "0 */2 * * *"
+
+jobs:
+ helper:
+ name: update-checker
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@master
+ - name: Set up Node.js
+ uses: actions/setup-node@master
+ with:
+ node-version: 16.0.0
+ - name: Install Github Actions toolkit
+ run: npm i @actions/github
+ # The env vars contain the relevant trigger information, so we don't need to pass it
+ - name: Runs helper
+ run: cd .github/helper-bot && node index.js
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/test/internal.test.js b/test/internal.test.js
index 32207e3..d6c11d8 100644
--- a/test/internal.test.js
+++ b/test/internal.test.js
@@ -3,6 +3,7 @@
const { timedTest } = require('./internal')
const { proxyTest } = require('./proxy')
const { Versions } = require('../src/options')
+const { sleep } = require('../src/datatypes/util')
describe('internal client/server test', function () {
const vcount = Object.keys(Versions).length
@@ -12,6 +13,7 @@ describe('internal client/server test', function () {
it('connects ' + version, async () => {
console.debug(version)
await timedTest(version)
+ await sleep(100)
})
}
@@ -19,6 +21,8 @@ describe('internal client/server test', function () {
it('proxies ' + version, async () => {
console.debug(version)
await proxyTest(version)
+ await sleep(5000)
+ console.debug('Done', version)
})
}
})
diff --git a/test/vanilla.test.js b/test/vanilla.test.js
index 9cea359..3bae596 100644
--- a/test/vanilla.test.js
+++ b/test/vanilla.test.js
@@ -2,6 +2,7 @@
const { clientTest } = require('./vanilla')
const { Versions } = require('../src/options')
+const { sleep } = require('../src/datatypes/util')
describe('vanilla server test', function () {
const vcount = Object.keys(Versions).length
@@ -10,6 +11,7 @@ describe('vanilla server test', function () {
for (const version in Versions) {
it('client spawns ' + version, async () => {
await clientTest(version)
+ await sleep(100)
})
}
})