const { ApolloClient, InMemoryCache, createHttpLink, from } = require('@apollo/client') const { RetryLink } = require('@apollo/client/link/retry') const { ApolloError } = require('@apollo/client/errors') const httpLink = createHttpLink({ uri: 'https://api.github.com/graphql', headers: { authorization: `Bearer ${process.env.GRAPHQL_TOKEN}`, } }) const retryLink = new RetryLink({ attempts: (count, _operation, /** @type {ApolloError} */ error) => { return count < 3 }, delay: (_count, operation, _error) => { const context = operation.getContext() /** @type {Response} */ const response = context.response const xRatelimitRemaining = parseInt(response.headers.get('x-ratelimit-remaining')) if (!isNaN(xRatelimitRemaining) && xRatelimitRemaining > 0) { console.log('[NetworkError] retry after 1 second') return 1000 } let retryAfter = parseInt(response.headers.get('retry-after')) const xRateLimitReset = parseInt(response.headers.get('x-ratelimit-reset')) if (isNaN(retryAfter) && isNaN(xRateLimitReset)) { console.log('[NetworkError] response header missing...') console.log('[NetworkError] retry after 1 min') return 60 * 1000 } if (isNaN(retryAfter)) { const retryAfter = (xRateLimitReset * 1000) - Date.now() console.log(`[NetworkError] retry after ${retryAfter} ms`) } return retryAfter * 1000 }, }) /** @type {import('@apollo/client').DefaultOptions} */ const defaultOptions = { watchQuery: { fetchPolicy: 'no-cache', }, query: { fetchPolicy: 'no-cache', } } const apolloClient = new ApolloClient({ link: from([retryLink, httpLink]), cache: new InMemoryCache(), defaultOptions: defaultOptions, }) const fetchFromGitHub = async (graphQLQuery) => { if (process.env.GRAPHQL_TOKEN === undefined) { throw new Error('token is undefined') } return apolloClient.query({ query: graphQLQuery, }).then((response) => { return response }) } const REGEX_PUBLIC_IMAGES = /https:\/\/github\.com\/[a-zA-Z0-9-]+\/[\w\-.]+\/assets\/\d+\/([0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12})/g const REGEX_PUBLIC_IMAGES2 = /https:\/\/github\.com\/user-attachments\/assets\/([0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12})/g const replacePrivateImage = (markdown, html) => { const publicMatches = new Map() for (const match of markdown.matchAll(REGEX_PUBLIC_IMAGES)) { publicMatches.set(match[0], match[1]) } for (const match of markdown.matchAll(REGEX_PUBLIC_IMAGES2)) { publicMatches.set(match[0], match[1]) } for (const match of publicMatches) { const regexPrivateImages = new RegExp(`https:\\/\\/private-user-images\\.githubusercontent\\.com\\/\\d+\\/\\d+-${match[1]}\\..*?(?=")`, 'g') html = html.replaceAll(regexPrivateImages, match[0]) } return html } exports.fetchFromGithub = fetchFromGitHub exports.replacePrivateImage = replacePrivateImage