Compare commits
50
Commits
v1.3.0
..
cd418f513d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd418f513d | ||
|
|
f68aa305fa | ||
|
|
9edaa687a5 | ||
|
|
b571953f10 | ||
|
|
71ef7fa36f | ||
|
|
73c68cc1e8 | ||
|
|
e11a7aad83 | ||
|
|
cb47d6faac | ||
|
|
106900860a | ||
|
|
b3b4495427 | ||
|
|
79d58f994d | ||
|
|
161f540e40 | ||
|
|
79702cb847 | ||
|
|
2fa4064c7c | ||
|
|
4ef392423a | ||
|
|
e6249ae330 | ||
|
|
51d467fbe7 | ||
|
|
52360ab559 | ||
|
|
c75850da2e | ||
|
|
17aeab9592 | ||
|
|
053c6aaa98 | ||
|
|
57c4b6f204 | ||
|
|
792974bd32 | ||
|
|
631ff4e9d2 | ||
|
|
1537881ebc | ||
|
|
8bcfee341c | ||
|
|
b09e0cea76 | ||
|
|
bf4e7170a6 | ||
|
|
cb2beecc0e | ||
|
|
d6bacef1ec | ||
|
|
9f02bebdea | ||
|
|
0128476c37 | ||
|
|
f7e72ddff3 | ||
|
|
2cf3b71e27 | ||
|
|
8020a24655 | ||
|
|
78570c4088 | ||
|
|
4d7ff1a0e7 | ||
|
|
a072edd6d1 | ||
|
|
d12a7da646 | ||
|
|
c4f379ff1c | ||
|
|
0d29f0116a | ||
|
|
cefad72416 | ||
|
|
544d4aab1a | ||
|
|
5a9c185a48 | ||
|
|
94d9b7a879 | ||
|
|
3c409ea048 | ||
|
|
06d9a4be11 | ||
|
|
b920bf6f61 | ||
|
|
a57d7ac44c | ||
|
|
a0178aa0ab |
@@ -6,11 +6,6 @@ on:
|
|||||||
tags: ['v*']
|
tags: ['v*']
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
# Ensure the job token can create releases and upload assets.
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
releases: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -26,12 +21,6 @@ jobs:
|
|||||||
- name: Install dev dependencies
|
- name: Install dev dependencies
|
||||||
run: npm ci || npm install
|
run: npm ci || npm install
|
||||||
|
|
||||||
- name: Install PyMuPDF (PDF text assertions)
|
|
||||||
run: |
|
|
||||||
python3 -m venv /tmp/venv
|
|
||||||
/tmp/venv/bin/pip install --quiet pymupdf
|
|
||||||
echo "/tmp/venv/bin" >> "$GITHUB_PATH"
|
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: npm test
|
run: npm test
|
||||||
|
|
||||||
@@ -47,27 +36,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Publish release on version tag
|
- name: Publish release on version tag
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
# Gitea-native release: softprops/action-gh-release targets GitHub's API
|
# softprops/action-gh-release works on Gitea Actions: it uses the
|
||||||
# and fails on Gitea (405). Instead we call the Gitea API directly with
|
# automatically-provided GITHUB_TOKEN to create a Release for the tag
|
||||||
# the runner-provided GITHUB_TOKEN and GITHUB_API_URL.
|
# and attaches the supplied files.
|
||||||
env:
|
uses: softprops/action-gh-release@v2
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
with:
|
||||||
run: |
|
files: dist/*.zip
|
||||||
set -euo pipefail
|
name: ${{ github.ref_name }}
|
||||||
REPO="${{ github.repository }}"
|
generate_release_notes: true
|
||||||
TAG="${{ github.ref_name }}"
|
|
||||||
API="${GITHUB_API_URL:-https://git.ranio.xyz/api/v1}"
|
|
||||||
# Create the release for the tag
|
|
||||||
RELEASE_JSON=$(curl -sS -X POST \
|
|
||||||
-H "Authorization: token ${GITHUB_TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "{\"tag_name\":\"${TAG}\",\"name\":\"${TAG}\",\"body\":\"ISMIS Helper ${TAG}\"}" \
|
|
||||||
"${API}/repos/${REPO}/releases")
|
|
||||||
RELEASE_ID=$(echo "${RELEASE_JSON}" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
|
|
||||||
# Attach both zips as release assets
|
|
||||||
for f in dist/*.zip; do
|
|
||||||
curl -sS -X POST \
|
|
||||||
-H "Authorization: token ${GITHUB_TOKEN}" \
|
|
||||||
-F "attachment=@${f}" \
|
|
||||||
"${API}/repos/${REPO}/releases/${RELEASE_ID}/assets?name=$(basename "${f}")"
|
|
||||||
done
|
|
||||||
|
|||||||
@@ -10,6 +10,17 @@ extension for the USC ISMIS student portal that:
|
|||||||
|
|
||||||
One codebase builds for **both browsers** (see [Build](#build)).
|
One codebase builds for **both browsers** (see [Build](#build)).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Saved login profiles** (stored in `browser.storage.local`) — only one enabled at
|
||||||
|
a time, with a master on/off toggle.
|
||||||
|
- **One-click Download PDF** button on the Study Load page.
|
||||||
|
- PDF settings in the popup:
|
||||||
|
- Truncate long course descriptions to match the official PDF (default on).
|
||||||
|
- Show/hide `Year Level`, and set a custom Year Level value.
|
||||||
|
- **Bundled Segoe UI fonts** for a 1:1 look with the official document.
|
||||||
|
- Builds and packages automatically via Gitea Actions (zips + versioned releases).
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
> Temporary/development install. For distribution, publish the built zips to
|
> Temporary/development install. For distribution, publish the built zips to
|
||||||
@@ -43,54 +54,6 @@ One codebase builds for **both browsers** (see [Build](#build)).
|
|||||||
> For a persistent install, package and publish via Firefox AMO / Chrome Web Store
|
> For a persistent install, package and publish via Firefox AMO / Chrome Web Store
|
||||||
> with the same built zips.
|
> with the same built zips.
|
||||||
|
|
||||||
### Distribute to real users
|
|
||||||
|
|
||||||
#### Firefox (self-hosted, temporary)
|
|
||||||
|
|
||||||
> **Note:** Firefox blocks unsigned extensions for normal installs. The proper
|
|
||||||
> fix is to sign the add-on via **addons.mozilla.org (AMO)** and publish it there
|
|
||||||
> (or self-host a signed `.xpi`). AMO is currently **under maintenance**, so until
|
|
||||||
> it returns, the only way to install the unsigned build is to lower Firefox's
|
|
||||||
> signing check on each user's machine. This is a per-user browser setting and is
|
|
||||||
> **not** suitable for a broad audience — use it for personal / small-group use.
|
|
||||||
|
|
||||||
1. Build the Firefox variant (or download `ismis-helper-firefox.zip` from a release):
|
|
||||||
```bash
|
|
||||||
node scripts/build.js firefox
|
|
||||||
```
|
|
||||||
2. In Firefox, open `about:config` and accept the warning.
|
|
||||||
3. Search for `xpinstall.signatures.required` and set it to **`false`**.
|
|
||||||
4. **Restart Firefox** for the change to take effect.
|
|
||||||
5. Install the add-on from the release zip: drag `ismis-helper-firefox.zip` into
|
|
||||||
Firefox (or File → Open File… and select it).
|
|
||||||
|
|
||||||
> When AMO is back online, prefer the signed route: publish the same zip to AMO
|
|
||||||
> (or sign it with `web-ext sign`) so users install it normally without touching
|
|
||||||
> `about:config`.
|
|
||||||
|
|
||||||
#### Chrome (self-hosted)
|
|
||||||
|
|
||||||
Chrome does **not** require signing for normal installs. Users can install the
|
|
||||||
built `ismis-helper-chrome.zip` directly:
|
|
||||||
|
|
||||||
1. Download `ismis-helper-chrome.zip` from a release and unzip it.
|
|
||||||
2. Open `chrome://extensions`, enable **Developer mode**, and click **Load unpacked**.
|
|
||||||
3. Select the unzipped folder.
|
|
||||||
|
|
||||||
> For a store experience, publish the same zip to the Chrome Web Store (one-time
|
|
||||||
> $5 developer registration).
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- **Saved login profiles** (stored in `browser.storage.local`) — only one enabled at
|
|
||||||
a time, with a master on/off toggle.
|
|
||||||
- **One-click Download PDF** button on the Study Load page.
|
|
||||||
- PDF settings in the popup:
|
|
||||||
- Truncate long course descriptions to match the official PDF (default on).
|
|
||||||
- Show/hide `Year Level`, and set a custom Year Level value.
|
|
||||||
- **Bundled Segoe UI fonts** for a 1:1 look with the official document.
|
|
||||||
- Builds and packages automatically via Gitea Actions (zips + versioned releases).
|
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
Requires **Node.js** (the `build.js` script itself is dependency-free; `npm install`
|
Requires **Node.js** (the `build.js` script itself is dependency-free; `npm install`
|
||||||
|
|||||||
+4
-24
@@ -10,27 +10,6 @@ function errMsg(err) {
|
|||||||
return String((err && err.message) || err);
|
return String((err && err.message) || err);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build a download URL from PDF bytes. Firefox's background is a real page
|
|
||||||
// (URL.createObjectURL available) and supports blob: URLs in downloads.
|
|
||||||
// Chrome MV3 runs the background as a service worker where URL.createObjectURL
|
|
||||||
// is NOT available, but its downloads API accepts base64 data: URLs.
|
|
||||||
// Firefox rejects data: URLs in browser.downloads.download with
|
|
||||||
// "Access denied for URL data:...", so pick per-browser.
|
|
||||||
function buildDownloadUrl(bytes) {
|
|
||||||
if (typeof URL !== 'undefined' && typeof URL.createObjectURL === 'function') {
|
|
||||||
const blob = new Blob([bytes], { type: 'application/pdf' });
|
|
||||||
return URL.createObjectURL(blob);
|
|
||||||
}
|
|
||||||
// Fallback: Chrome MV3 service worker. PDFs here are small (~150KB), well
|
|
||||||
// within data-URL length limits.
|
|
||||||
let binary = '';
|
|
||||||
const chunk = 0x8000;
|
|
||||||
for (let i = 0; i < bytes.length; i += chunk) {
|
|
||||||
binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunk));
|
|
||||||
}
|
|
||||||
return 'data:application/pdf;base64,' + btoa(binary);
|
|
||||||
}
|
|
||||||
|
|
||||||
browser.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
browser.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||||
if (msg && msg.type === 'generate-pdf') {
|
if (msg && msg.type === 'generate-pdf') {
|
||||||
generatePdf(msg.data)
|
generatePdf(msg.data)
|
||||||
@@ -53,9 +32,8 @@ function formatPeriod(period) {
|
|||||||
|
|
||||||
async function generatePdf(data) {
|
async function generatePdf(data) {
|
||||||
const bytes = await ISMIStools.buildStudyLoadPdf(data);
|
const bytes = await ISMIStools.buildStudyLoadPdf(data);
|
||||||
// Firefox accepts blob: object URLs; Chrome MV3 service worker has no
|
const blob = new Blob([bytes], { type: 'application/pdf' });
|
||||||
// URL.createObjectURL, so it falls back to a base64 data: URL there.
|
const url = URL.createObjectURL(blob);
|
||||||
const url = buildDownloadUrl(bytes);
|
|
||||||
const header = data.header || {};
|
const header = data.header || {};
|
||||||
const year = parseInt(data.year, 10);
|
const year = parseInt(data.year, 10);
|
||||||
const period = formatPeriod(data.period || header.semester || '');
|
const period = formatPeriod(data.period || header.semester || '');
|
||||||
@@ -77,10 +55,12 @@ async function generatePdf(data) {
|
|||||||
const onChanged = (delta) => {
|
const onChanged = (delta) => {
|
||||||
if (delta.id === id && delta.state && (delta.state.current === 'complete' || delta.state.current === 'interrupted')) {
|
if (delta.id === id && delta.state && (delta.state.current === 'complete' || delta.state.current === 'interrupted')) {
|
||||||
browser.downloads.onChanged.removeListener(onChanged);
|
browser.downloads.onChanged.removeListener(onChanged);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
browser.downloads.onChanged.addListener(onChanged);
|
browser.downloads.onChanged.addListener(onChanged);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "ISMIS Helper",
|
"name": "ISMIS Helper",
|
||||||
"version": "1.3.0",
|
"version": "1.2.0",
|
||||||
"description": "Auto-login, auto-accept privacy policy, and Study Load PDF for the USC ISMIS portal.",
|
"description": "Auto-login, auto-accept privacy policy, and Study Load PDF for the USC ISMIS portal.",
|
||||||
"permissions": ["storage", "downloads"],
|
"permissions": ["storage", "downloads"],
|
||||||
"host_permissions": ["https://ismis.usc.edu.ph/*"],
|
"host_permissions": ["https://ismis.usc.edu.ph/*"],
|
||||||
|
|||||||
+1
-18
@@ -38,19 +38,7 @@ function extractPdfText(bytes) {
|
|||||||
return execFileSync('python3', ['-c', script], { encoding: 'utf8' });
|
return execFileSync('python3', ['-c', script], { encoding: 'utf8' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// PyMuPDF (fitz) is only needed for the text-extraction assertions below. It is
|
test('buildStudyLoadPdf returns valid PDF bytes', async () => {
|
||||||
// installed in CI, but if it is missing locally we skip those assertions rather
|
|
||||||
// than hard-failing.
|
|
||||||
function fitzAvailable() {
|
|
||||||
try {
|
|
||||||
execFileSync('python3', ['-c', 'import fitz'], { stdio: 'ignore' });
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test('buildStudyLoadPdf returns valid PDF bytes', async (t) => {
|
|
||||||
const bytes = await buildStudyLoadPdf(sampleData);
|
const bytes = await buildStudyLoadPdf(sampleData);
|
||||||
assert.ok(bytes instanceof Uint8Array);
|
assert.ok(bytes instanceof Uint8Array);
|
||||||
const head = Buffer.from(bytes.slice(0, 5)).toString('latin1');
|
const head = Buffer.from(bytes.slice(0, 5)).toString('latin1');
|
||||||
@@ -58,11 +46,6 @@ test('buildStudyLoadPdf returns valid PDF bytes', async (t) => {
|
|||||||
const doc = await PDFLib.PDFDocument.load(bytes);
|
const doc = await PDFLib.PDFDocument.load(bytes);
|
||||||
assert.strictEqual(doc.getPageCount(), 1);
|
assert.strictEqual(doc.getPageCount(), 1);
|
||||||
|
|
||||||
if (!fitzAvailable()) {
|
|
||||||
t.skip('PyMuPDF (fitz) not available; skipping PDF text assertions');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = extractPdfText(bytes);
|
const text = extractPdfText(bytes);
|
||||||
assert.ok(text.includes('CIS 2101'), 'PDF text should contain course code CIS 2101');
|
assert.ok(text.includes('CIS 2101'), 'PDF text should contain course code CIS 2101');
|
||||||
assert.ok(text.includes("STUDENT'S STUDY LOAD"), 'PDF text should contain the title');
|
assert.ok(text.includes("STUDENT'S STUDY LOAD"), 'PDF text should contain the title');
|
||||||
|
|||||||
Reference in New Issue
Block a user