Skip to content

检查角色卡是否更新并下载

实现

这要求你有一个永久的下载地址,能以某种方式得知版本需要更新

可选做法是直接用 git 把角色卡存在某网站上(如果也想用请参考此处),因而:

  • 每次更新都会有一个独特的 commit 标识
  • 最新版本始终是 main
  • 网站提供了比较 commit 和 main 之间变动的方法

注意

需要注意的是,出于安全性考虑,浏览器默认情况下不会从某些网址下载文件,并在 f12 的控制台提示:

Access to fetch at 'https://<target website>' from origin 'https://<your website>' in frame has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

示例

点击查看示例
html
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>Version Checker</title>
  <style>
    body {
      background-color: white;
    }

    .version-container {
      text-align: center;
      margin-top: 50px;
    }

    #version-text {
      font-size: 18px;
      margin-bottom: 20px;
    }

    #check-button {
      padding: 10px 20px;
      font-size: 16px;
      cursor: pointer;
      background-color: #ffffff;
      border: 1px solid #cccccc;
      border-radius: 4px;
    }

    #check-button:hover {
      background-color: #f5f5f5;
    }

    .current-version {
      color: black;
    }

    .latest-version {
      color: green;
    }

    .update-available {
      color: red;
    }
  </style>
</head>

<body>
  <div class="version-container">
    <div id="version-text" class="current-version">
      <span id="commit-display">2b74088f</span>
    </div>
    <button id="check-button">检查更新</button>
  </div>

  <script>
    const config = {
      gitlab_domain: 'gitgud.io',
      project_path: 'SmilingFace/tavern_resource',
      file_path: '角色卡/妹妹请求你保护她/妹妹请求你保护她.png',
      current_commit: '2b74088f',
      target_commit: 'main'
    };

    // 初始显示当前 commit 值
    document.getElementById('commit-display').textContent = config.current_commit;

    let isChecking = false;

    document.getElementById('check-button').addEventListener('click', async function () {
      if (isChecking) return;
      isChecking = true;

      const button = this;
      const versionText = document.getElementById('commit-display');

      try {
        const hasUpdate = await gitlab.checkFileChanged(
          config.gitlab_domain,
          config.project_path,
          config.file_path,
          config.current_commit,
          config.target_commit
        );

        if (hasUpdate) {
          versionText.textContent = '有可用更新';
          versionText.parentElement.className = 'update-available';
          button.textContent = '点击进行更新';
          button.onclick = () => gitlab.confirmAndDownload(
            config.gitlab_domain,
            config.project_path,
            config.file_path,
            config.target_commit
          );
        } else {
          versionText.textContent = '已是最新版本';
          versionText.parentElement.className = 'latest-version';

          setTimeout(() => {
            versionText.textContent = config.current_commit;
            versionText.parentElement.className = 'current-version';
            isChecking = false;
          }, 3000);
        }
      } catch (error) {
        alert(`检查更新失败: ${error}`);
        isChecking = false;
      }
    });
  </script>
  <script>
    var download;

    (function (download_1) {
      function confirmToDownload(download_link, file_name) {
        if (!confirm(`你确定要从 ${download_link} 下载 ${file_name} 吗?`)) {
          return false;
        }
        return true;
      }
      download_1.confirmToDownload = confirmToDownload;
      async function download(response, file_name) {
        return response
          .then(response => response.blob())
          .then(blob => {
            const object_url = window.URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.style.display = 'none';
            a.href = object_url;
            a.download = `${file_name}`;
            document.body.appendChild(a);
            a.click();
            window.URL.revokeObjectURL(object_url);
          }).catch(reason => alert(`下载失败: ${reason}`));
      }
      download_1.download = download;
      function getBaseName(filepath) {
        return filepath.match(/[^\\/]+$/)?.[0] ?? filepath;
      }
      download_1.getBaseName = getBaseName;
    })(download || (download = {}));

    var gitlab;

    (function (gitlab) {
      function toGitlabApi(gitlab_domain, project_path) {
        return `https://${gitlab_domain}/api/v4/projects/${encodeURIComponent(project_path)}`;
      }
      gitlab.toGitlabApi = toGitlabApi;
      async function fetchRawFile(gitlab_domain, project_path, file_path, ref = 'main') {
        return globalThis.fetch(`${toGitlabApi(gitlab_domain, project_path)}/repository/files/${encodeURIComponent(file_path)}/raw?ref=${ref}`);
      }
      gitlab.fetchRawFile = fetchRawFile;
      async function confirmAndDownload(gitlab_domain, project_path, file_path, ref = 'main') {
        const file_name = download.getBaseName(file_path);
        if (!download.confirmToDownload(`${gitlab_domain}/${project_path}/${file_path}?ref=${ref}`, download.getBaseName(file_name))) {
          return;
        }
        return download.download(fetchRawFile(gitlab_domain, project_path, file_path, ref), file_name);
      }
      gitlab.confirmAndDownload = confirmAndDownload;
      async function checkFileChanged(gitlab_domain, project_path, file_path, from_commit, to_commit = 'main') {
        try {
          const response = await globalThis.fetch(`${toGitlabApi(gitlab_domain, project_path)}/repository/compare?from=${from_commit}&to=${to_commit}&path=${encodeURIComponent(file_path)}`);
          if (!response.ok) {
            throw new Error(`获取更新情况时出错: ${response.status}`);
          }
          const data = await response.json();
          return data.diffs.some((diff) => diff.old_path === file_path || diff.new_path === file_path);
        }
        catch (error) {
          console.error(`${error}`);
          throw error;
        }
      }
      gitlab.checkFileChanged = checkFileChanged;
    })(gitlab || (gitlab = {}));
  </script>
