Skip to content

[Follow-up] Add experimental WebUI for short messages with session-based login#942

Closed
boomzero wants to merge 27 commits intodevfrom
codex/github-mention-feature-request]-webui-for-short-message-vi
Closed

[Follow-up] Add experimental WebUI for short messages with session-based login#942
boomzero wants to merge 27 commits intodevfrom
codex/github-mention-feature-request]-webui-for-short-message-vi

Conversation

@boomzero
Copy link
Member

@boomzero boomzero commented Mar 15, 2026

Motivation

  • Provide a lightweight WebUI so users can view and send short messages on xmoj.tech without installing the userscript, and make login easier for users who don't know how to extract PHPSESSID.
  • Keep a pragmatic balance between usability, security and maintainability by offering username/password login (to obtain PHPSESSID) while keeping credentials transient and session data tab-scoped.

Description

  • Added webui.html, an experimental single-file client that supports username/password login (uses md5(password) as in the userscript), manual PHPSESSID fallback, conversation list (GetMailList), message viewing (GetMail + ReadUserMailMention), sending (SendMail), and paste-to-upload image support (UploadImage).
  • The page stores session info in sessionStorage (tab-scoped) and exposes a clear button to remove the session, and it formats API payloads to match the userscript RequestAPI shape for compatibility.
  • Linked the WebUI from the homepage by adding a navbar entry in index.html and documented the experimental feature, usage and security/maintenance notes in README.md.

Testing

  • Static checks passed using git diff --check with no whitespace or obvious diff errors reported.
  • The new page was served locally and validated by starting a local HTTP server and loading webui.html, and a full-page screenshot was captured via Playwright to confirm the UI renders correctly (artifact saved).
  • Basic runtime interactions were exercised locally (page load, UI controls wired to API request functions); network API calls are wired to the existing api.xmoj-bbs.me endpoints and will require a valid PHPSESSID for live end-to-end verification.

Codex Task

Summary by Sourcery

Introduce an experimental browser-based WebUI for viewing and sending short messages without installing the userscript, and link it from the project homepage.

New Features:

  • Add a standalone experimental webui.html page that allows users to log in, list conversations, read messages, send messages, and upload images via the existing XMOJ APIs using session-based authentication.

Enhancements:

  • Add a navigation entry in the main index page to access the experimental short-message WebUI from the homepage.

Documentation:

  • Document the experimental short-message WebUI, its login methods, and security/maintenance considerations in the README.

Summary by cubic

