Compare commits

..

35 Commits

Author SHA1 Message Date
renovate[bot] 6ed1f82e94 fix(deps): update module github.com/protonmail/gopenpgp/v2 to v3 2026-10-05 18:40:29 +00:00
naiy_ 4c39bbe9c2 fix(drivers/189pc): align login with the official PC client (#3105)
QR code scans were authorized on the phone but the storage stayed stuck on
the QR page, and token-only setups failed with "params is null".

The driver used appId 8025431004 while the official PC client uses
9317140619. Tokens, sessions and QR sessions are all scoped to an appId, so
the mismatch meant the QR poll never saw status:0 and an accessToken could
not be exchanged for a sessionSecret.

- Use appId 9317140619 and version 7.2.4.0. QR state polling uses
  clientType=1; password login keeps 10020.
- Send the QR poll parameters the official client sends (cb_SaveName,
  isOauth2, state, user-finger header, logbox Referer) and poll locally
  instead of only checking once per save.
- Parse lt/reqId from the logbox redirect and paramId from appConf.do. The
  new login page no longer embeds them as inline variables; the old inline
  format is still supported.
- Implement the -133 second device verification via
  sendSmsCodeForSecondAuth/submitForSecondAuth. That endpoint has no
  dedicated SMS field: the code goes into epd, encrypted with the same
  public key used for the password. Persist the DEVICEID cookie so the
  verification only happens once.
- Detect refreshToken.do failures. It reports them as HTTP 200 with a
  result field, so SetError never fired and a failed refresh was treated as
  success, surfacing later as a misleading "params is null".
- username/password are no longer required, so token-only storages save
  without placeholders. clientSn/jgOpenId are optional and only sent when
  configured; user-finger is generated once per storage.
- Return named errors instead of panicking when the login page changes shape.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-03 15:45:29 +08:00
帅丘 e1d88b071f fix(drivers/lanzou): support new /fn download page structure (#3157)
Lanzou recently changed the file share page: download params moved
into an iframe (/fn?<token>) inner page, and the download API is now
an absolute URL (https://apifile.woozooo.com/ajaxfile.php?file=N)
with new sign params (wp_sign/ajaxdata, action=downprocess).

The old findFileIDReg ('/ajaxm.php?file=N' relative path) no longer
matches, causing 'failed link: failed get link: not find file id'.

Fall back to parsing the new /fn page structure when the legacy
regex does not match. Legacy flow is kept untouched.

Signed-off-by: rzsgsfm <rzsgsfm@users.noreply.github.com>
Co-authored-by: rzsgsfm <rzsgsfm@users.noreply.github.com>
Co-authored-by: GLM (ZCode CLI) <noreply@z.ai>
2026-10-03 14:50:51 +08:00
ILoveScratch ea10624fb6 fix: harden request handling for files, search, proxy and SSO
Co-authored-by: PIKACHUIM <PIKACHUIM@users.noreply.github.com>
2026-09-29 19:05:50 +08:00
ILoveScratch 54ae9d7451 fix(internal/db/searchnode): fix index update path (#3128) 2026-09-24 19:25:29 +08:00
Yinan Qin c16701b94b fix(build): update cgo-actions version to v1.3.0 and fallback FreeBSD version to 14.4 (#3130) 2026-09-24 19:23:21 +08:00
Nostalgia 893457cd50 fix(aliyundrive): limit callback concurrency (#3071)
* fix(aliyundrive): limit callback concurrency

- Share proxy callback admission by Aliyun user identity and hold permits for complete response-body lifetimes.
- Retry only verified callback-capacity rejections while preserving direct redirects and server download limiting.
- Map exhausted temporary capacity to S3 SlowDown through the merged OpenListTeam gofakes3 module.
- Cover shared limits, lifecycle release, cancellation, retry classification, and the S3 HTTP response.

Co-authored-by: Codex <267193182+codex@users.noreply.github.com>

# Conflicts:
#	go.mod
#	go.sum
#	server/s3/pager.go

* fix(op): separate redirect and proxy link cache entries

- Include redirect mode in the link cache key for all drivers.

- Cover both redirect-to-proxy and proxy-to-redirect cache reuse.

Co-authored-by: Codex <267193182+codex@users.noreply.github.com>

* fix(proxy): close range bodies before opening next

- make ServeHTTP own each range body and preserve cleanup failures
- remove the aggregate range closer and pass range readers directly
- replace the obsolete callback transport test with focused lifecycle coverage

Co-authored-by: Codex <267193182+codex@users.noreply.github.com>

---------

Co-authored-by: nostalume <nostalucent@gmail.com>
Co-authored-by: Codex <267193182+codex@users.noreply.github.com>
2026-09-24 12:01:50 +08:00
Nostalgia 90acfa18e4 refactor(context): centralize request origin access (#3100) 2026-09-24 01:57:21 +08:00
Nostalgia 1462d63a48 fix(net): preserve cancellation errors during partitioned downloads (#3090) 2026-09-24 01:47:30 +08:00
spark cadbf87246 fix(teldrive): use a valid root path for listings (#3108) 2026-09-24 01:46:35 +08:00
ZRHan 9de3f69b8f fix(fs): only list parent dir for related objs when file is video (#3113) 2026-09-24 01:46:14 +08:00
Wray e73a80c78c feat(drivers/pikpak): auto re-login with username/password when tokens expire (#3001) 2026-09-24 01:29:56 +08:00
ZRHan 6c6009109f fix(fs): return FOLDER type for directories in fs/get responses (#3114) 2026-09-24 01:26:42 +08:00
Nostalgia b51d1c8284 fix(s3): close ranged response bodies (#3095) 2026-09-24 01:26:26 +08:00
ILoveScratch f286862c61 refactor(stream): index parked readers by offset instead of full scans (#3048) 2026-09-24 01:25:57 +08:00
awaae 40f4f6546f fix(ftp): handle EOF and detect content type from read bytes (#3125) 2026-09-24 01:20:20 +08:00
Nostalgia 3a31b438a9 refactor(offline): centralize native tool setup (#3096)
- Keep storage-to-tool identity in the offline tool package.
- Share settings persistence, tool initialization, and storage admission across handlers.
- Preserve endpoint payloads and unsupported-storage diagnostics with focused tests.

Co-authored-by: nostalume <nostalucent@gmail.com>
Co-authored-by: Codex <267193182+codex@users.noreply.github.com>
2026-09-21 16:54:22 +08:00
Nostalgia 56064d1981 fix(op): enforce link cache lifecycle policy (#3101)
- Share one admitted lifecycle policy between regular and archive links.
- Reject and release links that combine TTL caching with owned resources.
- Keep wrapper clones independent from the source cache expiration.
- Cover reuse, reference, invalidation, conflict, and clone behavior.

Co-authored-by: nostalume <nostalucent@gmail.com>
Co-authored-by: Codex <267193182+codex@users.noreply.github.com>
2026-09-21 16:54:12 +08:00
Ziheng Mao d894a3983b fix(115_open): refresh expired OSS credentials during upload (#3063)
- Refresh OSS credentials and retry the rejected multipart operation
- Preserve upload IDs, completed parts, callbacks, and outer part retries
- Rewind parts before resending and pass the upload context to OSS requests

Co-authored-by: Codex <267193182+codex@users.noreply.github.com>
2026-09-21 10:38:14 +08:00
ShenLin 084c008102 chore(build): replace uncontrolled email domain (#3103)
- Use the controlled oplist.org domain for embedded build authors
- Update shell and release workflow build metadata

Signed-off-by: jyxjjj <16695261+jyxjjj@users.noreply.github.com>
Co-authored-by: Codex <267193182+codex@users.noreply.github.com>
2026-09-20 14:25:20 +08:00
Fighting 4580c4db33 fix(alist_v3): set child paths so nested directories resolve (#3019)
* fix(alist_v3): set child paths so nested directories resolve

- Set `Path` on every object returned by `List`, matching the OpenList
  driver. `op.Get` hands a child object straight back to `List`, so a
  child without a path made the driver request `""` from the upstream
  server, which answered with its own root: every directory below the
  mount point served the same listing back, endlessly.
- Add tests covering the child paths and a three-level descent.

Co-authored-by: Claude <81847+claude@users.noreply.github.com>

* test(alist_v3): trim the child-path test to a single case

Collapse the two tests into one two-level descent, drop the recorder
type and the helper funcs, and inline the driver setup. 173 -> 65 lines.

Co-authored-by: Claude <81847+claude@users.noreply.github.com>

---------

Co-authored-by: Fighting <3899648+Elity@users.noreply.github.com>
Co-authored-by: Claude <81847+claude@users.noreply.github.com>
2026-09-15 23:13:10 +08:00
Nostalgia 5447ecb072 fix(s3): encode multipart fixture paths (#3074)
test(s3): encode multipart fixture paths

- Serialize the Local storage addition instead of interpolating filesystem paths into JSON.
- Keep multipart fixture behavior portable across Windows and Linux.

Co-authored-by: nostalume <nostalucent@gmail.com>
Co-authored-by: Codex <267193182+codex@users.noreply.github.com>
2026-09-14 20:30:17 +08:00
flyingrtx f18b4acc76 feat(local): add PDF thumbnails on macOS (#3017)
- add an opt-in Local driver setting for PDF thumbnails
- render PDF first pages with macOS Quick Look
- preserve unsupported-platform behavior and cover the renderer with tests

Co-authored-by: Codex <267193182+codex@users.noreply.github.com>
Co-authored-by: ILoveScratch <ilovescratch@foxmail.com>
2026-09-10 19:45:24 +08:00
fryeggs d6109a7940 feat(task): persist task timestamps across restarts (#2914)
Co-authored-by: OpenAI Codex <noreply@openai.com>
2026-09-10 19:20:11 +08:00
ShenLin 0463da4034 chore(ci): enforce AI disclosures and lock invalid issues (#3059)
ci(github): enforce AI disclosures and lock invalid issues

- Require exactly one AI disclosure option and a model name when AI is used
- Comment, close, lock, and label issues invalid for missing or malformed AI disclosures or missing model names
- Lock issues closed for unchanged placeholder titles
- Lock issues closed for selecting the unread checklist option
- Exclude AI disclosure options from unchecked task detection
- Skip automatic checks for announcement titles
- Update bilingual closure replies to mention locking and request a new issue
- Split issue replies and PR title checks into separate workflows without changing PR title rules

Signed-off-by: jyxjjj <16695261+jyxjjj@users.noreply.github.com>
Co-authored-by: Codex <267193182+codex@users.noreply.github.com>
2026-09-08 15:20:36 +08:00
Pikachu Ren 2d51c9ab4b feat!(init): add initialization wizard (#3041)
feat: add system initialization (setup wizard) support

Co-authored-by: PIKACHUIM <PIKACHUIM@users.noreply.github.com>
2026-09-07 14:14:22 +08:00
ShenLin d9d8aa24e6 fix(s3): default upload content types and return partial content (#3053)
- Default missing upload MIME types to application/octet-stream before passing streams to storage drivers.
- Return HTTP 206 for successful ranged GET responses while preserving error statuses.
- Add isolated response-status regression tests without database initialization.

Signed-off-by: jyxjjj <16695261+jyxjjj@users.noreply.github.com>
Co-authored-by: Codex <267193182+codex@users.noreply.github.com>
2026-09-07 12:01:15 +08:00
ShenLin 55530ff171 fix(release): fetch frontend assets from edge (#3052)
- Fetch frontend prerelease assets from edge after release immutability was accidentally enabled for rolling.

Signed-off-by: jyxjjj <16695261+jyxjjj@users.noreply.github.com>
Co-authored-by: Codex <267193182+codex@users.noreply.github.com>
2026-09-07 12:00:50 +08:00
MadDogOwner 6247cf7be2 feat(server/s3): support multipart upload (#2813) 2026-09-05 15:56:40 +08:00
renovate[bot] eee910babb fix(deps): update module github.com/rclone/rclone to v1.75.1 (#3035)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-09-05 12:22:53 +08:00
renovate[bot] 6b55a82ffe chore(deps): update docker/setup-qemu-action digest to 1f40c72 (#3021)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-09-05 12:14:09 +08:00
renovate[bot] 93dac1655f chore(deps): update go toolchain directive to v1.27.1 (#3024)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-09-05 12:12:51 +08:00
Pikachu Ren 6ad44605c0 feat(drivers/guangyapan): add md5-based instant upload support (#3034)
feat(guangyapan): add md5-based instant upload support

Co-authored-by: PIKACHUIM <PIKACHUIM@users.noreply.github.com>
2026-09-05 12:11:51 +08:00
UcnacDx2 d90d84906e fix(drivers/139): improve mail login credential renewal (#3029)
* fix(drivers/139): improve mail login credential renewal

* fix(drivers/139): guard mail login client initialization

Fall back to base.NewRestyClient() when base.RestyClient has not been initialized, while preserving cloned global-client behavior and the login/SMS retry and redirect policies.
2026-09-05 11:55:41 +08:00
ShenLin c3d3da9286 fix(drivers/189): decode JSON strings before parsing timestamps (#3033)
- Decode JSON time strings before normalizing Unicode spaces in both 189 drivers
- Exercise escaped spaces and existing date formats through JSON unmarshalling
- Cover invalid JSON input and XML time parsing

Signed-off-by: jyxjjj <16695261+jyxjjj@users.noreply.github.com>
Co-authored-by: Codex <267193182+codex@users.noreply.github.com>
2026-09-05 00:12:20 +08:00
114 changed files with 5846 additions and 1003 deletions
+12 -4
View File
@@ -82,9 +82,17 @@ body:
label: 复现链接(可选)
description: |
请提供能复现此问题的链接。
- type: textarea
- type: checkboxes
id: aigenerated
attributes:
label: AI生成内容(可选)
description: |
如果此问题是由AI辅助您发现的,请提供全部聊天记录,包括使用的模型信息。
label: AI生成内容
description: 必须且只能勾选一项,请勿删除或修改声明文字。
options:
- label: 我使用了AI工具生成此内容
- label: 我没有使用AI工具生成此内容
- type: input
id: ai-model
attributes:
label: AI模型是
description: 如果使用了AI工具,请填写模型名称;未使用则留空。
placeholder: xxx
+12 -4
View File
@@ -82,9 +82,17 @@ body:
label: Reproduction Link (optional)
description: |
Please provide a link to a repo or page that can reproduce this issue.
- type: textarea
- type: checkboxes
id: aigenerated
attributes:
label: AI Generated Content (optional)
description: |
If this issue was identified with the assistance of AI, please provide the complete chat log, including information about the model used.
label: AI Generated Content
description: Select exactly one option. Do not delete or modify the disclosure text.
options:
- label: I used AI tools to generate this content
- label: I did not use AI tools to generate this content
- type: input
id: ai-model
attributes:
label: AI model used
description: If you used AI tools, enter the model name; otherwise leave this blank.
placeholder: xxx
@@ -48,9 +48,17 @@ body:
label: 附加信息
description: |
相关的任何其他上下文或截图,或者你觉得有帮助的信息
- type: textarea
- type: checkboxes
id: aigenerated
attributes:
label: AI生成内容(可选)
description: |
如果此请求是由AI辅助您提交的,请提供全部聊天记录,包括使用的模型信息。
label: AI生成内容
description: 必须且只能勾选一项,请勿删除或修改声明文字。
options:
- label: 我使用了AI工具生成此内容
- label: 我没有使用AI工具生成此内容
- type: input
id: ai-model
attributes:
label: AI模型是
description: 如果使用了AI工具,请填写模型名称;未使用则留空。
placeholder: xxx
@@ -48,9 +48,17 @@ body:
label: Additional Information
description: |
Any other context or screenshots related to this feature request, or information you find helpful.
- type: textarea
- type: checkboxes
id: aigenerated
attributes:
label: AI Generated Content (optional)
description: |
If this request was submitted with the assistance of an AI, please provide the complete chat log, including information about the model used.
label: AI Generated Content
description: Select exactly one option. Do not delete or modify the disclosure text.
options:
- label: I used AI tools to generate this content
- label: I did not use AI tools to generate this content
- type: input
id: ai-model
attributes:
label: AI model used
description: If you used AI tools, enter the model name; otherwise leave this blank.
placeholder: xxx
+3 -3
View File
@@ -115,7 +115,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version: "1.27.0"
go-version: "1.27.1"
- name: Setup web
run: bash build.sh dev web
@@ -124,7 +124,7 @@ jobs:
FRONTEND_REPO: ${{ vars.FRONTEND_REPO }}
- name: Build
uses: OpenListTeam/cgo-actions@6fcace5934c36d70503dba06e2396bb58b766130 # v1.2.5
uses: OpenListTeam/cgo-actions@d760a8ec1a6be1f8ec181229e11cb2671797f9e0 # v1.3.0
with:
targets: ${{ matrix.target }}
flags: ${{ matrix.flags || '-ldflags=' }}
@@ -136,7 +136,7 @@ jobs:
musl-base-url: "https://github.com/OpenListTeam/musl-compilers/releases/latest/download/"
x-flags: |
github.com/OpenListTeam/OpenList/v4/internal/conf.BuiltAt=$built_at
github.com/OpenListTeam/OpenList/v4/internal/conf.GitAuthor=The OpenList Projects Contributors <noreply@openlist.team>
github.com/OpenListTeam/OpenList/v4/internal/conf.GitAuthor=The OpenList Projects Contributors <noreply@oplist.org>
github.com/OpenListTeam/OpenList/v4/internal/conf.GitCommit=$git_commit
github.com/OpenListTeam/OpenList/v4/internal/conf.Version=$tag
github.com/OpenListTeam/OpenList/v4/internal/conf.WebVersion=rolling
+3 -3
View File
@@ -33,7 +33,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version: "1.27.0"
go-version: "1.27.1"
- name: Setup web
run: bash build.sh dev web
@@ -42,7 +42,7 @@ jobs:
FRONTEND_REPO: ${{ vars.FRONTEND_REPO }}
- name: Build
uses: OpenListTeam/cgo-actions@6fcace5934c36d70503dba06e2396bb58b766130 # v1.2.5
uses: OpenListTeam/cgo-actions@d760a8ec1a6be1f8ec181229e11cb2671797f9e0 # v1.3.0
with:
targets: ${{ matrix.target }}
flags: ${{ contains(matrix.target, '-musl') && '-ldflags=-linkmode external -extldflags ''-static -fpic''' || '-ldflags=' }}
@@ -52,7 +52,7 @@ jobs:
out-dir: build
x-flags: |
github.com/OpenListTeam/OpenList/v4/internal/conf.BuiltAt=$built_at
github.com/OpenListTeam/OpenList/v4/internal/conf.GitAuthor=The OpenList Projects Contributors <noreply@openlist.team>
github.com/OpenListTeam/OpenList/v4/internal/conf.GitAuthor=The OpenList Projects Contributors <noreply@oplist.org>
github.com/OpenListTeam/OpenList/v4/internal/conf.GitCommit=$git_commit
github.com/OpenListTeam/OpenList/v4/internal/conf.Version=$tag
github.com/OpenListTeam/OpenList/v4/internal/conf.WebVersion=rolling
+75
View File
@@ -0,0 +1,75 @@
name: Issue Auto Reply
on:
issues:
types: [opened]
permissions:
issues: write
jobs:
auto-reply:
runs-on: ubuntu-latest
steps:
- name: Check issue for unchecked tasks and reply
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
if (context.payload.issue.title.startsWith('[Announcements]')) return;
const titleNotEdited = /(请修改标题|Please modify the title)/i.test(context.payload.issue.title);
const issueBody = context.payload.issue.body || "";
const aiSection = issueBody.match(/^### (AI生成内容|AI Generated Content)\r?\n([\s\S]*?)(?=^### |$(?![\s\S]))/m);
const aiOptionsPattern = aiSection?.[1] === 'AI生成内容'
? /^\s*- \[([ xX])\] 我使用了AI工具生成此内容\r?\n- \[([ xX])\] 我没有使用AI工具生成此内容\s*$/
: /^\s*- \[([ xX])\] I used AI tools to generate this content\r?\n- \[([ xX])\] I did not use AI tools to generate this content\s*$/;
const aiOptions = (aiSection?.[2] || '').match(aiOptionsPattern);
const validAiDisclosure = aiOptions !== null && (aiOptions[1] !== ' ') !== (aiOptions[2] !== ' ');
const aiModelSection = issueBody.match(/^### (AI模型是|AI model used)\r?\n([\s\S]*?)(?=^### |$(?![\s\S]))/m);
const aiModel = (aiModelSection?.[2] || '').trim();
const missingAiModel = validAiDisclosure && aiOptions[1] !== ' ' && (aiModel === '' || aiModel === '_No response_');
const confirmNotRead = /- \[[xX]\] (?:我没有阅读这个清单|I have not read these checkboxes)/.test(issueBody);
const closeIssue = titleNotEdited || confirmNotRead || !validAiDisclosure || missingAiModel;
let comment;
if (titleNotEdited) {
comment = `⚠️ 请修改标题以更好地描述您的问题或需求,并删除示例提示。当前 Issue 将被自动关闭并锁定。如需继续提交,请创建新的 Issue。
⚠️ Please modify the title to better describe your issue or request, and remove the example prompt. This issue will be automatically closed and locked. If you wish to proceed, please create a new issue.
`;
} else if (confirmNotRead || !validAiDisclosure || missingAiModel) {
comment = `⚠️ 你的 Issue 不符合提交规则。请先阅读相关规范后再重新提交。当前 Issue 将被自动关闭并锁定。如需继续提交,请确认已了解规则后创建新的 Issue。
⚠️ Your issue does not comply with the submission rules. Please read the guidelines before submitting again. This issue will be automatically closed and locked. If you wish to proceed, please confirm that you have reviewed the rules before creating a new issue.
`;
} else if (/- \[ \] (?!我没有阅读这个清单|I have not read these checkboxes)/.test(issueBody.replace(aiSection[0], ''))) {
comment = `感谢您联系OpenList。我们会尽快回复您。
Thanks for contacting OpenList. We will reply to you as soon as possible.
由于您提出的 Issue 中包含部分未确认的项目,为了更好地管理项目,在人工审核后可能会直接关闭此问题。
如果您能确认并补充相关未确认项目的信息,欢迎随时重新提交。我们会及时关注并处理。感谢您的理解与支持!
Since your issue contains some unchecked tasks, it may be closed after manual review.
If you can confirm and provide information for the unchecked tasks, feel free to resubmit.
We will pay attention and handle it in a timely manner.
感谢您的理解与支持!
Thank you for your understanding and support!
`;
} else {
return;
}
await github.rest.issues.createComment({
...context.repo,
issue_number: context.issue.number,
body: comment
});
if (closeIssue) {
await github.rest.issues.update({
...context.repo,
issue_number: context.issue.number,
state: 'closed',
state_reason: 'not_planned',
labels: ['invalid']
});
await github.rest.issues.lock({
...context.repo,
issue_number: context.issue.number
});
}
-101
View File
@@ -1,101 +0,0 @@
name: Issue or PR Auto Reply
on:
issues:
types: [opened]
pull_request_target:
types: [opened]
permissions:
issues: write
pull-requests: write
jobs:
auto-reply:
runs-on: ubuntu-latest
if: github.event_name == 'issues'
steps:
- name: Check issue for unchecked tasks and reply
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
let comment = "";
const issueTitle = context.payload.issue.title || "";
const titleNotEdited = /(请修改标题|Please modify the title)/i.test(issueTitle);
if (titleNotEdited) {
comment = "⚠️ 请修改标题以更好地描述您的问题或需求,并删除示例提示。当前 Issue 将被自动关闭。如需继续提交,请创建新的 Issue。\n";
comment += "⚠️ Please modify the title to better describe your issue or request, and remove the example prompt. This issue will be automatically closed. If you wish to proceed, please create a new issue.\n";
await github.rest.issues.createComment({
...context.repo,
issue_number: context.issue.number,
body: comment
});
await github.rest.issues.update({
...context.repo,
issue_number: context.issue.number,
state: 'closed',
state_reason: 'not_planned',
labels: ['invalid']
});
return;
}
const issueBody = context.payload.issue.body || "";
const confirmHasRead = /- \[ \] (?!我没有阅读这个清单|I have not read these checkboxes)/.test(issueBody);
const confirmNotRead = /- \[[xX]\] (?:我没有阅读这个清单|I have not read these checkboxes)/.test(issueBody);
if (confirmNotRead) {
comment = "⚠️ 你的 Issue 不符合提交规则。请先阅读相关规范后再重新提交。当前 Issue 将被自动关闭。如需继续提交,请确认已了解规则后重新打开或创建新的 Issue。\n";
comment += "⚠️ Your issue does not comply with the submission rules. Please read the guidelines before submitting again. This issue will be automatically closed. If you wish to proceed, please confirm that you have reviewed the rules before reopening or creating a new issue.\n";
await github.rest.issues.createComment({
...context.repo,
issue_number: context.issue.number,
body: comment
});
await github.rest.issues.update({
...context.repo,
issue_number: context.issue.number,
state: 'closed',
state_reason: 'not_planned',
labels: ['invalid']
});
return;
}
if (confirmHasRead) {
comment = "感谢您联系OpenList。我们会尽快回复您。\n";
comment += "Thanks for contacting OpenList. We will reply to you as soon as possible.\n\n";
comment += "由于您提出的 Issue 中包含部分未确认的项目,为了更好地管理项目,在人工审核后可能会直接关闭此问题。\n";
comment += "如果您能确认并补充相关未确认项目的信息,欢迎随时重新提交。我们会及时关注并处理。感谢您的理解与支持!\n";
comment += "Since your issue contains some unchecked tasks, it may be closed after manual review.\n";
comment += "If you can confirm and provide information for the unchecked tasks, feel free to resubmit.\n";
comment += "We will pay attention and handle it in a timely manner.\n\n";
comment += "感谢您的理解与支持!\n";
comment += "Thank you for your understanding and support!\n";
await github.rest.issues.createComment({
...context.repo,
issue_number: context.issue.number,
body: comment
});
}
pr-title-check:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request_target'
steps:
- name: Check PR title for required prefix and comment
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
const title = context.payload.pull_request.title || "";
const ok = /^(feat|docs|fix|style|refactor|chore)\(.+?\)!?: /i.test(title);
if (!ok) {
let comment = "⚠️ PR 标题需以 `feat(): `, `docs(): `, `fix(): `, `style(): `, `refactor(): `, `chore(): ` 其中之一开头,例如:`feat(component): 新增功能`。\n";
comment += "⚠️ The PR title must start with `feat(): `, `docs(): `, `fix(): `, `style(): `, or `refactor(): `, `chore(): `. For example: `feat(component): add new feature`.\n\n";
comment += "如果跨多个组件,请使用主要组件作为前缀,并在标题中枚举、描述中说明。\n";
comment += "If it spans multiple components, use the main component as the prefix and enumerate in the title, describe in the body.\n\n";
comment += "如果是破坏性变更,请在类型后添加 `!`,例如 `feat(component)!: 破坏性变更`。\n";
comment += "For breaking changes, add `!` after the type, e.g., `feat(component)!: breaking change`.\n\n";
await github.rest.issues.createComment({
...context.repo,
issue_number: context.issue.number,
body: comment
});
}
+34
View File
@@ -0,0 +1,34 @@
name: PR Title Check
on:
pull_request_target:
types: [opened]
permissions:
pull-requests: write
jobs:
pr-title-check:
runs-on: ubuntu-latest
steps:
- name: Check PR title for required prefix and comment
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
const title = context.payload.pull_request.title;
if (/^(feat|docs|fix|style|refactor|chore)\(.+?\)!?: /i.test(title)) return;
const comment = `⚠️ PR 标题需以 \`feat(): \`, \`docs(): \`, \`fix(): \`, \`style(): \`, \`refactor(): \`, \`chore(): \` 其中之一开头,例如:\`feat(component): 新增功能\`。
⚠️ The PR title must start with \`feat(): \`, \`docs(): \`, \`fix(): \`, \`style(): \`, or \`refactor(): \`, \`chore(): \`. For example: \`feat(component): add new feature\`.
如果跨多个组件,请使用主要组件作为前缀,并在标题中枚举、描述中说明。
If it spans multiple components, use the main component as the prefix and enumerate in the title, describe in the body.
如果是破坏性变更,请在类型后添加 \`!\`,例如 \`feat(component)!: 破坏性变更\`。
For breaking changes, add \`!\` after the type, e.g., \`feat(component)!: breaking change\`.
`;
await github.rest.issues.createComment({
...context.repo,
issue_number: context.issue.number,
body: comment
});
+1 -1
View File
@@ -46,7 +46,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version: '1.27.0'
go-version: '1.27.1'
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
+3 -3
View File
@@ -87,7 +87,7 @@ jobs:
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version: '1.27.0'
go-version: '1.27.1'
- name: Cache Musl
id: cache-musl
@@ -153,7 +153,7 @@ jobs:
path: 'build/'
- name: Set up QEMU
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4
uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
@@ -237,7 +237,7 @@ jobs:
path: 'build/'
- name: Set up QEMU
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4
uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
+2 -2
View File
@@ -36,7 +36,7 @@ jobs:
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version: '1.27.0'
go-version: '1.27.1'
- name: Cache Musl
id: cache-musl
@@ -104,7 +104,7 @@ jobs:
path: 'build/'
- name: Set up QEMU
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4
uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
+4 -4
View File
@@ -1,7 +1,7 @@
set -e
appName="openlist"
builtAt="$(date +'%F %T %z')"
gitAuthor="The OpenList Projects Contributors <noreply@openlist.team>"
gitAuthor="The OpenList Projects Contributors <noreply@oplist.org>"
gitCommit=$(git log --pretty=format:"%h" -1)
# Set frontend repository, default to OpenListTeam/OpenList-Frontend
@@ -97,7 +97,7 @@ AssertStaticBinary() {
}
FetchWebRolling() {
pre_release_json=$(eval "curl -fsSL --max-time 2 $githubAuthArgs -H \"Accept: application/vnd.github.v3+json\" \"https://api.github.com/repos/$frontendRepo/releases/tags/rolling\"")
pre_release_json=$(eval "curl -fsSL --max-time 2 $githubAuthArgs -H \"Accept: application/vnd.github.v3+json\" \"https://api.github.com/repos/$frontendRepo/releases/tags/edge\"")
pre_release_assets=$(echo "$pre_release_json" | jq -r '.assets[].browser_download_url')
# There is no lite for rolling
@@ -531,8 +531,8 @@ BuildReleaseFreeBSD() {
sed 's/\.0$//')
if [ -z "$freebsd_version" ]; then
echo "Failed to get FreeBSD version, falling back to 14.3"
freebsd_version="14.3"
echo "Failed to get FreeBSD version, falling back to 14.4"
freebsd_version="14.4"
fi
echo "Using FreeBSD version: $freebsd_version"
+61 -16
View File
@@ -3,6 +3,7 @@ package _115_open
import (
"context"
"encoding/base64"
"errors"
"io"
"time"
@@ -70,6 +71,19 @@ func (d *Open115) singleUpload(ctx context.Context, tempF model.File, tokenResp
// } `json:"data"`
// }
// retryExpiredToken retries only the rejected OSS operation, preserving the upload ID.
func retryExpiredToken(refresh func() error, operation func() error) error {
err := operation()
var serviceErr oss.ServiceError
if !errors.As(err, &serviceErr) || serviceErr.Code != "SecurityTokenExpired" {
return err
}
if err := refresh(); err != nil {
return err
}
return operation()
}
func (d *Open115) multpartUpload(ctx context.Context, stream model.FileStreamer, up driver.UpdateProgress, tokenResp *sdk.UploadGetTokenResp, initResp *sdk.UploadInitResp) error {
ossClient, err := netutil.NewOSSClient(tokenResp.Endpoint, tokenResp.AccessKeyId, tokenResp.AccessKeySecret, oss.SecurityToken(tokenResp.SecurityToken))
if err != nil {
@@ -80,7 +94,32 @@ func (d *Open115) multpartUpload(ctx context.Context, stream model.FileStreamer,
return err
}
imur, err := bucket.InitiateMultipartUpload(initResp.Object, oss.Sequential())
refresh := func() error {
if err := d.WaitLimit(ctx); err != nil {
return err
}
token, err := d.client.UploadGetToken(ctx)
if err != nil {
return err
}
client, err := netutil.NewOSSClient(token.Endpoint, token.AccessKeyId, token.AccessKeySecret, oss.SecurityToken(token.SecurityToken))
if err != nil {
return err
}
newBucket, err := client.Bucket(initResp.Bucket)
if err != nil {
return err
}
bucket = newBucket
return nil
}
var imur oss.InitiateMultipartUploadResult
err = retryExpiredToken(refresh, func() error {
var err error
imur, err = bucket.InitiateMultipartUpload(initResp.Object, oss.Sequential(), oss.WithContext(ctx))
return err
})
if err != nil {
return err
}
@@ -109,13 +148,17 @@ func (d *Open115) multpartUpload(ctx context.Context, stream model.FileStreamer,
return err
}
err = retry.Do(func() error {
rd.Seek(0, io.SeekStart)
part, err := bucket.UploadPart(imur, driver.NewLimitedUploadStream(ctx, rd), partSize, int(i))
if err != nil {
return err
}
parts[i-1] = part
return nil
return retryExpiredToken(refresh, func() error {
if _, err := rd.Seek(0, io.SeekStart); err != nil {
return err
}
part, err := bucket.UploadPart(imur, driver.NewLimitedUploadStream(ctx, rd), partSize, int(i), oss.WithContext(ctx))
if err != nil {
return err
}
parts[i-1] = part
return nil
})
},
retry.Context(ctx),
retry.Attempts(3),
@@ -134,14 +177,16 @@ func (d *Open115) multpartUpload(ctx context.Context, stream model.FileStreamer,
up(float64(offset) * 100 / float64(fileSize))
}
// callbackRespBytes := make([]byte, 1024)
_, err = bucket.CompleteMultipartUpload(
imur,
parts,
oss.Callback(base64.StdEncoding.EncodeToString([]byte(initResp.Callback.Value.Callback))),
oss.CallbackVar(base64.StdEncoding.EncodeToString([]byte(initResp.Callback.Value.CallbackVar))),
// oss.CallbackResult(&callbackRespBytes),
)
err = retryExpiredToken(refresh, func() error {
_, err := bucket.CompleteMultipartUpload(
imur,
parts,
oss.Callback(base64.StdEncoding.EncodeToString([]byte(initResp.Callback.Value.Callback))),
oss.CallbackVar(base64.StdEncoding.EncodeToString([]byte(initResp.Callback.Value.CallbackVar))),
oss.WithContext(ctx),
)
return err
})
if err != nil {
return err
}
+4 -4
View File
@@ -7,11 +7,11 @@ import (
type Addition struct {
//Account string `json:"account" required:"true"`
Authorization string `json:"authorization" type:"text" help:"Authorization can be used alone. If empty, use mail_cookies alone for fast login, or mail_cookies + username + password for full login fallback."`
Username string `json:"username" help:"Required only when using password login fallback with mail_cookies."`
Password string `json:"password" secret:"true" help:"Required only when using password login fallback with mail_cookies."`
Authorization string `json:"authorization" type:"text" help:"Authorization can be used alone. If empty, use username + password; mail_cookies is optional and will be established/updated automatically. Existing mail_cookies can also be used alone for fast login."`
Username string `json:"username" help:"Use together with password when Authorization is empty. mail_cookies may be left empty on the first login."`
Password string `json:"password" secret:"true" help:"Use together with username when Authorization is empty. mail_cookies may be left empty on the first login."`
SmsCode string `json:"sms_code" secret:"true" help:"Fill this only after OpenList reports that a 139 Mail SMS verification code was sent, then save the storage again."`
MailCookies string `json:"mail_cookies" type:"text" help:"Cookies from mail.10086.cn. Used for fast login only when Authorization is empty; otherwise retained as device context for password login fallback."`
MailCookies string `json:"mail_cookies" type:"text" help:"Optional cookies from mail.10086.cn. Leave empty for a first username/password login; cookies created or updated by password/SMS login are persisted and reused as device context. Existing cookies may also be used alone for fast login."`
driver.RootID
Type string `json:"type" type:"select" options:"personal_new,family,group,personal,share" default:"personal_new"`
LinkID string `json:"link_id" type:"text" help:"Multiple shares are separated by commas or new lines. Use link_id#password for password-protected shares."`
+30 -142
View File
File diff suppressed because it is too large Load Diff
+124 -58
View File
File diff suppressed because it is too large Load Diff
+8 -1
View File
@@ -5,6 +5,7 @@ import (
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"encoding/xml"
"fmt"
"net/http"
@@ -59,7 +60,13 @@ func timestamp() int64 {
type Time time.Time
func (t *Time) UnmarshalJSON(b []byte) error { return t.Unmarshal(b) }
func (t *Time) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
return t.Unmarshal([]byte(s))
}
func (t *Time) UnmarshalXML(e *xml.Decoder, ee xml.StartElement) error {
b, err := e.Token()
if err != nil {
+27 -1
View File
@@ -1,6 +1,8 @@
package _189_tv
import (
"encoding/json"
"encoding/xml"
"testing"
"time"
)
@@ -17,11 +19,13 @@ func TestTimeUnmarshal(t *testing.T) {
{"new format no tz", `"Aug 11, 2026, 10:37:18 PM"`, time.Date(2026, 8, 11, 22, 37, 18, 0, time.FixedZone("", 8*3600))},
{"narrow no-break space (U+202F)", "\"Aug 12, 2026, 12:35:41\u202fAM +08\"", time.Date(2026, 8, 12, 0, 35, 41, 0, time.FixedZone("", 8*3600))},
{"no-break space (U+00A0)", "\"Aug 12, 2026, 12:35:41\u00a0AM +08\"", time.Date(2026, 8, 12, 0, 35, 41, 0, time.FixedZone("", 8*3600))},
{"JSON escaped narrow space", `"Sep 4, 2026, 10:59:33\u202fPM +08"`, time.Date(2026, 9, 4, 22, 59, 33, 0, time.FixedZone("", 8*3600))},
{"JSON escaped space without timezone", `"Sep 4, 2026, 12:59:33\u00a0AM"`, time.Date(2026, 9, 4, 0, 59, 33, 0, time.FixedZone("", 8*3600))},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var tm Time
if err := tm.Unmarshal([]byte(tt.input)); err != nil {
if err := json.Unmarshal([]byte(tt.input), &tm); err != nil {
t.Fatalf("Unmarshal(%s) error: %v", tt.input, err)
}
if !tt.want.Equal(time.Time(tm)) {
@@ -37,3 +41,25 @@ func TestTimeUnmarshalRejectsInvalid(t *testing.T) {
t.Fatal("Unmarshal accepted an invalid time")
}
}
func TestTimeUnmarshalJSONRejectsInvalid(t *testing.T) {
for _, input := range []string{`"invalid"`, `123`, `null`, `"Sep 4, 2026, 10:59:33\uZZZZPM +08"`} {
t.Run(input, func(t *testing.T) {
var tm Time
if err := tm.UnmarshalJSON([]byte(input)); err == nil {
t.Fatalf("UnmarshalJSON(%s) accepted invalid input", input)
}
})
}
}
func TestTimeUnmarshalXML(t *testing.T) {
var tm Time
if err := xml.Unmarshal([]byte(`<time>Sep 4, 2026, 10:59:33&#x202f;PM +08</time>`), &tm); err != nil {
t.Fatal(err)
}
want := time.Date(2026, 9, 4, 22, 59, 33, 0, time.FixedZone("", 8*3600))
if !want.Equal(time.Time(tm)) {
t.Fatalf("UnmarshalXML = %v, want %v", time.Time(tm), want)
}
}
+8 -1
View File
@@ -9,6 +9,7 @@ import (
"crypto/sha1"
"crypto/x509"
"encoding/hex"
"encoding/json"
"encoding/pem"
"encoding/xml"
"fmt"
@@ -102,7 +103,13 @@ func MustParseTime(str string) *time.Time {
type Time time.Time
func (t *Time) UnmarshalJSON(b []byte) error { return t.Unmarshal(b) }
func (t *Time) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
return t.Unmarshal([]byte(s))
}
func (t *Time) UnmarshalXML(e *xml.Decoder, ee xml.StartElement) error {
b, err := e.Token()
if err != nil {

Some files were not shown because too many files have changed in this diff Show More