banner
AcoFork

AcoFork

LOVETOLOVE

Cloudflare connects to Github! Code repository as a cloud storage!

WARNING: Abusing code repositories (like Github/GitLab) may risk account suspension!#

Basic Principle#

Pages are deployed through Git repositories, and Workers access your Github repository directory open query API via API Key. Pages read this API to display files.

Example#

image

Create a Github Repository#

  1. Not teaching, it's simple and stylish. There are more tutorials online than I have hair.
  2. Create a file folder in the root directory; subsequent file uploads and downloads will be in this folder.
  3. Create index.html in the root directory and fill in the code:
    Change the Body's query API URL to yours:
    const response = await fetch('https://file-up.afo.im/list');
    You currently don't know what to change it to, keep reading.
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>File List</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      padding: 20px;
    }
    ul {
      list-style-type: none;
      padding: 0;
    }
    li {
      margin: 5px 0;
    }
    a {
      text-decoration: none;
      color: #007bff;
    }
    a:hover {
      text-decoration: underline;
    }
  </style>
</head>
<body>
  <h1>File List</h1>
  <ul id="file-list"></ul>

  <script>
    async function fetchFileList() {
      try {
        const response = await fetch('https://file-up.afo.im/list');
        if (!response.ok) {
          throw new Error('Failed to fetch file list');
        }
        const fileList = await response.text();
        const files = fileList.split('\n').filter(file => file.trim() !== '');
        displayFiles(files);
      } catch (error) {
        console.error('Error fetching file list:', error);
        document.getElementById('file-list').innerHTML = '<li>Error fetching file list</li>';
      }
    }

    function displayFiles(files) {
      const fileListElement = document.getElementById('file-list');
      fileListElement.innerHTML = files.map(file => 
        `<li><a href="/file/${encodeURIComponent(file)}">${file}</a></li>`
      ).join('');
    }

    fetchFileList();
  </script>
</body>
</html>

Create Workers, Open API Query and Upload Endpoints#

  1. Not teaching, it's simple and stylish. See Create Workers, Connect R2 don't look at the part about connecting R2.
  2. Change Workers code:
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
  const url = new URL(request.url)
  const path = url.pathname

  if (path === '/') {
    return addCORSHeaders(showLoginForm())
  } else if (path === '/login') {
    return addCORSHeaders(await handleLogin(request))
  } else if (path === '/upload') {
    return addCORSHeaders(await handleUpload(request))
  } else if (path === '/check-config') {
    return addCORSHeaders(await checkConfig())
  } else if (path === '/list') {
    return addCORSHeaders(await listFiles()) // Allow direct access to /list without authorization
  } else {
    return addCORSHeaders(new Response('Not Found', { status: 404 }))
  }
}

function showLoginForm() {
  const html = `
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Login</title>
    </head>
    <body>
      <h1>Login</h1>
      <form action="/login" method="POST">
        <input type="password" name="password" placeholder="Enter password" required>
        <button type="submit">Login</button>
      </form>
      <p><a href="/check-config">Check server configuration</a></p>
    </body>
    </html>
  `
  return new Response(html, {
    headers: { 'Content-Type': 'text/html' }
  })
}

async function handleLogin(request) {
  const formData = await request.formData()
  const password = formData.get('password')

  if (password === PASSWORD) {
    return showUploadForm()
  } else {
    return new Response('Invalid password', { status: 401 })
  }
}

function showUploadForm() {
  const html = `
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>File Upload</title>
    </head>
    <body>
      <h1>File Upload</h1>
      <form action="/upload" method="POST" enctype="multipart/form-data">
        <input type="file" name="file" required>
        <button type="submit">Upload</button>
      </form>
    </body>
    </html>
  `
  return new Response(html, {
    headers: { 'Content-Type': 'text/html' }
  })
}

async function handleUpload(request) {
  try {
    const formData = await request.formData()
    const file = formData.get('file')

    if (!file) {
      return new Response('No file uploaded', { status: 400 })
    }

    const content = await file.arrayBuffer()
    const encodedContent = btoa(String.fromCharCode.apply(null, new Uint8Array(content)))

    const githubResponse = await fetch(`https://api.github.com/repos/${GITHUB_REPO}/contents/file/${file.name}`, {
      method: 'PUT',
      headers: {
        'Authorization': `token ${GITHUB_TOKEN}`,
        'Content-Type': 'application/json',
        'User-Agent': 'Cloudflare Worker'
      },
      body: JSON.stringify({
        message: `Upload ${file.name}`,
        content: encodedContent
      })
    })

    if (githubResponse.ok) {
      return new Response('File uploaded successfully', { status: 200 })
    } else {
      const errorData = await githubResponse.text()
      console.error('GitHub API Error:', errorData)
      return new Response(`Failed to upload file: ${errorData}`, { status: 500 })
    }
  } catch (error) {
    console.error('Upload error:', error)
    return new Response(`Error during upload: ${error.message}`, { status: 500 })
  }
}

