绳网跨域助手

none

// ==UserScript==
// @name         绳网跨域助手
// @namespace    http://tampermonkey.net/
// @version      1.7.1
// @description  none
// @author       claxmo
// @license      MIT
// @run-at       document-start
// @match        http://localhost:*/inter-knot/*
// @match        https://claxmo.github.io/inter-knot/*
// @connect      github.com
// @connect      api.github.com
// @connect      private-user-images.githubusercontent.com
// @grant        unsafeWindow
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function () {
    'use strict';
    const CLIENT_ID = "Ov23lifQzV3VA2p4vEmL";
    const CLIENT_SECRET = "b9cab7872c74d1958582676e4393bc112cacee24";
    const OWNER = "claxmo";
    const REPO = "inter-knot";
     const TIMEOUT = 5000;
    let accessToken = localStorage.getItem('access_token');
    unsafeWindow.version = "1.7.1";

    const request = async (method, url, data = null, timeout = TIMEOUT) => {
         return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method,
                url,
                headers: {
                    "Authorization": `Bearer ${accessToken}`,
                    "Accept": "application/json"
                },
                ...(data && { data: JSON.stringify(data) }),
                timeout,
                onload: (res) => {
                    if (res.status === 401){
                        localStorage.removeItem("access_token");
                        accessToken = null;
                        return authLogin();
                    }else{
                        resolve(JSON.parse(res.responseText));
                    }
                },
                ontimeout: () => reject(new Error('request timeout')),
                onerror: () => reject(new Error('request error'))
            });
        });
    };

    const getAccessToken = async (code, timeout = TIMEOUT) => {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "POST",
                url: "https://github.com/login/oauth/access_token",
                headers: {
                    "Content-Type": "application/json",
                    "Accept": "application/json"
                },
                data: JSON.stringify({
                    client_id: CLIENT_ID,
                    client_secret: CLIENT_SECRET,
                    code,
                }),
                timeout,
                onload: (res) => {
                    resolve(JSON.parse(res.responseText).access_token);
                },
                onerror: () => reject(new Error('get access_token error')),
                ontimeout: () => reject(new Error ('get access_token timeout')),
            });
        });
    };

    const authLogin = () => {
        window.location.href =`https://github.com/login/oauth/authorize?client_id=${CLIENT_ID}&scope=public_repo`;
    };

    const graphql = async (query, variables) => {
        return await request("POST","https://api.github.com/graphql",{query,variables});
    };

    unsafeWindow.getBlob = async (url) => {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: url,
                responseType: 'blob',
                onload: (res) => {
                    resolve(res.response);
                },
                onerror: () => reject(new Error('get blob error: ' + url)),
            });
        });
    };

    unsafeWindow.getUserProfile = async () => {
        return await request("GET","https://api.github.com/user");
    };

    unsafeWindow.searchDiscussion = async (query = '', cursor = null) => {
        return (await graphql(
          `query($query: String!, $cursor: String ) { search(type: DISCUSSION, query: $query, first: 20, after: $cursor ) { pageInfo { endCursor hasNextPage } nodes { ... on Discussion { id number title body bodyHTML bodyText author { login avatarUrl } category { id name emoji } createdAt updatedAt url comments { totalCount } locked viewerCanDelete viewerDidAuthor reactionGroups { content viewerHasReacted users { totalCount } } reactions { totalCount } upvoteCount } } } }`,
          { query: `repo:${OWNER}/${REPO} ${query}`.trim(), cursor }
        )
      ).data.search;
    };

    unsafeWindow.getDiscussionComments = async (discussionId, cursor = null, isAsc = true) => {
        return (await graphql(
          `query($id: ID!, $cursor: String) { node(id: $id) { ... on Discussion { comments(${isAsc ? 'first' : 'last'}: 20, ${isAsc ? 'after' : 'before'}: $cursor) { pageInfo { startCursor hasPreviousPage, endCursor, hasNextPage } totalCount nodes { author { login avatarUrl } authorAssociation body bodyHTML bodyText createdAt id updatedAt url viewerCanDelete viewerDidAuthor reactionGroups { content viewerHasReacted users { totalCount } } reactions { totalCount } upvoteCount } } } } }`,
          { id: discussionId, cursor }
        )
      ).data.node.comments;
    };

    unsafeWindow.deleteDiscussion = async (discussionId) => {
        return (await graphql(
            `mutation($discussionId: ID!) { deleteDiscussion(input: { id: $discussionId } ) { clientMutationId } }`,
            { discussionId }
        )
      ).data.deleteDiscussion;
    };

    unsafeWindow.deleteDiscussionComment = async (commentId) => {
        return (await graphql(
            `mutation($commentId: ID!) { deleteDiscussionComment(input: { id: $commentId } ) { clientMutationId } }`,
            { commentId }
        )
      ).data.deleteDiscussionComment;
    };

    unsafeWindow.addDiscussionComment = async (discussionId, body) => {
        return (await graphql(
            `mutation($discussionId: ID!, $body: String!) { addDiscussionComment(input: {discussionId: $discussionId, body: $body}) { comment { author { login avatarUrl } authorAssociation body bodyHTML bodyText createdAt id updatedAt url viewerCanDelete viewerDidAuthor } } } }`,
            { discussionId, body }
        )
      ).data.addDiscussionComment.comment;
    };

    unsafeWindow.getDiscussion = async (number) => {
        return (await graphql(
            `query getDiscussion($number: Int!) { repository(owner: "${OWNER}", name: "${REPO}") { discussion(number: $number ) { id number title body bodyHTML bodyText author { login avatarUrl } category { id name emoji } createdAt updatedAt url comments { totalCount } locked viewerCanDelete viewerDidAuthor reactionGroups { content viewerHasReacted users { totalCount } } reactions { totalCount } upvoteCount } } }`,
            { number }
        )
      ).data.repository.discussion;
    };

    unsafeWindow.addReaction = async (subjectId, content) => {
        return (await graphql(
            `mutation($subjectId: ID!, $content: ReactionContent!) { addReaction(input: {subjectId: $subjectId, content: $content}) {reaction { content } subject { id } } }`,
            { subjectId, content }
        )
      ).data.addReaction.reaction;
    };

    unsafeWindow.removeReaction = async (subjectId, content) => {
        return (await graphql(
            `mutation($subjectId: ID!, $content: ReactionContent!) { removeReaction(input: {subjectId: $subjectId, content: $content}) {reaction { content } subject { id } } }`,
            { subjectId, content }
        )
      ).data.removeReaction.reaction;
    };

    const urlParams = new URLSearchParams(window.location.search);
    const code = urlParams.get("code");
    if (code) {
        history.replaceState(null, '', window.location.origin + window.location.pathname);
        getAccessToken(code).then((token) => {
            accessToken = token;
            localStorage.setItem('access_token', accessToken);
            // console.log("Access Token:",accessToken);
        });
    }else if(!accessToken){
        authLogin();
    }
})();