Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Beta Merge #18

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
43 changes: 43 additions & 0 deletions .github/workflows/build-container.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Build the docker image and upload to github cr

name: Create and publish a Docker image

on:
push:
branches: ['beta']

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

- name: Build and push Docker image
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
File renamed without changes.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,6 @@ dist
.tern-port
.vercel
.idea

# JS build
build.js
12 changes: 12 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM node:18-slim AS build-env
WORKDIR /build
COPY . .
RUN yarn install --frozen-lockfile && yarn build

FROM gcr.io/distroless/nodejs18:nonroot AS run-env
WORKDIR /app

COPY --from=build-env /build/build.js .
COPY public ./public

CMD ["build.js"]
2 changes: 2 additions & 0 deletions cloudflare/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export default {
}

const time = Date.now()
// We want to follow the redirects, as gradle can do a pretty
// bad job at following links
const response = await fetch(request, { redirect: 'follow' })
const duration = Date.now() - time

Expand Down
28 changes: 14 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
"description": "A more robust alternative to the curseforge maven",
"main": "src/index.ts",
"scripts": {
"dev": "vercel dev",
"dev": "ts-node-dev src/index.ts",
"test": "cross-env NODE_ENV=test jest --testTimeout=30000",
"vercel-build": "yarn test"
"build": "esbuild src/index.ts --bundle --platform=node --minify --outfile=build.js",
"vercel-build-comment": "Temporary code to build on vercel. This should be removed when I eventually move to an express Dockerfile",
"vercel-build": "yarn test && mkdir -p /vercel/output/functions/src/app.ts.func && cp -r public /vercel/output/functions/src/app.ts.func"
},
"repository": {
"type": "git",
Expand All @@ -19,28 +21,26 @@
},
"homepage": "https://www.cursemaven.com",
"dependencies": {
"dotenv": "^14.2.0",
"dotenv": "^16.3.1",
"escape-html": "^1.0.3",
"express": "^4.17.1",
"node-fetch": "2",
"promise.any": "^2.0.3"
"express": "^4.17.1"
},
"devDependencies": {
"@types/es-aggregate-error": "^1.0.2",
"@types/escape-html": "^1.0.2",
"@types/express": "^4.17.13",
"@types/jest": "^27.4.0",
"@types/node": "^16.11.9",
"@types/node-fetch": "2",
"@types/promise.any": "^2.0.0",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/supertest": "^2.0.11",
"cross-env": "^7.0.3",
"jest": "^27.4.5",
"esbuild": "^0.18.11",
"jest": "^29.5.0",
"supertest": "^6.1.6",
"ts-jest": "^27.1.2",
"ts-jest": "^29.1.0",
"ts-node": "^10.4.0",
"ts-node-dev": "^2.0.0",
"tslint": "^6.1.3",
"typescript": "^4.5.2",
"vercel": "^31.0.0"
"typescript": "^5.1.3",
"vercel": "^30.2.2"
}
}
28 changes: 23 additions & 5 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<div class="main-container">
<h1>Curse Maven</h1>
<span>
<h1 style="color: red">Beta build - here be dragons!</h1>
<span class="subtitle-padding-start">
Made by Wyn Price
</span>
Expand All @@ -40,24 +41,40 @@ <h1>Curse Maven</h1>
<br>
<p>Curse maven is an alternative to the normal curseforge maven, that takes in the project id and file id,
rather than getting the artifacts from the jar name.</p>
<p>Like to live fast? <a href="https://beta.cursemaven.com">Try out the beta build of cursemaven</a></p>
<p>Like to be on the safe side? <a href="https://cursemaven.com">Try out the stable build of cursemaven</a></p>
<h2>Adding the Maven</h2>
<p>Add <span class="inline-host text-highlight">https://cursemaven.com/</span> as a maven repository, like normal.</p>
<p>Add <span class="inline-host text-highlight">https://cursemaven.com/</span> as a maven repository, like
normal.</p>

<pre class="text-highlight big"><span class="code-orange">repositories</span> {
maven {
url <span class="code-yellow">"<span class="inline-host">https://cursemaven.com/</span>"</span>
}
}</pre>
<h3>Gradle 5+</h3>
<p>If you're using Gradle 5+, you can optimize the maven repository:</p>
<p>If you're using Gradle 5+, you can optimise the maven repository:</p>
<pre class="text-highlight big"><span class="code-orange">repositories</span> {
maven {
url <span class="code-yellow">"<span class="inline-host">https://cursemaven.com/</span>"</span>
content {
includeGroup <span class="code-yellow">"curse.maven"</span>
}
}
}</pre>
<br>
<h3>Gradle 6.2+</h3>
<p>If you're using Gradle 6.2+, you can take this further:</p>
<pre class="text-highlight big"><span class="code-orange">repositories</span> {
exclusiveContent {
forRepository {
maven {
url <span class="code-yellow">"<span class="inline-host">https://cursemaven.com/</span>"</span>
}
}
filter {
includeGroup <span class="code-yellow">"curse.maven"</span>
}
}
}</pre>
<br>
<h2>Usage</h2>
Expand Down Expand Up @@ -162,7 +179,8 @@ <h2>Examples</h2>
</p>
<h2>Testing</h2>
<p>To test cursemaven, get the project id and file ids, and navigate to
<span class="text-highlight"><span class="inline-host">https://cursemaven.com</span>/test/&lt;ProjectId&gt;/&lt;FileIds&gt;</span>
<span class="text-highlight"><span
class="inline-host">https://cursemaven.com</span>/test/&lt;ProjectId&gt;/&lt;FileIds&gt;</span>
</p>
<h2>Special Thanks</h2>
<ul>
Expand Down Expand Up @@ -190,7 +208,7 @@ <h2>Special Thanks</h2>
</div>
<script defer>
let host = window.location.host;
// Normalize subdomains
// Normalise subdomains
if (host === "www.cursemaven.com") {
host = "cursemaven.com";
} else if (host === "cfa2.cursemaven.com") {
Expand Down
9 changes: 7 additions & 2 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import express from "express";
import express, { Request } from "express";
import direct from "./direct";
import normaljar from "./normaljar";
import pom from "./pom";
Expand All @@ -9,14 +9,19 @@ const app = express();

const urlBase = "/curse/maven/:descriptor/:fileIds/:filename"

app.use(express.static("public", {
extensions: ["html"],
}));

app.get(`${urlBase}.jar`, verifyParams, normaljar)
//Although we don't use `verifyParams` for the pom, it's still good to
//Although we don't use `verifyParams` for the pom, it's still good to
//prevent malformed maven coordinates producing a valid pom
app.get(`${urlBase}.pom`, verifyParams, pom)
app.get(`${urlBase}.md5`, (_, res) => res.sendStatus(404))
app.get(`${urlBase}.sha1`, (_, res) => res.sendStatus(404))
app.get(`${urlBase}.*`, verifyParams, direct)

app.get("/test/:id/:fileIds/:classifier?", testing)
app.get('/source', (_, res) => res.redirect("https://github.com/Wyn-Price/CurseMaven/"));

export default app
11 changes: 9 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@ const server = http.createServer(app);

const port = process.env.PORT || 3000;

server.listen(port, () => {
console.log(`Server running on port ${port}`);
if (process.env.NODE_ENV !== 'test') {
server.listen(port, () => {
console.log(`Server running on port ${port}`);
});
}

process.on('SIGINT', () => {
console.info("Interrupted");
process.exit(0);
});

export default server
4 changes: 2 additions & 2 deletions src/normaljar.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { RequestHandler } from "express";
import { fetchDownloadUrl, getFetchedData, getRedirectUrl } from "./util";
import { fetchDownloadUrl, getFetchedData } from "./util";

const normaljar: RequestHandler = async (req, res) => {
const { id, file } = res.locals

const response = await fetchDownloadUrl(id, file)
if (response.ok) {
return res.redirect(getRedirectUrl(await getFetchedData(response)))
return res.redirect(await getFetchedData(response))
} else {
return res.status(response.status).send(response.statusText)
}
Expand Down
2 changes: 1 addition & 1 deletion src/testing.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import escapeHTML from 'escape-html';
import { RequestHandler } from 'express';
import createClassifierMap from './classifiermap';
import { authFetch, getDownloadUrl, getFetchedData, getRedirectUrl } from './util';
import { authFetch, getDownloadUrl, getFetchedData } from './util';


const testing: RequestHandler = async (req, res) => {
Expand Down
41 changes: 1 addition & 40 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,9 @@
import fetch, { Response } from "node-fetch"

export const authFetch = (url: string) => fetch(url, { headers: { 'x-api-key': process.env.API_KEY ?? "" } })

export const getDownloadUrl = (id: string, file: string | number) => `https://api.curseforge.com/v1/mods/${id}/files/${file}/download-url`
export const fetchDownloadUrl = (id: string, file: string | number) => authFetch(getDownloadUrl(id, file))

export const getFetchedData = async (response: Response) => {
const json = await response.json()
const json = await response.json() as { data: string }
return json.data
}

//Gets the redirect url for the given url. An example of this download url would be: https://edge.forgecdn.net/files/2724/420/jei_1.12.2-4.15.0.281.jar
//
//Due to an issue with apache's http client which gradle uses, some characters will be decoded, but not encoded.
//This in junction with curseforge's media server needing the correct encoding means redirecting to the media server won't always work.
//A fix for this is to redirect to `/download-binary/...`, instead of `https://media.forgecdn.net/files/...`. Doing this means I have to encode the
//Jar name twice (hopfully) meaning that the apache issues don't occur, as the special characters won't show up in the decoding.
//The `/download-binary/` is just a reverse proxy to the forge media server, however I'm not too sure that it doesn't use up bandwidth when downloading the jar, so I only use this when I need to.
//
//If the file name (jei_1.12.2-4.15.0.281.jar) contains any problematic characters then return a redirect to `/download-binary/`, otherwise it redirects to curseforge's media server.
//
//
//2022-01-02T06:14:58.021+0000 [DEBUG] [org.gradle.internal.resource.transport.http.HttpClientConfigurer$DowngradeProtectingRedirectStrategy] Redirect requested to location 'https://media.forgecdn.net/files/3335/93/BetterFoliage-2.6.5%2B368b50a-Fabric-1.16.5.jar'
//2022-01-02T06:14:58.022+0000 [DEBUG] [org.apache.http.impl.execchain.RedirectExec] Resetting target auth state
//2022-01-02T06:14:58.022+0000 [DEBUG] [org.apache.http.impl.execchain.RedirectExec] Redirecting to 'https://media.forgecdn.net/files/3335/93/BetterFoliage-2.6.5+368b50a-Fabric-1.16.5.jar' via {s}->https://media.forgecdn.net:443
//
//Decoding is done at URLEncodedUtils#urlDecode
//Call stack is below (line numbers are the line of the next call):
//DefaultRedirectStrategy#getLocationURI L 97
//URIUtils#normalizeSyntax L 196
//new URIBuilder --> URIBuilder#digestURI --> URIBuilder#parsePath L 157
//URLEncodedUtils#parsePathSegments L 236
//URLEncodedUtils#urlDecode
export const getRedirectUrl = (url: string) => {

//The file name will be the last one. The reason I pop to get the raw file name instead of just doing `split[6]`, is as `split` is joined back if there are no problematic chars.
var split = url.split('\/')
var rawFileName = split.pop()
var fileName = encodeURIComponent(rawFileName)

// If there are problematic chars then redirect internally.
if (rawFileName.includes("+")) {
return `/download-binary/${split[4]}/${split[5]}/${encodeURIComponent(fileName)}` //We have to encode it twice for this to work with gradle
} else {
return split.join('/') + '/' + fileName
}
}
14 changes: 0 additions & 14 deletions tests/download.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,6 @@ describe('Normal Download URL', () => {
expect(res.headers['location']).toStrictEqual("https://edge.forgecdn.net/files/2724/420/jei_1.12.2-4.15.0.281.jar")
})

test('Normal Jar w/ problamatic chars should be correct', async () => {
//curse.maven:better-foliage-228529:3335093
const res = await requestWithSupertest.get(downloadUrl('better-foliage', '228529', '3335093', '.jar'))
expect(res.status).toStrictEqual(302)
expect(res.headers['location']).toStrictEqual("/download-binary/3335/93/BetterFoliage-2.6.5%252B368b50a-Fabric-1.16.5.jar")
})

test("Normal Jar that doesn't exist should return 404", async () => {
//curse.maven:invalid-12345:12345
const res = await requestWithSupertest.get(downloadUrl('invalid', '12345', '54321', '.jar'))
Expand Down Expand Up @@ -67,13 +60,6 @@ describe('Classifier Download URL', () => {
expect(res.headers['location']).toStrictEqual("https://edge.forgecdn.net/files/2452/538/JustEnoughResources-1.12-0.8.2.20-api.jar")
})

test('Classifier Jar w/ problamatic chars should be correct', async () => {
//curse.maven:pehkui-319596:3577084-sources-dev-3577085:sources-dev
const res = await requestWithSupertest.get(downloadUrl('pehkui', '319596', '3577084-sources-dev-3577085', '-sources-dev.jar'))
expect(res.status).toStrictEqual(302)
expect(res.headers['location']).toStrictEqual("/download-binary/3577/85/Pehkui-3.1.0%252B1.18.1-forge-sources-dev.jar")
})

test("Classifier Jar where original jar doesn't exist should return 404", async () => {
//curse.maven:invalid-12345:54321-sources-54322:sources
const res = await requestWithSupertest.get(downloadUrl('invalid', '12345', '54321', '-sources.jar'))
Expand Down
38 changes: 38 additions & 0 deletions tests/static.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { describe, test } from "@jest/globals"
import supertest from "supertest"
import server from "../src/index"

const requestWithSupertest = supertest(server)

describe('Index page', () => {
test("GET / returns 200", async () => {
const res = await requestWithSupertest.get("/")
expect(res.status).toStrictEqual(200)
});

test("POST / returns 404", async () => {
const res = await requestWithSupertest.post("/")
expect(res.status).toStrictEqual(404)
});
});

describe('Sub page', () => {
test("GET /stats returns 200", async () => {
const res = await requestWithSupertest.get("/stats")
expect(res.status).toStrictEqual(200)
});

test("POST /stats returns 404", async () => {
const res = await requestWithSupertest.post("/stats")
expect(res.status).toStrictEqual(404)
});
});

describe('Page doesnt exist', () => {
test("GET /invalidpage returns 200", async () => {
const res = await requestWithSupertest.get("/invalidpage")
expect(res.status).toStrictEqual(404)
});
});

afterAll(() => server.close())
Loading