The-CodingSloth / haha-funny-leetcode-extension

MIT License
396 stars 75 forks source link

Interface with Leetcode API #35

Open walter-0 opened 10 months ago

walter-0 commented 10 months ago

From what I've seen so far, a lot of the issues here, like these

https://github.com/The-CodingSloth/haha-funny-leetcode-extension/issues/33 https://github.com/The-CodingSloth/haha-funny-leetcode-extension/issues/29 https://github.com/The-CodingSloth/haha-funny-leetcode-extension/issues/20 https://github.com/The-CodingSloth/haha-funny-leetcode-extension/issues/4 https://github.com/The-CodingSloth/haha-funny-leetcode-extension/issues/2

could be fixed by using an API instead of a web scraper. Leetcode has a public graphql endpoint but there is no official documentation on how to use it, however there are open source APIs that people have put together. I'm currently looking into a few different APIs to see if I can connect the extension directly to LC's data for problems and user submissions.

https://github.com/codingsnack/leetcode-api/ https://github.com/JacobLinCool/LeetCode-Query

The-CodingSloth commented 10 months ago

Someone left a YouTube comment to use their public endpoint, and I think it's a good idea for a lot of these features. If you can find a way to connect it into the extension that'd be awesome to not rely on this duct tapped web scraper.

walter-0 commented 10 months ago

Link to that endpoint?

The-CodingSloth commented 10 months ago

The person was talking about that same public graphql endpoint. I think that LeetCode Query repo could work. Maybe we could do something like this: If we don't have their leetcode username we can intercept the http graphql response from leetcode when they get redirected to a problem since in the response it contains the username and then we can save the username in storage so we don't have to intercept it anymore? It's what I was thinking, but definitely mess around with it haha

Tayomide commented 10 months ago

That will work! Here's the link to the Leetcode API https://leetcode.com/graphql and here's the GraphQL query to check if the user is a premium user or not

query globalData {
  userStatus {
    isPremium 
  }
}

However, this might be a privacy concern as you could get any user data displayed on the website using this method, LeetCode doesn't have any documentation for their API but it is public and does not require a token once you know the queries

I think it'll be okay if the data used is publicly accessible, but I have no idea honestly

The-CodingSloth commented 10 months ago

yeah that's what I was thinking too, trying my best to prevent going through personal details as much as possible, but I think as long as we use public information like their username only, and since these queries are public (for now) hopefully we're fine lol

walter-0 commented 10 months ago

I worked on this a bit over the weekend and I got stuck here. I'm adding an options page with an input for the user to put in their session cookie so the extension can know which problems they've solved, but I'm not able to get anything from credential.init.

I'm using this library https://github.com/JacobLinCool/LeetCode-Query/

import { Credential, LeetCode } from "leetcode-query";

import { Storage } from "@plasmohq/storage";
import { useStorage } from "@plasmohq/storage/hook";

const storage = new Storage();

const Options: React.FC = () => {
  const [leetcodeSessionCookie, setLeetcodeSessionCookie] =
    useStorage<string>("");

  const handleSaveUserToken = async () => {
    const leetcode = new LeetCode();
    const credential = new Credential();
    await credential.init(
      "YOUR_LEETCODE_SESSION_VALUE"
    );

    await storage.set("credential", credential);
    await storage
      .get("credential")
      .then((credential) =>
        console.log(credential ? credential : "no credential")
      );
  };

  return (
    <div>
      <h1>Options</h1>
      <h2>Leetcode Session Cookie</h2>

      <textarea
        placeholder="LEETCODE_SESSION"
        onChange={(e) => setLeetcodeSessionCookie(e.target.value)}>
        {leetcodeSessionCookie}
      </textarea>
      <button onClick={handleSaveUserToken}>Save</button>
    </div>
  );
};

export default Options;
The-CodingSloth commented 10 months ago

whoops my bad about that one. Do we need their credentials for this: AcSubmissionNum. And is it session cookie or csrf token?

Tayomide commented 10 months ago

That's the thing

I don't know how to say this without sounding like a hacker, but the extension can get anything displayed on Leetcode's website, including their solved problems whether they are a premium subscriber or not, and more personal details

We'll have to be really careful when implementing it, and maybe have a consent form and promise the project will remain open source for anyone to see what data we use. We also can not store anything in a third-party database like MongoDB etc so we'll have to stick to the browser storage

I could walk you through getting the query needed for certain features from Leetcode, As long as it works on your laptop, it should work for the next person

You can also use this extension "GraphQL Playground for Chrome" to try out the queries you find

I don't know enough about making GraphQL APIs to know if this is from Leetcode's end or if the browser takes a request made from an extension as a user's request, but I tried it earlier, and I was able to get every data on my two accounts using the extension

The-CodingSloth commented 10 months ago

hm that's interesting. Does it still work even if we modify the credential header in the fetch request? It could be possible that when we do this request, since it's on the user's browser, it already has access to their tokens which could be an issue lol.

Tayomide commented 10 months ago

That could be it. I know Leetcode's API doesn't have a token. I initially thought it was open source but queries like

query globalData {
  userStatus {
    isPremium 
    ...
  }
}

work without getting any information about who the user is, so it's definitely not open source like GItHub's, Maybe they check the cookies that come with the request like you said, that might be it

I'm more into frontend, so I don't know a lot about it, but it definitely shouldn't know I'm the one making the request if it's open source and does not ask for any ID or username

The-CodingSloth commented 10 months ago

Yup just tested this query out:

 const testQuery = ` query globalData {
      userStatus {
        userId
        isSignedIn
        isPremium
        isVerified
        username
      }
    }`
    const testBody = {
      query: testQuery
    }
 await fetch("https://leetcode.com/graphql", {
      method: "POST",
      body: JSON.stringify(testBody),
      headers: {
        "Content-Type": "application/json"
      },
      credentials: "omit" // This should be outside the headers object, ensures no credentials are sent with the fetch request.
    })

and on the network tab on the extension it doesn't show any personal information anymore. So it's probably best to have that credential property for any queries we make.

Tayomide commented 10 months ago

Nice! Got it! I just moved the functionality over to the settings drawer, I will make a PR now and you could add it to the requests!