async function listFiles() {
  try {
    const githubResponse = await fetch(`https://api.github.com/repos/${GITHUB_REPO}/contents/file`, {
      headers: {
        'Authorization': `token ${GITHUB_TOKEN}`,
        'User-Agent': 'Cloudflare Worker'
      }
    })

    if (!githubResponse.ok) {
      const errorData = await githubResponse.text()
      console.error('GitHub API Error:', errorData)
      return new Response(`Failed to fetch file list: ${errorData}`, { status: 500 })
    }

    const files = await githubResponse.json()

    // Convert file names to plain text, one per line
    const fileNames = files.map(file => file.name).join('\n')

    return new Response(fileNames, {
      headers: { 'Content-Type': 'text/plain' }
    })
  } catch (error) {
    console.error('Error fetching files:', error)
    return new Response(`Error during fetching files: ${error.message}`, { status: 500 })
  }
}

async function checkConfig() {
  let configStatus = 'All configurations are set correctly.'

  if (!PASSWORD) {
    configStatus = 'ERROR: PASSWORD is not set.'
  } else if (!GITHUB_TOKEN) {
    configStatus = 'ERROR: GITHUB_TOKEN is not set.'
  } else if (!GITHUB_REPO) {
    configStatus = 'ERROR: GITHUB_REPO is not set.'
  } else {
    try {
      const response = await fetch(`https://api.github.com/repos/${GITHUB_REPO}`, {
        headers: {
          'Authorization': `token ${GITHUB_TOKEN}`,
          'User-Agent': 'Cloudflare Worker'
        }
      })

      const responseText = await response.text()

      if (!response.ok) {
        try {
          const data = JSON.parse(responseText)
          configStatus = `ERROR: GitHub API returned an error: ${data.message}`
        } catch (jsonError) {
          configStatus = `ERROR: GitHub API returned a non-JSON response. Status: ${response.status}, Body: ${responseText.substring(0, 100)}...`
        }
      } else {
        try {
          JSON.parse(responseText)
          configStatus += ' GitHub connection successful.'
        } catch (jsonError) {
          configStatus = `WARNING: GitHub API returned a non-JSON response, but the connection was successful. Status: ${response.status}, Body: ${responseText.substring(0, 100)}...`
        }
      }
    } catch (error) {
      configStatus = `ERROR: Failed to connect to GitHub API: ${error.message}`
    }
  }

  const html = `
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Configuration Check</title>
    </head>
    <body>
      <h1>Configuration Check</h1>
      <p>${configStatus}</p>
      <a href="/">Back to Login</a>
    </body>
    </html>
  `

  return new Response(html, {
    status: 200,
    headers: { 'Content-Type': 'text/html' }
  })
}

// Add CORS headers
function addCORSHeaders(response) {
  const headers = new Headers(response.headers)
  headers.set('Access-Control-Allow-Origin', '*')
  headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, OPTIONS')
  headers.set('Access-Control-Allow-Headers', 'Content-Type')

  return new Response(response.body, {
    status: response.status,
    statusText: response.statusText,
    headers
  })
}
  1. Add variables for Workers
    GITHUB_REPO format: GithubUsername/GithubRepoName
    GITHUB_TOKEN format: YourGithub API Key. Needs repository modification permissions.
    PASSWORD is the password for accessing the upload endpoint, set by yourself (using Git upload is more recommended).
    image

  2. Access your custom domain's /list to see if the query API is working properly and can list files in the file folder under the repository, like

image

  1. Fill in your URL in the third step of Create Github Repository

const response = await fetch('https://file-up.afo.im/list');
Replace https://file-up.afo.im/list with yours.

Create Pages, Connect Git Repository#

  1. Not teaching, it's simple and stylish. See Teach you to build your first website! Get started without a server! Note to use Git repository deployment instead of local deployment.

  2. Cloudflare Pages will automatically redeploy when there are commits, achieving automatic updates of new files.

Access#

image

  • Written in a hurry, if you have questions, feel free to comment.
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.