| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| const fs = require("fs"); |
| const https = require("https"); |
| |
| const teamSrc = "src/pages/team/data/team.json"; |
| const avatarFile = "src/pages/team/data/github-avatar.json"; |
| const avatarSize = 100; |
| const authorsFile = "blog/authors.json"; |
| |
| /** |
| * Generates a random delay between min and max milliseconds |
| * @param {number} min - Minimum delay in milliseconds |
| * @param {number} max - Maximum delay in milliseconds |
| * @returns {number} Random delay in milliseconds |
| */ |
| function getRandomDelay(min, max) { |
| return Math.floor(Math.random() * (max - min + 1)) + min; |
| } |
| |
| /** |
| * Fetches avatar image from GitHub and converts it to base64 |
| * @param {string} githubId - GitHub ID |
| * @returns {Promise<string>} Base64 encoded avatar image |
| */ |
| function fetchAvatarAsBase64(githubId) { |
| return new Promise((resolve, reject) => { |
| const avatarUrl = `https://avatars.githubusercontent.com/u/${githubId}?v=4&s=${avatarSize}`; |
| |
| https |
| .get(avatarUrl, (response) => { |
| // Check if request was successful |
| if (response.statusCode !== 200) { |
| reject(new Error(`Failed to fetch avatar from ${avatarUrl}: ${response.statusCode}`)); |
| return; |
| } |
| |
| const chunks = []; |
| |
| // Collect data chunks |
| response.on("data", (chunk) => { |
| chunks.push(chunk); |
| }); |
| |
| // Convert to base64 when complete |
| response.on("end", () => { |
| const buffer = Buffer.concat(chunks); |
| const base64 = buffer.toString("base64"); |
| resolve(base64); |
| }); |
| }) |
| .setTimeout(10000, () => { |
| reject(new Error(`Failed to fetch avatar ${avatarUrl}: timed out`)); |
| }) |
| .on("error", (error) => { |
| reject(error); |
| }); |
| }); |
| } |
| |
| /** |
| * Get GitHub name from URL |
| * @param {string} url - GitHub URL |
| * @returns {string} GitHub name |
| */ |
| function getGitName(url) { |
| return url.replace('https://github.com/', ''); |
| } |
| |
| /** |
| * Processes a list of githubIds and adds avatar_base64 property |
| * @param {Array} ids - Array of id |
| * @returns {Promise<Array>} Array of avatar_base64 |
| */ |
| async function processAvatars(ids) { |
| const processedArray = []; |
| |
| for (let i = 0; i < ids.length; i++) { |
| const _id = ids[i]; |
| |
| try { |
| console.log(`-- Fetching avatar for ${_id} ... [${i + 1}/${ids.length}]`); |
| |
| // Fetch avatar and convert to base64 |
| const avatarBase64 = await fetchAvatarAsBase64(_id); |
| |
| processedArray.push({ |
| id: _id, |
| avatar_base64: avatarBase64 |
| }); |
| console.log(`ā Successfully processed ${_id}`); |
| } catch (error) { |
| console.error(`ā Error processing ${_id}: ${error.message}`); |
| } |
| |
| // Add random delay between 100-2000 millisecond before next request (except for the last member) |
| if (i < ids.length - 1) { |
| await new Promise((resolve) => setTimeout(resolve, getRandomDelay(100, 2000))); |
| } |
| } |
| return processedArray; |
| } |
| |
| /** |
| * Processes blog authors data and adds avatar_base64 property |
| * @param {Object} teamData - Team data |
| * @param {Array} avatars - Array of avatars |
| * @returns {Promise<Object>} Blog authors data |
| */ |
| async function processBlogAuthors(teamData, avatars) { |
| const blogAuthorsMapPath = {}; |
| (teamData.pmc.concat(teamData.committer) || []).forEach((m) => { |
| const gitName = getGitName(m.gitUrl); |
| const avatarObj = avatars.find((item) => item.id === m.githubId); |
| blogAuthorsMapPath[gitName] = { |
| "name": m.name, |
| "url": m.gitUrl, |
| "image_url": "data:image/png;base64," + avatarObj.avatar_base64, |
| "socials": { |
| "github": gitName |
| } |
| } |
| }); |
| |
| return blogAuthorsMapPath; |
| } |
| |
| /** |
| * Main function |
| */ |
| async function main() { |
| try { |
| const uniqueGithubIdsSet = new Set(); |
| |
| // 1. Read and parse team |
| console.log(`==> Reading ${teamSrc} file`); |
| const teamSrcData = JSON.parse(fs.readFileSync(teamSrc, "utf8")); |
| |
| // PMC && Committer |
| (teamSrcData.pmc.concat(teamSrcData.committer) || []).forEach((d) => { |
| if (d.githubId) { |
| uniqueGithubIdsSet.add(d.githubId); |
| } |
| }); |
| |
| const uniqueGithubArray = Array.from(uniqueGithubIdsSet); |
| |
| console.log("\n==> Processing avatars"); |
| const avatarsArray = await processAvatars(uniqueGithubArray); |
| |
| // 2. Write files |
| console.log(`\n==> Write to ${avatarFile}`); |
| fs.writeFileSync(avatarFile, JSON.stringify(avatarsArray, null, 2)); |
| |
| // 3. Blog authors |
| const blogAuthorsMapPaths = await processBlogAuthors(teamSrcData, avatarsArray); |
| console.log(`\n==> Write to ${authorsFile}`); |
| fs.writeFileSync(authorsFile, JSON.stringify(blogAuthorsMapPaths, null, 2)); |
| |
| console.log("\nā Done!"); |
| } catch (error) { |
| console.error("Error:", error.message); |
| process.exit(1); |
| } |
| } |
| |
| main(); |