Adds an experimental WebUI for short messages with session-based login, now with an optional local proxy so password login and API calls work reliably without CORS issues. Users can read/send messages and upload images on xmoj.tech without installing the userscript.

  • New Features
    • webui.html: username/password login (md5(password)) with manual PHPSESSID fallback; conversations (GetMailList), messages (GetMail + ReadUserMailMention), sending (SendMail), paste-to-upload images (UploadImage); auto-detects proxy vs direct mode and uses /__webui/login + /__webui/api/* when proxied; tab-scoped sessionStorage with one-click clear; API payloads match RequestAPI.
    • tools/webui-proxy.mjs: tiny local server (node tools/webui-proxy.mjs, defaults to http://127.0.0.1:8787) that serves static files and provides /__webui/health, /__webui/login (sets/returns PHPSESSID), and /__webui/api/:action to forward requests to https://api.xmoj-bbs.me/.
    • index.html: adds a navbar link to the WebUI.
    • README.md: documents proxy mode usage, direct mode limitations, and security/maintenance notes.

Written for commit b7c77e6. Summary will update on new commits.

boomzero and others added 26 commits February 8, 2025 09:15
Signed-off-by: Zhu Chenrui <boomzero_zcr@outlook.com>
Signed-off-by: Zhu Chenrui <boomzero_zcr@outlook.com>
(cherry picked from commit 07d7590)

Update feature.yml

Signed-off-by: Zhu Chenrui <boomzero_zcr@outlook.com>
(cherry picked from commit 1a99430)

Update docs.yml

Signed-off-by: Zhu Chenrui <boomzero_zcr@outlook.com>
(cherry picked from commit 6017bcf)
Signed-off-by: Shan Wenxiao <seanoj_noreply@yeah.net>
…ns[bot]

This prevents infinite loops where the bot commits version updates,
which triggers the workflow again, causing another commit.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The last-commit-author guard now only exits for non-edited events,
so PR title/body changes still update Update.json metadata even
when the branch tip is a github-actions[bot] commit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Exclude all bot actors (not just github-actions[bot]) from triggering
the UpdateVersion workflow, preventing loops from AI code review bots.
Allow edited events through the script-level guard so PR title/body
changes still update Update.json metadata.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sourcery-ai
Copy link

sourcery-ai bot commented Mar 15, 2026

Reviewer's Guide

Adds an experimental standalone web UI (webui.html) for XMOJ short messages with session-based authentication, wires it to existing API endpoints using the same RequestAPI payload shape as the userscript, and documents/links it from the homepage and README.

Sequence diagram for WebUI login and session-based initialization

sequenceDiagram
  actor User
  participant BrowserWebUI
  participant XMOJLoginServer
  participant SessionStorage
  participant ShortMessageAPI

  User->>BrowserWebUI: Open webuihtml
  BrowserWebUI->>SessionStorage: loadSession
  SessionStorage-->>BrowserWebUI: Stored username and SessionID (optional)
  BrowserWebUI->>BrowserWebUI: Pre-fill username and SessionID

  User->>BrowserWebUI: Click loginBtn (loginAndGetSession)
  BrowserWebUI->>XMOJLoginServer: POST loginphp (user_id, md5password)
  XMOJLoginServer-->>BrowserWebUI: HTML response + SetCookie PHPSESSID
  BrowserWebUI->>BrowserWebUI: Extract PHPSESSID from response headers
  alt SessionID extracted
    BrowserWebUI->>BrowserWebUI: Update stateusername and statesessionId
    BrowserWebUI->>SessionStorage: persistSession(username, SessionID)
    BrowserWebUI->>ShortMessageAPI: POST GetMailList with RequestAPI payload
    ShortMessageAPI-->>BrowserWebUI: MailList response
    BrowserWebUI->>BrowserWebUI: Render conversation list
  else SessionID not available
    BrowserWebUI->>User: Show error hint (fill PHPSESSID manually)
  end

  User->>BrowserWebUI: Optionally click saveSessionBtn
  BrowserWebUI->>BrowserWebUI: Read username and PHPSESSID inputs
  BrowserWebUI->>BrowserWebUI: Update stateusername and statesessionId
  BrowserWebUI->>SessionStorage: persistSession(username, SessionID)
  BrowserWebUI->>ShortMessageAPI: POST GetMailList
  ShortMessageAPI-->>BrowserWebUI: MailList response
  BrowserWebUI->>BrowserWebUI: Render conversation list
Loading

Sequence diagram for sending a message with optional pasted image

sequenceDiagram
  actor User
  participant BrowserWebUI
  participant SessionStorage
  participant ShortMessageAPI
  participant AssetServer

  User->>BrowserWebUI: Select conversation in list
  BrowserWebUI->>ShortMessageAPI: POST ReadUserMailMention
  ShortMessageAPI-->>BrowserWebUI: Success
  BrowserWebUI->>ShortMessageAPI: POST GetMail(UserID)
  ShortMessageAPI-->>BrowserWebUI: Mail list
  BrowserWebUI->>BrowserWebUI: Render message bubbles

  User->>BrowserWebUI: Paste image into messageInput
  BrowserWebUI->>BrowserWebUI: Detect image in clipboard items
  BrowserWebUI->>ShortMessageAPI: POST UploadImage(Image DataURL)
  ShortMessageAPI-->>BrowserWebUI: ImageID
  BrowserWebUI->>AssetServer: Build image URL with ImageID
  AssetServer-->>BrowserWebUI: Image URL
  BrowserWebUI->>BrowserWebUI: Insert Markdown ![](url) into messageInput

  User->>BrowserWebUI: Click sendBtn
  BrowserWebUI->>SessionStorage: Read stored username and SessionID
  BrowserWebUI->>ShortMessageAPI: POST SendMail(ToUser, Content)
  ShortMessageAPI-->>BrowserWebUI: Success
  BrowserWebUI->>BrowserWebUI: Clear messageInput and show success
  BrowserWebUI->>ShortMessageAPI: POST GetMail(UserID)
  ShortMessageAPI-->>BrowserWebUI: Updated mail list
  BrowserWebUI->>BrowserWebUI: Re-render messages and conversation list
Loading

Class diagram for WebUI state, session, and API helpers

classDiagram

  class State {
    string username
    string sessionId
    string selectedUser
  }

  class Elements {
    HTMLElement username
    HTMLElement password
    HTMLElement sessionId
    HTMLElement loginBtn
    HTMLElement saveSessionBtn
    HTMLElement clearBtn
    HTMLElement loginStatus
    HTMLElement refreshListBtn
    HTMLElement conversationList
    HTMLElement refreshMessageBtn
    HTMLElement currentTarget
    HTMLElement messageList
    HTMLElement messageInput
    HTMLElement sendBtn
    HTMLElement sendStatus
  }

  class SessionManager {
    string STORAGE_KEY
    persistSession()
    loadSession()
    clearAll()
  }

  class ApiClient {
    string API_BASE
    apiRequest(action, data)
  }

  class LoginController {
    string LOGIN_URL
    loginAndGetSession()
  }

  class MessageController {
    refreshConversationList()
    refreshMessages()
    uploadImageFromPaste(file)
  }

  class UIHelpers {
    setStatus(msg, isError)
    setSendStatus(msg, isError)
    formatTime(ts)
    escapeHtml(text)
    renderContent(content)
  }

  class EventBindings {
    bindLoginButton()
    bindSaveSessionButton()
    bindClearButton()
    bindRefreshListButton()
    bindRefreshMessageButton()
    bindSendButton()
    bindPasteHandler()
  }

  State <.. SessionManager : manages
  State <.. ApiClient : reads
  State <.. LoginController : updates
  State <.. MessageController : reads and updates

  Elements <.. EventBindings : uses
  Elements <.. UIHelpers : updates DOM

  SessionManager --> State
  ApiClient --> State
  LoginController --> State
  MessageController --> State

  MessageController --> ApiClient
  LoginController --> ApiClient

  UIHelpers <.. MessageController
  UIHelpers <.. LoginController
Loading

File-Level Changes

Change Details Files
Introduce a single-page experimental WebUI for viewing and sending short messages, including login/session handling and paste-to-upload image support, implemented as a standalone HTML+JS client hitting existing APIs.
  • Create webui.html using Bootstrap 5 for layout and styling plus blueimp-md5 for password hashing to mirror the userscript behavior.
  • Implement username/password login flow against xmoj.tech login.php, using md5(password), attempting to extract PHPSESSID from the response and falling back to manual entry.
  • Maintain session state (username + PHPSESSID) in tab-scoped sessionStorage with helpers to load, persist, and clear it, and expose clear/login/save-session controls in the UI.
  • Implement a generic apiRequest wrapper that builds Authentication/Data/Version/DebugMode payloads to match the existing RequestAPI shape and calls api.xmoj-bbs.me endpoints with appropriate headers.
  • Add conversation-list fetching via GetMailList, including unread badge display, last-message preview, and active-conversation highlighting.
  • Add message fetching for a selected conversation via ReadUserMailMention + GetMail, rendering messages as in/out bubbles with timestamps and basic Markdown-image expansion while escaping HTML.
  • Implement SendMail-based message sending, including optimistic UI status updates and list refresh after send.
  • Implement UploadImage-based paste-to-upload for images from the clipboard, converting them to data URLs, sending them to the API, and inserting returned URLs as Markdown image syntax into the message input.
webui.html
Surface the experimental WebUI entry point in user-facing documentation and navigation.
  • Document the experimental short-message WebUI, its login options, and security/maintenance considerations in the README.
  • Add a navbar link in the main index.html to navigate to webui.html as an experimental feature entry.
README.md
index.html

Possibly linked issues

  • #: The PR delivers the requested short message WebUI, enabling viewing/sending messages without the userscript, linked from homepage.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@hendragon-bot hendragon-bot bot added the website This issue or pull request is related to website related files label Mar 15, 2026
@boomzero boomzero changed the base branch from master to dev March 15, 2026 07:46
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>XMOJ 短消息 WebUI(实验)</title>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/blueimp-md5/2.19.0/js/md5.min.js"></script>

Check failure

Code scanning / CodeQL

Untrusted domain used in script or other content High

Content loaded from untrusted domain with no integrity check.
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • The loginAndGetSession flow relies on reading Set-Cookie from the cross-origin login.php response, but browsers generally do not expose Set-Cookie headers to JS for security/CORS reasons, so this is likely to fail in practice; consider switching to a same-origin helper endpoint, a server-side proxy, or explicitly documenting that only manual PHPSESSID input is reliable.
  • There are a few hard-coded strings and config values repeated across the script (e.g., webui-0.1.0 in both the payload and headers, API_BASE/endpoints), which could be centralized into a small config object to make future updates less error-prone.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `loginAndGetSession` flow relies on reading `Set-Cookie` from the cross-origin `login.php` response, but browsers generally do not expose `Set-Cookie` headers to JS for security/CORS reasons, so this is likely to fail in practice; consider switching to a same-origin helper endpoint, a server-side proxy, or explicitly documenting that only manual `PHPSESSID` input is reliable.
- There are a few hard-coded strings and config values repeated across the script (e.g., `webui-0.1.0` in both the payload and headers, `API_BASE`/endpoints), which could be centralized into a small config object to make future updates less error-prone.

## Individual Comments

### Comment 1
<location path="webui.html" line_range="307-314" />
<code_context>
+  body.set('user_id', username);
+  body.set('password', md5(password));
+
+  const response = await fetch(LOGIN_URL, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+    body: body.toString(),
+    credentials: 'include'
+  });
+  const text = await response.text();
+  const setCookie = response.headers.get('set-cookie') || '';
+  let sessionId = '';
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Reading `Set-Cookie` from `fetch` response headers in browser won’t work due to security restrictions.

In browsers, `response.headers.get('set-cookie')` is never available to JS (even with `credentials: 'include'`) because `Set-Cookie` is a forbidden CORS response header. As a result, `sessionId` will always be empty and this auto-extraction path won’t work in browser contexts. Consider either relying only on manual PHPSESSID input, routing this through your own backend that returns the session ID in a custom header/body, or clearly limiting/guarding this behavior to non-browser environments.
</issue_to_address>

### Comment 2
<location path="webui.html" line_range="275-293" />
<code_context>
+  });
+}
+
+async function uploadImageFromPaste(file) {
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader();
+    reader.onload = async () => {
+      try {
+        const result = await apiRequest('UploadImage', { Image: reader.result });
+        if (!result.Success) {
+          reject(new Error(result.Message || '上传失败'));
+          return;
+        }
+        resolve(`https://assets.xmoj-bbs.me/GetImage?ImageID=${result.Data.ImageID}`);
+      } catch (error) {
+        reject(error);
+      }
+    };
+    reader.onerror = () => reject(new Error('读取图片失败'));
+    reader.readAsDataURL(file);
+  });
+}
</code_context>
<issue_to_address>
**suggestion (performance):** No file size or type validation when uploading pasted images could cause performance and UX issues.

Any pasted `image/*` blob is read fully as a data URL and uploaded without limits. Consider enforcing a reasonable max size (e.g., a few MB) and optionally restricting to common formats (PNG/JPEG) before calling `readAsDataURL` and `apiRequest` to avoid excessive memory and request payloads.

```suggestion
async function uploadImageFromPaste(file) {
  const MAX_PASTE_IMAGE_SIZE = 5 * 1024 * 1024; // 5MB
  const ALLOWED_PASTE_IMAGE_TYPES = ['image/png', 'image/jpeg'];

  return new Promise((resolve, reject) => {
    if (!file || !file.type || !file.type.startsWith('image/')) {
      reject(new Error('不支持的图片类型'));
      return;
    }

    if (!ALLOWED_PASTE_IMAGE_TYPES.includes(file.type)) {
      reject(new Error('仅支持 PNG 和 JPEG 图片'));
      return;
    }

    if (file.size > MAX_PASTE_IMAGE_SIZE) {
      reject(new Error('图片过大,请上传不超过 5MB 的图片'));
      return;
    }

    const reader = new FileReader();
    reader.onload = async () => {
      try {
        const result = await apiRequest('UploadImage', { Image: reader.result });
        if (!result.Success) {
          reject(new Error(result.Message || '上传失败'));
          return;
        }
        resolve(`https://assets.xmoj-bbs.me/GetImage?ImageID=${result.Data.ImageID}`);
      } catch (error) {
        reject(error);
      }
    };
    reader.onerror = () => reject(new Error('读取图片失败'));
    reader.readAsDataURL(file);
  });
}
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

webui.html Outdated
Comment on lines +275 to +293
async function uploadImageFromPaste(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = async () => {
try {
const result = await apiRequest('UploadImage', { Image: reader.result });
if (!result.Success) {
reject(new Error(result.Message || '上传失败'));
return;
}
resolve(`https://assets.xmoj-bbs.me/GetImage?ImageID=${result.Data.ImageID}`);
} catch (error) {
reject(error);
}
};
reader.onerror = () => reject(new Error('读取图片失败'));
reader.readAsDataURL(file);
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (performance): No file size or type validation when uploading pasted images could cause performance and UX issues.

Any pasted image/* blob is read fully as a data URL and uploaded without limits. Consider enforcing a reasonable max size (e.g., a few MB) and optionally restricting to common formats (PNG/JPEG) before calling readAsDataURL and apiRequest to avoid excessive memory and request payloads.

Suggested change
async function uploadImageFromPaste(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = async () => {
try {
const result = await apiRequest('UploadImage', { Image: reader.result });
if (!result.Success) {
reject(new Error(result.Message || '上传失败'));
return;
}
resolve(`https://assets.xmoj-bbs.me/GetImage?ImageID=${result.Data.ImageID}`);
} catch (error) {
reject(error);
}
};
reader.onerror = () => reject(new Error('读取图片失败'));
reader.readAsDataURL(file);
});
}
async function uploadImageFromPaste(file) {
const MAX_PASTE_IMAGE_SIZE = 5 * 1024 * 1024; // 5MB
const ALLOWED_PASTE_IMAGE_TYPES = ['image/png', 'image/jpeg'];
return new Promise((resolve, reject) => {
if (!file || !file.type || !file.type.startsWith('image/')) {
reject(new Error('不支持的图片类型'));
return;
}
if (!ALLOWED_PASTE_IMAGE_TYPES.includes(file.type)) {
reject(new Error('仅支持 PNG 和 JPEG 图片'));
return;
}
if (file.size > MAX_PASTE_IMAGE_SIZE) {
reject(new Error('图片过大,请上传不超过 5MB 的图片'));
return;
}
const reader = new FileReader();
reader.onload = async () => {
try {
const result = await apiRequest('UploadImage', { Image: reader.result });
if (!result.Success) {
reject(new Error(result.Message || '上传失败'));
return;
}
resolve(`https://assets.xmoj-bbs.me/GetImage?ImageID=${result.Data.ImageID}`);
} catch (error) {
reject(error);
}
};
reader.onerror = () => reject(new Error('读取图片失败'));
reader.readAsDataURL(file);
});
}

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 636b037711

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

webui.html Outdated
Comment on lines +253 to +257
const data = await apiRequest('GetMail', { UserID: String(state.selectedUser) });
if (!data.Success) {
throw new Error(data.Message || '获取消息失败');
}
const list = data.Data?.Mails || [];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Align GetMail request/response keys with API contract

This call uses UserID and then reads data.Data?.Mails, but the existing short-message client in XMOJ.user.js calls GetMail with OtherUser and consumes ResponseData.Data.Mail (see lines 4686-4692 there). With the mismatched field names here, message fetches for a selected conversation can fail or render as empty even with a valid session.

Useful? React with 👍 / 👎.

webui.html Outdated
Comment on lines +314 to +317
const setCookie = response.headers.get('set-cookie') || '';
let sessionId = '';

const match = setCookie.match(/PHPSESSID=([^;]+)/i);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Stop extracting session from forbidden Set-Cookie header

The login flow reads response.headers.get('set-cookie') to obtain PHPSESSID, but browsers do not expose Set-Cookie to JavaScript fetch responses, so sessionId stays empty and this path always falls into the error branch. As written, the “登录并获取会话” feature cannot actually complete and users are forced to manually paste the session ID.

Useful? React with 👍 / 👎.

@PythonSmall-Q
Copy link
Member

你看,codex自己都觉得代码有问题(bushi

"Notes": "No release notes were provided for this release."
},
"1.10.0": {
"1.999990.0": {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wtf is this

@boomzero
Copy link
Member Author

他会自己修

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 issues found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="README.md">

<violation number="1" location="README.md:78">
P2: This line overstates the login flow: the WebUI cannot auto-read `PHPSESSID` from a browser `fetch()` response, so username/password login won't obtain the session as documented.</violation>
</file>

<file name="webui.html">

<violation number="1" location="webui.html:8">
P1: This login page executes a third-party script without integrity pinning, which can expose usernames, passwords, and `PHPSESSID` if the CDN asset is tampered with.</violation>

<violation number="2" location="webui.html:253">
P1: The `GetMail` request uses `{ UserID: ... }` but the existing API (as used in `XMOJ.user.js`) expects the field to be named `OtherUser`. Similarly, the response is read as `data.Data?.Mails` (plural) but the API returns `Data.Mail` (singular). Both mismatches will cause message fetches to silently fail or return empty.</violation>

<violation number="3" location="webui.html:257">
P1: The response field `data.Data?.Mails` (plural) does not match the API's actual response shape `Data.Mail` (singular). This will always evaluate to `undefined`, falling back to an empty array, so no messages will ever be rendered.</violation>

<violation number="4" location="webui.html:314">
P1: `response.headers.get('set-cookie')` cannot retrieve `PHPSESSID` in browser code, so the automatic login flow will always fail to capture the session.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>XMOJ 短消息 WebUI(实验)</title>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/blueimp-md5/2.19.0/js/md5.min.js"></script>
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This login page executes a third-party script without integrity pinning, which can expose usernames, passwords, and PHPSESSID if the CDN asset is tampered with.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At webui.html, line 8:

<comment>This login page executes a third-party script without integrity pinning, which can expose usernames, passwords, and `PHPSESSID` if the CDN asset is tampered with.</comment>

<file context>
@@ -0,0 +1,425 @@
+  <meta name="viewport" content="width=device-width,initial-scale=1" />
+  <title>XMOJ 短消息 WebUI(实验)</title>
+  <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet">
+  <script src="https://cdn.bootcdn.net/ajax/libs/blueimp-md5/2.19.0/js/md5.min.js"></script>
+  <style>
+    body { background: #f7f8fa; }
</file context>
Fix with Cubic

webui.html Outdated
if (!data.Success) {
throw new Error(data.Message || '获取消息失败');
}
const list = data.Data?.Mails || [];
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The response field data.Data?.Mails (plural) does not match the API's actual response shape Data.Mail (singular). This will always evaluate to undefined, falling back to an empty array, so no messages will ever be rendered.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At webui.html, line 257:

<comment>The response field `data.Data?.Mails` (plural) does not match the API's actual response shape `Data.Mail` (singular). This will always evaluate to `undefined`, falling back to an empty array, so no messages will ever be rendered.</comment>

<file context>
@@ -0,0 +1,425 @@
+  if (!data.Success) {
+    throw new Error(data.Message || '获取消息失败');
+  }
+  const list = data.Data?.Mails || [];
+  el.messageList.innerHTML = '';
+  if (list.length === 0) {
</file context>
Fix with Cubic

webui.html Outdated

await apiRequest('ReadUserMailMention', { UserID: String(state.selectedUser) });

const data = await apiRequest('GetMail', { UserID: String(state.selectedUser) });
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The GetMail request uses { UserID: ... } but the existing API (as used in XMOJ.user.js) expects the field to be named OtherUser. Similarly, the response is read as data.Data?.Mails (plural) but the API returns Data.Mail (singular). Both mismatches will cause message fetches to silently fail or return empty.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At webui.html, line 253:

<comment>The `GetMail` request uses `{ UserID: ... }` but the existing API (as used in `XMOJ.user.js`) expects the field to be named `OtherUser`. Similarly, the response is read as `data.Data?.Mails` (plural) but the API returns `Data.Mail` (singular). Both mismatches will cause message fetches to silently fail or return empty.</comment>

<file context>
@@ -0,0 +1,425 @@
+
+  await apiRequest('ReadUserMailMention', { UserID: String(state.selectedUser) });
+
+  const data = await apiRequest('GetMail', { UserID: String(state.selectedUser) });
+  if (!data.Success) {
+    throw new Error(data.Message || '获取消息失败');
</file context>
Fix with Cubic

@boomzero
Copy link
Member Author

boomzero commented Mar 15, 2026

@codex address review

}

function md5(input) {
return createHash('md5').update(input).digest('hex');

Check failure

Code scanning / CodeQL

Use of password hash with insufficient computational effort High

Password from
an access to password
is hashed insecurely.
Password from
an access to password
is hashed insecurely.

Copilot Autofix

AI 11 days ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

Comment on lines +93 to +97
const response = await fetch(`https://api.xmoj-bbs.me/${action}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.

Copilot Autofix

AI 11 days ago

In general, to fix this kind of issue you should never let raw user input determine the full path of an internal/backend request. Instead, map user inputs to a controlled set of allowed actions or validate them against a strict pattern, and then build the backend path from that sanitized value. This ensures that a client cannot cause the server to query unexpected endpoints, even if the hostname is fixed.

In this specific file, the problem is that action is directly derived from the path /__webui/api/<action> and then used verbatim in fetch(`https://api.xmoj-bbs.me/${action}`, ...). The safest fix without changing visible behavior too much is:

  • Introduce a small validation/normalization function (inside this file) that:
    • Rejects any action containing path traversal (..), backslashes, or leading /.
    • Optionally constrains it to a simple character set (e.g., letters, digits, /_-).
  • Call this validator both at the routing point (to fail fast) and inside handleApiProxy (defense in depth).
  • If validation fails, return a 400 error with a simple JSON error message instead of performing the fetch.
  • Keep the scheme and hostname unchanged and still allow multi-segment paths like foo/bar if needed by the existing API.

Concretely:

  • Add a helper function sanitizeAction(action) above handleApiProxy.
  • In createServer where action is extracted (around lines 135–137), pass action through sanitizeAction; if it returns null, respond with 400.
  • In handleApiProxy, re-sanitize action and reject if invalid, then use the sanitized value to construct the URL.

No new imports are needed; we can implement this logic with plain JavaScript.

Suggested changeset 1
tools/webui-proxy.mjs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/tools/webui-proxy.mjs b/tools/webui-proxy.mjs
--- a/tools/webui-proxy.mjs
+++ b/tools/webui-proxy.mjs
@@ -88,9 +88,22 @@
   });
 }
 
+function sanitizeAction(action) {
+  if (typeof action !== 'string') return null;
+  // Disallow absolute paths or path traversal
+  if (action.startsWith('/') || action.includes('..') || action.includes('\\')) return null;
+  // Allow only safe characters: letters, digits, underscore, hyphen, slash
+  if (!/^[A-Za-z0-9_\/-]+$/.test(action)) return null;
+  return action;
+}
+
 async function handleApiProxy(req, res, action) {
+  const safeAction = sanitizeAction(action);
+  if (!safeAction) {
+    return json(res, 400, { success: false, message: '非法 action' });
+  }
   const body = await readBody(req);
-  const response = await fetch(`https://api.xmoj-bbs.me/${action}`, {
+  const response = await fetch(`https://api.xmoj-bbs.me/${safeAction}`, {
     method: 'POST',
     headers: { 'Content-Type': 'application/json' },
     body: JSON.stringify(body)
@@ -134,7 +145,9 @@
     if (pathname.startsWith('/__webui/api/') && req.method === 'POST') {
       const action = pathname.substring('/__webui/api/'.length);
       if (!action) return json(res, 400, { success: false, message: '缺少 action' });
-      return await handleApiProxy(req, res, action);
+      const safeAction = sanitizeAction(action);
+      if (!safeAction) return json(res, 400, { success: false, message: '非法 action' });
+      return await handleApiProxy(req, res, safeAction);
     }
 
     return await serveStatic(req, res, pathname);
EOF
@@ -88,9 +88,22 @@
});
}

function sanitizeAction(action) {
if (typeof action !== 'string') return null;
// Disallow absolute paths or path traversal
if (action.startsWith('/') || action.includes('..') || action.includes('\\')) return null;
// Allow only safe characters: letters, digits, underscore, hyphen, slash
if (!/^[A-Za-z0-9_\/-]+$/.test(action)) return null;
return action;
}

async function handleApiProxy(req, res, action) {
const safeAction = sanitizeAction(action);
if (!safeAction) {
return json(res, 400, { success: false, message: '非法 action' });
}
const body = await readBody(req);
const response = await fetch(`https://api.xmoj-bbs.me/${action}`, {
const response = await fetch(`https://api.xmoj-bbs.me/${safeAction}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
@@ -134,7 +145,9 @@
if (pathname.startsWith('/__webui/api/') && req.method === 'POST') {
const action = pathname.substring('/__webui/api/'.length);
if (!action) return json(res, 400, { success: false, message: '缺少 action' });
return await handleApiProxy(req, res, action);
const safeAction = sanitizeAction(action);
if (!safeAction) return json(res, 400, { success: false, message: '非法 action' });
return await handleApiProxy(req, res, safeAction);
}

return await serveStatic(req, res, pathname);
Copilot is powered by AI and may make mistakes. Always verify output.
@chatgpt-codex-connector
Copy link

Summary

  • Addressed the review-blocking login issue by removing the impossible Set-Cookie extraction path in browser JS, updating the login UX text/button copy, and making the flow explicitly require manual PHPSESSID entry after credential check. This prevents the misleading “auto session capture” behavior. webui.htmlL40-L45 webui.htmlL314-L342
  • Fixed the GetMail API contract mismatch by switching request payload key to OtherUser and reading Data.Mail (singular), so message fetching/rendering works with the existing API shape. webui.htmlL253-L279
  • Added pasted-image validation (type + size) before FileReader upload to reduce performance/UX risk from oversized/unsupported clipboard images. webui.htmlL281-L312
  • Centralized repeated constants into APP_CONFIG (API base, login URL, version, storage key, asset base, paste limits) to reduce duplicated hard-coded values. webui.htmlL93-L101 webui.htmlL170-L198
  • Mitigated the untrusted external script concern by replacing remote MD5 script loading with a local vendored copy. webui.htmlL8 vendor/blueimp-md5-2.19.0.min.jsL1-L2
  • Updated README wording so login behavior no longer overstates automatic PHPSESSID retrieval in browser context. README.mdL76-L80

PR / Commit

  • Committed on current branch: 8eaa5c1
  • Created follow-up PR via make_pr with title: [Follow-up] Fix WebUI review issues: session flow, GetMail contract, and safer assets

Testing

  • curl -Ls https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_response_header_name | rg -n "Set-Cookie|forbidden response header" | head
  • git diff --check
  • python -m http.server 4173 (used for local manual render/screenshot run)
  • ✅ Playwright screenshot capture on http://localhost:4173/webui.html (artifact attached above)

View task →

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Mar 15, 2026

Deploying xmoj-script-dev-channel with  Cloudflare Pages  Cloudflare Pages

Latest commit: 636b037
Status: ✅  Deploy successful!
Preview URL: https://2ace9192.xmoj-script-dev-channel.pages.dev
Branch Preview URL: https://codex-github-mention-follow.xmoj-script-dev-channel.pages.dev

View logs

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tools/webui-proxy.mjs">

<violation number="1" location="tools/webui-proxy.mjs:7">
P1: Default this proxy to loopback. Binding to `0.0.0.0` exposes the served working tree to the LAN by default.</violation>

<violation number="2" location="tools/webui-proxy.mjs:9">
P2: Resolve static files from the script/repository directory instead of `process.cwd()`, or the proxy serves the wrong tree when launched from another directory.</violation>
</file>

<file name="README.md">

<violation number="1" location="README.md:80">
P3: This line understates the limitation: direct mode username/password login is not merely unstable, it is unsupported by the current implementation.</violation>
</file>

<file name="webui.html">

<violation number="1" location="webui.html:239">
P2: The direct-mode login branch can fail with a raw CORS/network error before your explanatory message runs. Skip the cross-origin login attempt here and fail fast with the guidance message instead.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

import { createHash } from 'node:crypto';
import { extname, join, normalize } from 'node:path';

const HOST = process.env.HOST || '0.0.0.0';
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Default this proxy to loopback. Binding to 0.0.0.0 exposes the served working tree to the LAN by default.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/webui-proxy.mjs, line 7:

<comment>Default this proxy to loopback. Binding to `0.0.0.0` exposes the served working tree to the LAN by default.</comment>

<file context>
@@ -0,0 +1,146 @@
+import { createHash } from 'node:crypto';
+import { extname, join, normalize } from 'node:path';
+
+const HOST = process.env.HOST || '0.0.0.0';
+const PORT = Number(process.env.PORT || 8787);
+const ROOT = process.cwd();
</file context>
Fix with Cubic


const HOST = process.env.HOST || '0.0.0.0';
const PORT = Number(process.env.PORT || 8787);
const ROOT = process.cwd();
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Resolve static files from the script/repository directory instead of process.cwd(), or the proxy serves the wrong tree when launched from another directory.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/webui-proxy.mjs, line 9:

<comment>Resolve static files from the script/repository directory instead of `process.cwd()`, or the proxy serves the wrong tree when launched from another directory.</comment>

<file context>
@@ -0,0 +1,146 @@
+
+const HOST = process.env.HOST || '0.0.0.0';
+const PORT = Number(process.env.PORT || 8787);
+const ROOT = process.cwd();
+
+const MIME = {
</file context>
Fix with Cubic

Comment on lines +239 to +248
const params = new URLSearchParams();
params.set('user_id', username);
params.set('password', md5(password));
await fetch('https://www.xmoj.tech/login.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params.toString(),
credentials: 'include'
});
throw new Error('直连模式下浏览器无法读取跨域 Set-Cookie,请手动填写 PHPSESSID,或改用代理模式。');
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The direct-mode login branch can fail with a raw CORS/network error before your explanatory message runs. Skip the cross-origin login attempt here and fail fast with the guidance message instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At webui.html, line 239:

<comment>The direct-mode login branch can fail with a raw CORS/network error before your explanatory message runs. Skip the cross-origin login attempt here and fail fast with the guidance message instead.</comment>

<file context>
@@ -168,59 +167,101 @@ <h2 class="h5">4) 发送消息 / 图片</h2>
   }
-  return response.json();
+
+  const params = new URLSearchParams();
+  params.set('user_id', username);
+  params.set('password', md5(password));
</file context>
Suggested change
const params = new URLSearchParams();
params.set('user_id', username);
params.set('password', md5(password));
await fetch('https://www.xmoj.tech/login.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params.toString(),
credentials: 'include'
});
throw new Error('直连模式下浏览器无法读取跨域 Set-Cookie,请手动填写 PHPSESSID,或改用代理模式。');
throw new Error('直连模式下浏览器无法读取跨域 Set-Cookie,请手动填写 PHPSESSID,或改用代理模式。');
Fix with Cubic


- 推荐启动代理模式:`node tools/webui-proxy.mjs`,然后访问 `http://127.0.0.1:8787/webui.html`。
- 代理模式下支持直接输入用户名/密码登录并自动获取 `PHPSESSID`;同时也支持手动粘贴 `PHPSESSID`。
- 直连模式(直接打开页面)受浏览器跨域限制,账号密码登录可能不可用,此时建议改用代理模式。
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This line understates the limitation: direct mode username/password login is not merely unstable, it is unsupported by the current implementation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At README.md, line 80:

<comment>This line understates the limitation: direct mode username/password login is not merely unstable, it is unsupported by the current implementation.</comment>

<file context>
@@ -75,8 +75,10 @@
-- 安全策略:密码仅用于发起登录请求,不会保存;会话信息仅存储在当前标签页的 `sessionStorage`,可一键清除。
+- 推荐启动代理模式:`node tools/webui-proxy.mjs`,然后访问 `http://127.0.0.1:8787/webui.html`。
+- 代理模式下支持直接输入用户名/密码登录并自动获取 `PHPSESSID`;同时也支持手动粘贴 `PHPSESSID`。
+- 直连模式(直接打开页面)受浏览器跨域限制,账号密码登录可能不可用,此时建议改用代理模式。
+- 安全策略:密码仅用于发起登录请求,不会在前端存储;会话信息仅存储在当前标签页的 `sessionStorage`,可一键清除。
 - 维护策略:API 调用格式与现有脚本中的 `RequestAPI` 保持一致,便于后续复用和统一维护。
</file context>
Suggested change
- 直连模式(直接打开页面)受浏览器跨域限制,账号密码登录可能不可用,此时建议改用代理模式
- 直连模式(直接打开页面)受浏览器跨域限制,账号密码登录不可用,此时请手动填写 `PHPSESSID`,或改用代理模式
Fix with Cubic

@boomzero
Copy link
Member Author

What a mess

@boomzero boomzero closed this Mar 15, 2026
@boomzero
Copy link
Member Author

I am clauding

@PythonSmall-Q
Copy link
Member

你可以看看我用copilot做的那个,里面有一个和你奇妙的登录功能

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

codex size/XL website This issue or pull request is related to website related files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants