Unverified Commit 463bf1ea authored by TheTechRobo's avatar TheTechRobo Committed by GitHub
Browse files

Merge pull request #21 from TheTechRobo/apiselection

parents 805fcb20 7f10696b
Loading
Loading
Loading
Loading
+25 −9
Original line number Diff line number Diff line
@@ -6,32 +6,48 @@ from flask import Flask, render_template, request, abort
app = Flask(__name__)

@app.route("/find/<id>")
async def youtube(id):
    if not re.match(r"^[A-Za-z0-9_-]{10}[AEIMQUYcgkosw048]$", id):
        return {"status": "bad.id", "true": True, "id": None}, 400
async def youtubev2(id):
    return (await lostmediafinder.YouTubeResponse.generateAsync(id)).json()

async def wrapperYT(id):
    return await lostmediafinder.YouTubeResponse.generateAsync(id)

@app.route("/api/v<int:v>/<site>/<id>")
@app.route("/api/v<int:v>/<id>")
async def findapi(v, id, site="query"):
async def youtube(v, id, site="youtube"):
    if v == 1:
        return "This API version is no longer supported.", 410
    if v != 2:
    if v not in (2, 3):
        return "Unrecognised API version", 404
    if site == "query":
        site = request.args.get("site") or abort(400)
    if site == "youtube":
        return await youtube(id)
        return (await wrapperYT(id)).coerce_to_api_version(v).json()
    return "Unrecognised site", 404

@app.route("/")
async def index():
    return render_template("init.html")

def parseChangelog(changelog):
    parsed = {}
    for i in changelog.split("API VERSION "):
        restOfLine = i.split("\n")[0]
        others = i.split("\n")[1:]
        parsed[restOfLine] = others
    return parsed

@app.route("/api")
async def api():
    responseDocstring = lostmediafinder.YouTubeResponse.__doc__
    serviceDocstring = lostmediafinder.Service.__doc__
    changelog = [{}, {}]
    rChangelog = responseDocstring.split("=Changelog=")
    sChangelog = serviceDocstring.split("=Changelog=")
    responseDocstring = rChangelog[0]
    serviceDocstring = sChangelog[0]
    if len(rChangelog) > 1:
        changelog[0] = parseChangelog(rChangelog[1].strip())
    if len(sChangelog) > 1:
        changelog[1] = parseChangelog(sChangelog[1].strip())
    # TODO: Parse that
    # This works fine for now tho
    return render_template("api.html", fields=responseDocstring, services=serviceDocstring, type=type)
    return render_template("api.html", fields=responseDocstring, services=serviceDocstring, changelog=changelog)
