# safetwitch-backend
The backend for [SafeTwitch](https://codeberg.org/dragongoose/safetwitch)

# Documentation

## API Endpoints

### Disclaimer

Every endpoint can return a 500 status code, and it follows this schema:

```json
{
  status: "error",
  data: "Error message..."
}
```

### /api/users/:username

**GET** - :username is any streamer
Gets a specific twitch streamer

#### Responses

###### 200

The request was successful, returns data of type [Streamer](https://codeberg.org/dragongoose/safetwitch-backend/src/branch/master/extractor/structs/parsed.go)
*Example:*

```json
{
  "status": "ok",
  "data": {
    "username": "filian",
    "about": "Welcome to my Vtuber alpha! 3D streaming and variety games! Join the discord! n_n",
    "pfp": "https://safetwitch.dragongoose.us/proxy/img/aHR0cHM6Ly9zdGF0aWMtY2RuLmp0dm53Lm5ldC9qdHZfdXNlcl9waWN0dXJlcy9mNzVkNDEwMy1hMjY1LTRlMjEtODhiNS00NDc0NWZjMWJmNDQtcHJvZmlsZV9pbWFnZS0zMDB4MzAwLnBuZw",
    "banner": "https://safetwitch.dragongoose.us/proxy/img/aHR0cHM6Ly9zdGF0aWMtY2RuLmp0dm53Lm5ldC9qdHZfdXNlcl9waWN0dXJlcy9kMjY2OTZlMC1hYjJhLTRlZjEtYTI3Ni0wZmZjZWM5NmM3NzYtcHJvZmlsZV9iYW5uZXItNDgwLnBuZw==",
    "followers": 579463,
    "socials": [
      {
        "type": "discord",
        "name": "discord.gg/filian",
        "link": "https://discord.gg/filian"
      },
      {
        "type": "twitter",
        "name": "twitter.com/filianIsLost",
        "link": "https://twitter.com/filianIsLost"
      }
    ],
    "isLive": false,
    "isPartner": true,
    "colorHex": "#8040E0",
    "id": 198633200,
    "stream": null
  }
}
```

##### 404

The streamer was not found

### /api/discover

**GET**
Gets the discover page of twitch, a list of categories

#### Responses

##### 200

The request was successful, returns a [CategoryData[]](https://codeberg.org/dragongoose/safetwitch-backend/src/branch/master/extractor/structs/parsed.go)
*Example:*

```json
{
  "status": "ok",
  "data": [
    {
      "name": "Just Chatting",
      "displayName": "Just Chatting",
      "viewers": 510365,
      "tags": [
        "IRL"
      ],
      "createdAt": null,
      "cursor": "eyJzIjoxLCJkIjpmYWxzZSwidCI6dHJ1ZX0=",
      "image": "https://safetwitch.dragongoose.us/proxy/img/aHR0cHM6Ly9zdGF0aWMtY2RuLmp0dm53Lm5ldC90dHYtYm94YXJ0LzUwOTY1OC0yODV4MzgwLmpwZw"
    },
    ...
}
```

### /api/discover/:game

**GET** - :game is a name of a twitch category
Gets a specific twitch category

#### Responses

##### 200

The server found the category, returns data of type [CategoryPreview[]](https://codeberg.org/dragongoose/safetwitch-backend/src/branch/master/extractor/structs/parsed.go)
*Example:*

```json
{
  "status": "ok",
  "data": {
    "name": "Just Chatting",
    "cover": "https://safetwitch.dragongoose.us/proxy/img/aHR0cHM6Ly9zdGF0aWMtY2RuLmp0dm53Lm5ldC90dHYtYm94YXJ0LzUwOTY1OC0xNDR4MTkyLmpwZw",
    "description": null,
    "viewers": 533593,
    "followers": 23147702,
    "tags": [
      "IRL"
    ],
    "streams": [
      {
        "title": "CIERRE DEL MERCATO DE LA KINGS LEAGUE | ÚLTIMO DÍA DE MERCATO | ÚLTIMOS CLAUSULAZOS | SE VIENEN LLOROS",
        "viewers": 60897,
        "preview": "https://safetwitch.dragongoose.us/proxy/img/aHR0cHM6Ly9zdGF0aWMtY2RuLmp0dm53Lm5ldC9wcmV2aWV3cy10dHYvbGl2ZV91c2VyX2liYWktNDQweDI0OC5qcGc",
        "tags": [
          "Español",
          "KOI",
          "KingsLeague"
        ],
        "cursor": "eyJzIjo2MDg5Ny40NDU3NDY0NjY4NSwiZCI6ZmFsc2UsInQiOnRydWV9",
        "streamer": {
          "name": "ibai",
          "pfp": "https://safetwitch.dragongoose.us/proxy/img/aHR0cHM6Ly9zdGF0aWMtY2RuLmp0dm53Lm5ldC9qdHZfdXNlcl9waWN0dXJlcy81NzQyMjhiZS0wMWVmLTRlYWItYmMwZS1hNGY2YjY4YmVkYmEtcHJvZmlsZV9pbWFnZS01MHg1MC5wbmc",
          "colorHex": "2A2B62"
        }
      },
      ...
    ]
  }
}
```

##### 404

The category was not found

### /api/badges?streamerName=NAME

**GET**
Gets global twitch chat badges

streamerName query is optional, if given it will only provide the badges for that streamer.

#### Responses

##### 200

Server retrieved the badges, returns type [Badge[]](hhttps://codeberg.org/dragongoose/safetwitch-backend/src/branch/master/extractor/structs/parsed.go)
*Example:*

```json
{
  "status": "ok",
  "data": [
    {
      "id": "getting-over-it_2;1;",
      "setId": "getting-over-it_2",
      "title": "Getting Over It",
      "version": "1",
      "images": {
        "image1x": "https://safetwitch.dragongoose.us/proxy/img/aHR0cHM6Ly9zdGF0aWMtY2RuLmp0dm53Lm5ldC9iYWRnZXMvdjEvYmI2MjBiNDItZTBlMS00MzczLTkyOGUtZDRhNzMyZjk5Y2NiLzE",
        "image2x": "https://safetwitch.dragongoose.us/proxy/img/aHR0cHM6Ly9zdGF0aWMtY2RuLmp0dm53Lm5ldC9iYWRnZXMvdjEvYmI2MjBiNDItZTBlMS00MzczLTkyOGUtZDRhNzMyZjk5Y2NiLzI",
        "image4x": "https://safetwitch.dragongoose.us/proxy/img/aHR0cHM6Ly9zdGF0aWMtY2RuLmp0dm53Lm5ldC9iYWRnZXMvdjEvYmI2MjBiNDItZTBlMS00MzczLTkyOGUtZDRhNzMyZjk5Y2NiLzM"
      }
    },
    ...
  ]
}
```

### /api/search?query=SEARCHQUERY

**GET** - SEARCHQUERY is any string
Searches for categories, streamers, tags, and live streamers. Returns data of type [SearchResult](https://codeberg.org/dragongoose/safetwitch-backend/src/branch/master/extractor/structs/parsed.go)



#### Responses

##### 200

The server found the search data, returns:
*Example:*

```json
{
  "status": "ok",
  "data": {
    "channels": [
      {
        "username": "filian",
        "followers": 580066,
        "isLive": false,
        "about": "Welcome to my Vtuber alpha! 3D streaming and variety games! Join the discord! n_n",
        "pfp": "https://safetwitch.dragongoose.us/proxy/img/aHR0cHM6Ly9zdGF0aWMtY2RuLmp0dm53Lm5ldC9qdHZfdXNlcl9waWN0dXJlcy9mNzVkNDEwMy1hMjY1LTRlMjEtODhiNS00NDc0NWZjMWJmNDQtcHJvZmlsZV9pbWFnZS0xNTB4MTUwLnBuZw",
        "isPartner": null,
        "colorHex": "#fff",
        "id": 198633200
      },
      ...
    ],
    "categories": [
      {
        "name": "Lilian: The beginning of the end",
        "displayName": "Lilian: The beginning of the end",
        "viewers": null,
        "tags": [
          ""
        ],
        "image": "https://safetwitch.dragongoose.us/proxy/img/aHR0cHM6Ly9zdGF0aWMtY2RuLmp0dm53Lm5ldC9wcmV2aWV3cy10dHYvbGl2ZV91c2VyX2VsbHllbi0xMjgweDcyMC5qcGc"
      }
    ],
    "relatedChannels": [
      {
        "username": "EllyEN",
        "about": "HI I'M ELLY❗❗ I'm a cwiminal Vtuber who loves cars, boba, games, & getting into trouble~❗❗",
        "pfp": "https://safetwitch.dragongoose.us/proxy/img/aHR0cHM6Ly9zdGF0aWMtY2RuLmp0dm53Lm5ldC9qdHZfdXNlcl9waWN0dXJlcy8xZjJjNmQ2Yi1hZmZmLTQ4NGYtYTdjMy1iNGRhZTU2MzllOGMtcHJvZmlsZV9pbWFnZS0zMDB4MzAwLmpwZWc",
        "followers": 100538,
        "socials": [
          {
            "type": "twitter",
            "name": "Twitter",
            "link": "https://www.twitter.com/EllyVtuber"
          },
          {
            "type": "tiktok",
            "name": "Tiktok",
            "link": "https://www.tiktok.com/@ellyvtuber"
          },
          {
            "type": "youtube",
            "name": "YouTube",
            "link": "https://www.youtube.com/EllyEN"
          }
        ],
        "isLive": true,
        "isPartner": true,
        "colorHex": "#FA2929",
        "id": 141045387,
        "stream": {
          "title": "little bit of valheim THEN FFXIV! 🌱 I'M OBSESSED LOL | SUBATHON PART 2 DAY 16 | #AlienwareHive !GamerSupps",
          "topic": "I'm Only Sleeping",
          "startedAt": 1682363001000,
          "tags": [
            "LGBTQIA",
            "NoBackseating",
            "Vtuber",
            "ENVtuber",
            "English"
          ],
          "viewers": 654,
          "preview": "https://safetwitch.dragongoose.us/proxy/img/aHR0cHM6Ly9zdGF0aWMtY2RuLmp0dm53Lm5ldC9wcmV2aWV3cy10dHYvbGl2ZV91c2VyX2VsbHllbi0xMjgweDcyMC5qcGc"
        }
      },
      ...
    ],
    "channelsWithTag": []
  }
}
```

## Proxying Endpoints

### /proxy/img/:base64Url

**GET**
Proxies an image through the server
:base64Url can be any base64 encoded Url

#### Responses

##### 200

Server returns the requested image

##### 404

The requested image was invalid

### /proxy/stream/:username/hls.m3u8

**GET**
Gets the m3u8 manifest for a streamer. This manifest will contain all stream qualities if they are live

#### Responses

##### 200

Returns the manifest

##### 400

The streamer is not live
*Example:*

```json
{
  "status": "error",
  "data": "Streamer is not live"
}
```

### /proxy/stream/sub/:encodedUrl

**GET**
Returns the m3u8 manifest for a specific quality under the [master manifest](#/proxy/stream/:username/hls.m3u8)

### 200

Returns the manifest file

### /proxy/stream/segment/:encodedUrl

**GET**
Gets a segment from one of the quality's manifest file. This is the actual video thats displayed on your screen

### 200

Returns the stream segment, HLS streaming.