</body>

</html>
typescript
// <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
const config = {
  gitlab_domain: "gitgud.io",
  project_path: "SmilingFace/tavern_resource",
  file_path: "角色卡/妹妹请求你保护她/妹妹请求你保护她.png",
  from_commit: "eabaf643",
}

async function check() {
  return gitlab.checkFileChanged(config.gitlab_domain, config.project_path, config.file_path, config.from_commit);
}

async function update() {
  return gitlab.confirmAndDownload(config.gitlab_domain, config.project_path, config.file_path, config.from_commit);
}

namespace download {
  export function confirmToDownload(download_link: string, file_name: string): boolean {
    if (!confirm(`你确定要从 ${download_link} 下载 ${file_name} 吗?`)) {
      return false;
    }
    return true;
  }

  export async function download(response: Promise<Response>, file_name: string): Promise<void> {
    return response
      .then(response => response.blob())
      .then(blob => {
        const object_url = window.URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.style.display = 'none';
        a.href = object_url;
        a.download = `${file_name}`;
        document.body.appendChild(a);
        a.click();
        window.URL.revokeObjectURL(object_url);
      }).catch(reason => alert(`下载失败: ${reason}`));
  }

  export function getBaseName(filepath: string): string {
    return filepath.match(/[^\\/]+$/)?.[0] ?? filepath
  }
}

namespace gitlab {
  export function toGitlabApi(gitlab_domain: string, project_path: string) {
    return `https://${gitlab_domain}/api/v4/projects/${encodeURIComponent(project_path)}`
  }

  export async function fetchRawFile(
    gitlab_domain: string,
    project_path: string,
    file_path: string,
    ref: string = 'main'
  ): Promise<Response> {
    return fetch(`${toGitlabApi(gitlab_domain, project_path)}/repository/files/${encodeURIComponent(file_path)}/raw?ref=${ref}`);
  }

  export async function confirmAndDownload(
    gitlab_domain: string,
    project_path: string,
    file_path: string,
    ref: string = 'main'
  ): Promise<void> {
    const file_name = download.getBaseName(file_path);
    if (!download.confirmToDownload(`${gitlab_domain}/${project_path}/${file_path}?ref=${ref}`, download.getBaseName(file_name))) {
      return;
    }
    return download.download(fetchRawFile(gitlab_domain, project_path, file_path, ref), file_name);
  }

  export async function checkFileChanged(
    gitlab_domain: string,
    project_path: string,
    file_path: string,
    from_commit: string,
    to_commit: string = 'main'): Promise<boolean> {
    try {
      const response = await fetch(`${toGitlabApi(gitlab_domain, project_path)}/repository/compare?from=${from_commit}&to=${to_commit}&path=${encodeURIComponent(file_path)}`);
      if (!response.ok) {
        throw new Error(`获取更新情况时出错: ${response.status}`);
      }

      const data = await response.json();
      return data.diffs.some((diff: any) => diff.old_path === file_path || diff.new_path === file_path);
    } catch (error) {
      console.error(`${error}`);
      throw error;
    }
  }
}

作者:KAKAA, 青空莉想做舞台少女的狗