+1 −1
Original line number Diff line number Diff line
@@ -159,7 +159,7 @@ class Filmot(YouTubeService):
        capcount = int(archived)
        available = f"https://filmot.com/video/{id}" if archived else None
        return cls(
                archived=archived, capcount=capcount, error=False,
                archived=archived, capcount=capcount,
                lastupdated=lastupdated, name=cls.getName(), note="",
                rawraw=rawraw, metaonly=True, comments=False,
                available=available
+46 −8
Original line number Diff line number Diff line
@@ -37,15 +37,19 @@ class Service(JSONDataclass):

    Attributes:
        archived (bool): Whether the video is archived or not.
        available (int): A link to the archived material if it can be produced; null otherwise.
        available (Optional[str]): A link to the archived material if it can be produced; null otherwise.
        capcount (int): The number of captures. Currently deprecated - the capture count sent may or may not be the true number of captures. However, it will always be a positive non-zero number if the video is archived.
        error (bool): Whether or not the request failed.
        error (Optional[str]): An error message if an error was encountered; otherwise, null.
        lastupdated (int): The timestamp the data was retrieved from the server. Used internally to expire cache entries.
        name (str): The name of the service. Used in the UI.
        note (str): A footnote about the service. This could be different depending on conditions. For example, the Internet Archive has an extra passage if the item is dark. Used in the UI.
        rawraw (Any): The data used to check whether the video is archived on that particular service. For example, for GhostArchive, it would be the HTTP status code.
        metaonly (bool): True if only the metadata is archived. This value should not be relied on!
        comments (bool): True if the comments are archived. This value should not be relied on!

        =Changelog=
        API VERSION 2 -> 3:
            - The `error` attribute is now no longer a boolean; it contains an error message if an error occured and null if no error occured
    """
    archived: bool
    capcount: int
@@ -58,7 +62,7 @@ class Service(JSONDataclass):

    available: typing.Optional[str] = None
    suppl: str = ""
    error: bool = False
    error: bool = None

    @staticmethod
    def _getFromConfig(key, key1=None):
@@ -89,11 +93,11 @@ class Service(JSONDataclass):
        except Exception as ename: # pylint: disable=broad-except
            note = f"An error occured while retrieving data from {cls.getName()}."
            print(ename)
            rawraw = f"{type(ename)}{repr(ename)}" if includeRaw else None
            rawraw = f"{type(ename)}: {repr(ename)}"
            return cls(
                    archived=False, capcount=0, error=True,
                    archived=False, capcount=0, error=rawraw,
                    lastupdated=time.time(), name=cls.getName(), note=note,
                    rawraw=rawraw, metaonly=False, comments=False,
                    rawraw=None, metaonly=False, comments=False,
                    available=None
            )

@@ -134,12 +138,46 @@ class YouTubeResponse(JSONDataclass):
        id (str): The interpreted video ID.
        status (str): bad.id if invalid ID.
        keys (list[YouTubeService]): An array with all the server responses. THIS IS DIFFERENT THAN BEFORE! Before, this would be an array of strings. You'd use the strings as keys. Now, this array has the data directly!
        api_version (int): The API version. Currently set to %s. Breaking API changes are made by incrementing this. There may be a way to request a specific version in the future.
        api_version (int): The API version. Breaking API changes are made by incrementing this.
    """
    id: str
    status: str
    keys: list[YouTubeService]
    api_version: int = 2
    api_version: int = 3

    def coerce_to_api_version(selfNEW, target):
        """
        Downgrades the API version to one of your choice, then returns it.

        Arguments:
            target (int): The target API version. Must be lower than self.api_version
        """
        import copy
        self = copy.deepcopy(selfNEW)
        currentApiVersion = self.api_version
        if currentApiVersion < target:
            raise ValueError("cannot upgrade api version")
        while self.api_version != target:
            fname = f"_convert_v{self.api_version}_to_v{self.api_version-1}"
            if not hasattr(self, fname):
                raise ValueError("cannot downgrade any further")
            self = getattr(self, fname)()
        assert self.api_version == target
        return self

    def _convert_v3_to_v2(selfNEW):
        import copy
        self = copy.deepcopy(selfNEW)
        assert self.api_version == 3
        self.api_version = 2
        for index, service in enumerate(self.keys):
            if service.error is None:
                service.error = False
            else:
                service.rawraw = service.error
                service.error = True
            self.keys[index] = service
        return self

    @classmethod
    def _get_services(cls):
+32 −2
Original line number Diff line number Diff line
@@ -10,9 +10,25 @@ body {
  <body>
    <p>Please don't spam my servers - anyone that spams too much may be banned. Contact me on Discord (TheTechRobo#7420) or IRC (TheTechRobo on hackint) to discuss.</p>
    <h1>API Documentation</h1>
    <h3>Call: GET <code>/find/$videoid</code></h3>
    <h3>Call: GET <code>/api/:version/:videoid</code></h3>
    <h4>Current versions available: v2, v3</h4>
    <p>
    <b>Fields</b>
    {% if changelog[0] %}<div id="Fcl">
      <u>Changelog</u>
      <ul>
        {% for log, logitems in changelog[0].items() %}
          {% if log %}
            <li>Api version {{log}}</li>
            <ul>
              {% for logitem in logitems %}
                <li>{{logitem}}</li>
              {% endfor %}
            </ul>
          {% endif %}
        {% endfor %}
      </ul>
    </div>{%endif%}
    <br>
    <dl>
      {% if fields is mapping %}
@@ -30,7 +46,21 @@ body {
      {% endif %}
    </dl>
    <b>Service Objects</b>
    <p>One object for every service searched.</p>
    {% if changelog[1] %}<div id="Scl">
      <u>Changelog</u>
      <ul>
        {% for log, logitems in changelog[1].items() %}
          {% if log %}
            <li>Api version {{log}}</li>
            <ul>
              {% for logitem in logitems %}
                <li>{{logitem}}</li>
              {% endfor %}
            </ul>
          {% endif %}
        {% endfor %}
      </ul>
    </div>{%endif%}
    {% if services is mapping %}
      <dl>
        {% for field, value in services.items() %}
+14 −3
Original line number Diff line number Diff line
@@ -64,6 +64,10 @@ button {
  color: yellow;
  background-color: black;
}
.white {
  color: white;
  background-color: black;
}
</style>
    <script>
      function coerceToVid(vid) {
@@ -102,7 +106,7 @@ button {
        let url = "{{ url_for('static', filename='ab79a231234507.564a1d23814ef.gif') }}";
        let src = "https://www.behance.net/gallery/31234507/Open-source-Loading-GIF-Icons-Vol-1/modules/199929391";
        document.getElementById("data").innerHTML = `<img src="${url}" width="25" height="25" /> Loading could take up to 30 seconds.`;
        fetch(`api/v2/youtube/${vid}`)
        fetch(`api/v3/youtube/${vid}`)
                .then((response) => {
                        if (response.status == 410 || response.status == 404) {
                                document.getElementById("data").innerHTML = `<span style="color: red;">api version is not supported - this should never happen</span>`;
@@ -122,7 +126,10 @@ button {
            let keys = data.keys;
            keys.forEach((wbm) => {
              var colour;
              if (wbm.archived && wbm.metaonly) {
              if (wbm.error) {
                colour = "white";
              }
              else if (wbm.archived && wbm.metaonly) {
                colour = "yellow";
              }
              else if (wbm.archived) {
@@ -131,7 +138,11 @@ button {
              else {
                colour = "red";
              }
              let isarchived = wbm.archived ? "Archived" : "Not Archived";
              var isarchived = wbm.archived ? "Archived" : "Not Archived";
              if (wbm.error !== null) {
                isarchived = "Unknown";
                wbm.note = wbm.note + wbm.error;
              }
              let archived = `<span class='${colour}'>${isarchived}</span>`;
              let metaonly = (wbm.metaonly && wbm.archived) ? " (metadata only) " : " ";
              let comments = (wbm.archived && wbm.comments) ? " (incl. comments) ": " ";