mirror of https://github.com/aruppi/aruppi-api.git
Compare commits
No commits in common. 'v5.0.1' and 'v4' have entirely different histories.
@ -1,4 +0,0 @@
|
|||||||
build
|
|
||||||
.gradle
|
|
||||||
.git
|
|
||||||
*.md
|
|
@ -0,0 +1,6 @@
|
|||||||
|
PORT_LISTEN=5000
|
||||||
|
DATABASE_HOST=localhost
|
||||||
|
DATABASE_PORT=27017
|
||||||
|
REDIS_HOST=localhost
|
||||||
|
REDIS_PORT=6379
|
||||||
|
REDIS_PASSWORD=aruppiTime
|
@ -0,0 +1,32 @@
|
|||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
commonjs: true,
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
extends: ['prettier', 'eslint:recommended'],
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2018,
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: ['@typescript-eslint/eslint-plugin'],
|
||||||
|
rules: {
|
||||||
|
'no-underscore-dangle': 'off',
|
||||||
|
'class-methods-use-this': 'off',
|
||||||
|
camelcase: 'off',
|
||||||
|
'no-unused-vars': 'warn',
|
||||||
|
'no-undef': 'warn',
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
'import/resolver': {
|
||||||
|
node: {
|
||||||
|
extensions: ['.ts'],
|
||||||
|
typescript: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Desktop (please complete the following information):**
|
||||||
|
- OS: [e.g. iOS]
|
||||||
|
- Browser [e.g. chrome, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
**Smartphone (please complete the following information):**
|
||||||
|
- Device: [e.g. iPhone6]
|
||||||
|
- OS: [e.g. iOS8.1]
|
||||||
|
- Browser [e.g. stock browser, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
@ -1,109 +0,0 @@
|
|||||||
# This wrorkflow checkouts the latest src code, builds a docker image and
|
|
||||||
# push it to DockerHun and GitHub Container Registry
|
|
||||||
|
|
||||||
# This workflow uses actions that are not certified by GitHub.
|
|
||||||
# They are provided by a third-party and are governed by
|
|
||||||
# separate terms of service, privacy policy, and support
|
|
||||||
# documentation.
|
|
||||||
|
|
||||||
name: Publish Docker image
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
#branches: [ v5 ]
|
|
||||||
tags:
|
|
||||||
- '[0-9]+.[0-9]+.[0-9]+'
|
|
||||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
|
||||||
#paths:
|
|
||||||
# - 'src/*'
|
|
||||||
# - '!src/main/resources/*'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-and-push:
|
|
||||||
name: Build Docker image and push to multiple registries
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
packages: write
|
|
||||||
contents: read
|
|
||||||
attestations: write
|
|
||||||
id-token: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
# Step 1: Source code checkout
|
|
||||||
- name: Source code checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
#- name: Gradle cache
|
|
||||||
# uses: actions/setup-java@v4
|
|
||||||
# with:
|
|
||||||
# distribution: 'corretto'
|
|
||||||
# java-version: '21'
|
|
||||||
# cache: 'gradle'
|
|
||||||
#
|
|
||||||
#- name: Setup Gradle
|
|
||||||
# uses: gradle/actions/setup-gradle@v4
|
|
||||||
#
|
|
||||||
#- name: Gradle build
|
|
||||||
# run: ./gradlew build --no-daemon
|
|
||||||
|
|
||||||
# Step 2: Configure Docker Buildx
|
|
||||||
- name: Configure Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
# Step 2.5: Setup QEMU
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
# Step 3: Log in to Docker Hub
|
|
||||||
- name: Log in to Docker Hub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
|
|
||||||
# Step 4: GitHub Container Registry login
|
|
||||||
- name: Log in to GitHub Container Registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GHCR_TOKEN }}
|
|
||||||
|
|
||||||
# Step 5: Configure image tags
|
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: |
|
|
||||||
${{ secrets.DOCKERHUB_USERNAME }}/aruppi-api
|
|
||||||
ghcr.io/${{ github.repository }}
|
|
||||||
tags: |
|
|
||||||
type=ref,event=branch
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
type=semver,pattern={{major}}
|
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
|
||||||
type=sha
|
|
||||||
|
|
||||||
# Step 6: Build and publish to Docker Hub and GitHub Container Registry
|
|
||||||
- name: Build and publish
|
|
||||||
id: push
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
#platforms: linux/amd64,linux/arm64
|
|
||||||
platforms: linux/amd64
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
#github-token: ${{ secrets.GHCR_TOKEN }}
|
|
||||||
|
|
||||||
## Step 7: Generate artifact attestation
|
|
||||||
#- name: Generate artifact attestation
|
|
||||||
# uses: actions/attest-build-provenance@v1
|
|
||||||
# with:
|
|
||||||
# subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
|
|
||||||
# subject-digest: ${{ steps.push.outputs.digest }}
|
|
||||||
# push-to-registry: true
|
|
@ -0,0 +1,24 @@
|
|||||||
|
name: Node.js CI
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [8.x, 10.x, 12.x]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- run: npm install
|
||||||
|
- run: npm run build --if-present
|
||||||
|
- run: npm test
|
||||||
|
env:
|
||||||
|
CI: true
|
@ -1,49 +1,193 @@
|
|||||||
.gradle
|
# Logs
|
||||||
build/
|
logs
|
||||||
!gradle/wrapper/gradle-wrapper.jar
|
*.log
|
||||||
!**/src/main/**/build/
|
npm-debug.log*
|
||||||
!**/src/test/**/build/
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
# System Files
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
.DS_Store
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
# Environment Variables
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
.env
|
.env
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
# Docker
|
# Gatsby files
|
||||||
docker-compose.override.yml
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
### STS ###
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
.apt_generated
|
# public
|
||||||
.classpath
|
|
||||||
.factorypath
|
# vuepress build output
|
||||||
.project
|
.vuepress/dist
|
||||||
.settings
|
|
||||||
.springBeans
|
# Serverless directories
|
||||||
.sts4-cache
|
.serverless/
|
||||||
bin/
|
|
||||||
!**/src/main/**/bin/
|
# FuseBox cache
|
||||||
!**/src/test/**/bin/
|
.fusebox/
|
||||||
|
|
||||||
### IntelliJ IDEA ###
|
# DynamoDB Local files
|
||||||
.idea
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
*.iws
|
*.iws
|
||||||
*.iml
|
|
||||||
*.ipr
|
# IntelliJ
|
||||||
out/
|
out/
|
||||||
!**/src/main/**/out/
|
|
||||||
!**/src/test/**/out/
|
|
||||||
|
|
||||||
### NetBeans ###
|
# mpeltonen/sbt-idea plugin
|
||||||
/nbproject/private/
|
.idea_modules/
|
||||||
/nbbuild/
|
|
||||||
/dist/
|
# JIRA plugin
|
||||||
/nbdist/
|
atlassian-ide-plugin.xml
|
||||||
/.nb-gradle/
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
### VS Code ###
|
# Android studio 3.1+ serialized cache file
|
||||||
.vscode/
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
gradlew
|
# exclude apidocs
|
||||||
|
animeflv-docs/
|
||||||
|
|
||||||
gradlew.bat
|
.idea/
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
singleQuote: true,
|
||||||
|
trailingComma: 'all',
|
||||||
|
arrowParens: 'avoid',
|
||||||
|
};
|
@ -1,29 +0,0 @@
|
|||||||
# Stage 1: Cache Gradle dependencies
|
|
||||||
FROM gradle:8-jdk21-corretto AS cache
|
|
||||||
RUN mkdir -p /home/gradle/cache_home
|
|
||||||
ENV GRADLE_USER_HOME /home/gradle/cache_home
|
|
||||||
COPY build.gradle.kts /home/gradle/src/build.gradle.kts
|
|
||||||
COPY settings.gradle.kts /home/gradle/src/settings.gradle.kts
|
|
||||||
COPY gradle/libs.versions.toml /home/gradle/src/gradle/libs.versions.toml
|
|
||||||
WORKDIR /home/gradle/src
|
|
||||||
RUN gradle clean build -i --stacktrace
|
|
||||||
|
|
||||||
# Stage 2: Build Application
|
|
||||||
FROM gradle:8-jdk21-corretto AS build
|
|
||||||
COPY --from=cache /home/gradle/cache_home /home/gradle/.gradle
|
|
||||||
COPY . /usr/src/app/
|
|
||||||
WORKDIR /usr/src/app
|
|
||||||
COPY --chown=gradle:gradle . /home/gradle/src
|
|
||||||
WORKDIR /home/gradle/src
|
|
||||||
# Build the fat JAR, Gradle also supports shadow
|
|
||||||
# and boot JAR by default.
|
|
||||||
RUN gradle buildFatJar --no-daemon
|
|
||||||
|
|
||||||
# Stage 3: Create the Runtime Image
|
|
||||||
FROM amazoncorretto:21 AS runtime
|
|
||||||
EXPOSE 8080/tcp
|
|
||||||
RUN mkdir /app
|
|
||||||
COPY --from=build /home/gradle/src/build/libs/aruppi-api-all.jar /app/app.jar
|
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
|
||||||
RUN chmod +x /entrypoint.sh
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
|
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Jéluchu
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
Binary file not shown.
Before Width: | Height: | Size: 69 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.3 MiB |
@ -1,33 +0,0 @@
|
|||||||
plugins {
|
|
||||||
alias(libs.plugins.kotlin.jvm)
|
|
||||||
alias(libs.plugins.ktor)
|
|
||||||
alias(libs.plugins.kotlin.plugin.serialization)
|
|
||||||
}
|
|
||||||
|
|
||||||
group = "com.jeluchu"
|
|
||||||
version = "5.0.0"
|
|
||||||
|
|
||||||
application {
|
|
||||||
mainClass.set("io.ktor.server.netty.EngineMain")
|
|
||||||
|
|
||||||
val isDevelopment: Boolean = project.ext.has("development")
|
|
||||||
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(libs.bson)
|
|
||||||
implementation(libs.logback.classic)
|
|
||||||
implementation(libs.ktor.server.core)
|
|
||||||
implementation(libs.ktor.server.netty)
|
|
||||||
implementation(libs.ktor.server.swagger)
|
|
||||||
implementation(libs.mongodb.driver.core)
|
|
||||||
implementation(libs.mongodb.driver.sync)
|
|
||||||
implementation(libs.ktor.server.config.yaml)
|
|
||||||
implementation(libs.ktor.server.status.pages)
|
|
||||||
implementation(libs.ktor.serialization.kotlinx.json)
|
|
||||||
implementation(libs.ktor.server.content.negotiation)
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
|
||||||
app:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
container_name: ktor_app
|
|
||||||
ports:
|
|
||||||
- "8080:8080"
|
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
networks:
|
|
||||||
- app-network
|
|
||||||
|
|
||||||
networks:
|
|
||||||
app-network:
|
|
||||||
driver: bridge
|
|
@ -1,2 +0,0 @@
|
|||||||
MONGO_CONNECTION_STRING=mongodb://user:password@host:port
|
|
||||||
MONGO_DATABASE_NAME=aruppi
|
|
@ -0,0 +1,4 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
git log --oneline --all --graph --decorate $(git reflog | awk '{print $1}')
|
||||||
|
|
@ -1,3 +0,0 @@
|
|||||||
kotlin.code.style=official
|
|
||||||
org.gradle.caching=true
|
|
||||||
org.gradle.daemon=false
|
|
@ -1,26 +0,0 @@
|
|||||||
|
|
||||||
[versions]
|
|
||||||
kotlin-version = "2.0.21"
|
|
||||||
ktor-version = "3.0.1"
|
|
||||||
logback-version = "1.4.14"
|
|
||||||
mongo-version = "4.10.2"
|
|
||||||
|
|
||||||
[libraries]
|
|
||||||
ktor-server-core = { module = "io.ktor:ktor-server-core-jvm", version.ref = "ktor-version" }
|
|
||||||
ktor-server-swagger = { module = "io.ktor:ktor-server-swagger-jvm", version.ref = "ktor-version" }
|
|
||||||
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json-jvm", version.ref = "ktor-version" }
|
|
||||||
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation-jvm", version.ref = "ktor-version" }
|
|
||||||
mongodb-driver-core = { module = "org.mongodb:mongodb-driver-core", version.ref = "mongo-version" }
|
|
||||||
mongodb-driver-sync = { module = "org.mongodb:mongodb-driver-sync", version.ref = "mongo-version" }
|
|
||||||
bson = { module = "org.mongodb:bson", version.ref = "mongo-version" }
|
|
||||||
ktor-server-netty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "ktor-version" }
|
|
||||||
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-version" }
|
|
||||||
ktor-server-config-yaml = { module = "io.ktor:ktor-server-config-yaml-jvm", version.ref = "ktor-version" }
|
|
||||||
ktor-server-test-host = { module = "io.ktor:ktor-server-test-host-jvm", version.ref = "ktor-version" }
|
|
||||||
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin-version" }
|
|
||||||
ktor-server-status-pages = { module = "io.ktor:ktor-server-status-pages", version.ref = "kotlin-version" }
|
|
||||||
|
|
||||||
[plugins]
|
|
||||||
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" }
|
|
||||||
ktor = { id = "io.ktor.plugin", version.ref = "ktor-version" }
|
|
||||||
kotlin-plugin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version = "2.0.21" }
|
|
Binary file not shown.
@ -1,5 +0,0 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
|
||||||
distributionPath=wrapper/dists
|
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
|
||||||
zipStorePath=wrapper/dists
|
|
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "aruppi",
|
||||||
|
"version": 2,
|
||||||
|
"builds": [
|
||||||
|
{
|
||||||
|
"src": "./src/index.js",
|
||||||
|
"use": "@now/node"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{ "src": "/(.*)", "dest": "/src/index.js" }
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
{
|
||||||
|
"name": "aruppi",
|
||||||
|
"version": "4.2.2",
|
||||||
|
"description": "Aruppi is a custom API to obtain data from the Japanese culture for the mobile app",
|
||||||
|
"main": "./src/api/api.ts",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node ./dist/server.js",
|
||||||
|
"dev": "tsnd --transpile-only --ignore-watch node_modules --respawn src/server.ts",
|
||||||
|
"build": "tsc -p ."
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"aruppi",
|
||||||
|
"aruppi api",
|
||||||
|
"aruppi app",
|
||||||
|
"aruppi android"
|
||||||
|
],
|
||||||
|
"author": {
|
||||||
|
"name": "Jéluchu",
|
||||||
|
"email": "infoaruppi@gmail.com",
|
||||||
|
"reason": "Android Developer and Others",
|
||||||
|
"url": "https://jeluchu.github.io/",
|
||||||
|
"social": {
|
||||||
|
"github": "https://github.com/Jeluchu",
|
||||||
|
"twitter": "https://twitter.com/Jeluchu"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contributors": [
|
||||||
|
{
|
||||||
|
"name": "Darkangeel",
|
||||||
|
"url": "https://github.com/Darkangeel-hd",
|
||||||
|
"reason": "System administration authority (SYSADM)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Capitanwesler",
|
||||||
|
"url": "https://github.com/capitanwesler",
|
||||||
|
"reason": "Backend and Frontend developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.x",
|
||||||
|
"npm": ">= 6.14.x"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/aruppi/aruppi-api/issues"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/aruppi/aruppi-api.git"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"body-parser": "^1.19.0",
|
||||||
|
"cheerio": "^1.0.0-rc.6",
|
||||||
|
"compose-middleware": "^5.0.1",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^8.2.0",
|
||||||
|
"express": "^4.17.3",
|
||||||
|
"got": "^11.8.5",
|
||||||
|
"helmet": "^4.5.0",
|
||||||
|
"mongodb": "^3.6.6",
|
||||||
|
"mongoose": "^5.13.15",
|
||||||
|
"redis": "^3.1.2",
|
||||||
|
"rss-parser": "^3.12.0",
|
||||||
|
"tough-cookie": "^4.0.0",
|
||||||
|
"ts-node-dev": "^1.1.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/cheerio": "^0.22.28",
|
||||||
|
"@types/cors": "^2.8.10",
|
||||||
|
"@types/express": "^4.17.11",
|
||||||
|
"@types/node": "^15.0.2",
|
||||||
|
"@types/redis": "^2.8.28",
|
||||||
|
"@types/tough-cookie": "^4.0.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
||||||
|
"@typescript-eslint/parser": "^4.22.0",
|
||||||
|
"eslint": "^7.24.0",
|
||||||
|
"eslint-config-prettier": "^8.2.0",
|
||||||
|
"eslint-plugin-import": "^2.22.1",
|
||||||
|
"eslint-plugin-prettier": "^3.4.0",
|
||||||
|
"prettier": "^2.2.1",
|
||||||
|
"ts-node": "^9.1.1",
|
||||||
|
"typescript": "^4.2.4"
|
||||||
|
}
|
||||||
|
}
|
@ -1 +0,0 @@
|
|||||||
rootProject.name = "aruppi-api"
|
|
@ -0,0 +1,782 @@
|
|||||||
|
import { NextFunction, Request, Response } from 'express';
|
||||||
|
import { requestGot } from '../utils/requestCall';
|
||||||
|
import {
|
||||||
|
imageUrlToBase64,
|
||||||
|
jkanimeInfo,
|
||||||
|
monoschinosInfo,
|
||||||
|
tioanimeInfo,
|
||||||
|
videoServersJK,
|
||||||
|
videoServersMonosChinos,
|
||||||
|
videoServersTioAnime,
|
||||||
|
} from '../utils/util';
|
||||||
|
import { transformUrlServer } from '../utils/transformerUrl';
|
||||||
|
import AnimeModel, { Anime as ModelA } from '../database/models/anime.model';
|
||||||
|
import util from 'util';
|
||||||
|
import { hashStringMd5 } from '../utils/util';
|
||||||
|
import {
|
||||||
|
animeExtraInfo,
|
||||||
|
getAnimeVideoPromo,
|
||||||
|
getAnimeCharacters,
|
||||||
|
getRelatedAnimesMAL,
|
||||||
|
} from '../utils/util';
|
||||||
|
import urls from '../utils/urls';
|
||||||
|
import { redisClient } from '../database/connection';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
redisClient.get = util.promisify(redisClient.get);
|
||||||
|
|
||||||
|
/*
|
||||||
|
AnimeController - a class to manage the schedule,
|
||||||
|
top, all the animes, return the last episodes
|
||||||
|
with async to return promises.
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface Schedule {
|
||||||
|
title: string;
|
||||||
|
mal_id: number;
|
||||||
|
image_url: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Anime {
|
||||||
|
index: string;
|
||||||
|
animeId: string;
|
||||||
|
title: string;
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Top {
|
||||||
|
rank: string;
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
image_url: string;
|
||||||
|
type: string;
|
||||||
|
subtype: string;
|
||||||
|
page: number;
|
||||||
|
score: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Episode {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
image: string;
|
||||||
|
episode: number;
|
||||||
|
servers: { id: string; url: string; direct: boolean };
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Movie {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
type: string;
|
||||||
|
page: string;
|
||||||
|
banner: string;
|
||||||
|
image: string;
|
||||||
|
synopsis: string;
|
||||||
|
status: string;
|
||||||
|
rate: string;
|
||||||
|
genres: string[];
|
||||||
|
episodes: object[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class AnimeController {
|
||||||
|
async schedule(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { day } = req.params;
|
||||||
|
let info: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
const resultQueryRedis: any = await redisClient.get(
|
||||||
|
`schedule_${hashStringMd5(day)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resultQueryRedis) {
|
||||||
|
const resultRedis: any = JSON.parse(resultQueryRedis);
|
||||||
|
|
||||||
|
return res.status(200).json(resultRedis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info = await requestGot(`${urls.BASE_JIKAN}schedules?filter=${day}`, {
|
||||||
|
parse: true,
|
||||||
|
scrapy: false,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const animeList: Schedule[] = info.data.map((item: any) => ({
|
||||||
|
title: item.titles.find((x: { type: string; }) => x.type === "Default").title,
|
||||||
|
malid: item.mal_id,
|
||||||
|
image: item.images.jpg.image_url,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (animeList.length > 0) {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
/* Set the key in the redis cache. */
|
||||||
|
|
||||||
|
redisClient.set(
|
||||||
|
`schedule_${hashStringMd5(day)}`,
|
||||||
|
JSON.stringify({ day: animeList }),
|
||||||
|
);
|
||||||
|
|
||||||
|
/* After 6hrs expire the key. */
|
||||||
|
|
||||||
|
redisClient.expire(
|
||||||
|
`schedule_${hashStringMd5(day)}`,
|
||||||
|
+ 21600,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
day: animeList,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async top(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { type, subtype, page } = req.params;
|
||||||
|
let info: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
let resultQueryRedis: any;
|
||||||
|
|
||||||
|
if (subtype) {
|
||||||
|
resultQueryRedis = await redisClient.get(
|
||||||
|
`top_${hashStringMd5(`${type}:${subtype}:${page}`)}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
resultQueryRedis = await redisClient.get(
|
||||||
|
`top_${hashStringMd5(`${type}:${page}`)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultQueryRedis) {
|
||||||
|
const resultRedis: any = JSON.parse(resultQueryRedis);
|
||||||
|
|
||||||
|
return res.status(200).json(resultRedis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subtype !== undefined) {
|
||||||
|
info = await requestGot(
|
||||||
|
`${urls.BASE_JIKAN}top/${type}?filter=${subtype}&page=${page}`,
|
||||||
|
{ parse: true, scrapy: false },
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
info = await requestGot(`${urls.BASE_JIKAN}top/${type}?page=${page}`, {
|
||||||
|
parse: true,
|
||||||
|
scrapy: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const top: Top[] = info.data.map((item: any, index: number) => ({
|
||||||
|
// A little hacky way to fix null ranks
|
||||||
|
rank: item.rank || index + 1 + (info.pagination.current_page-1)*info.pagination.items.per_page,
|
||||||
|
title: item.titles.find((x: { type: string; }) => x.type === "Default").title,
|
||||||
|
url: item.url,
|
||||||
|
image_url: item.images.jpg.image_url,
|
||||||
|
type: type,
|
||||||
|
subtype: subtype,
|
||||||
|
page: page,
|
||||||
|
score: item.score,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (top.length > 0) {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
/* Set the key in the redis cache. */
|
||||||
|
if (subtype) {
|
||||||
|
redisClient.set(
|
||||||
|
`top_${hashStringMd5(`${type}:${subtype}:${page}`)}`,
|
||||||
|
JSON.stringify({ top }),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
redisClient.set(
|
||||||
|
`top_${hashStringMd5(`${type}:${page}`)}`,
|
||||||
|
JSON.stringify({ top }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* After 24hrs expire the key. */
|
||||||
|
|
||||||
|
if (subtype) {
|
||||||
|
redisClient.expireat(
|
||||||
|
`top_${hashStringMd5(`${type}:${subtype}:${page}`)}`,
|
||||||
|
parseInt(`${+new Date() / 1000}`, 10) + 7200,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
redisClient.expireat(
|
||||||
|
`top_${hashStringMd5(`${type}:${page}`)}`,
|
||||||
|
parseInt(`${+new Date() / 1000}`, 10) + 7200,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({ top });
|
||||||
|
} else {
|
||||||
|
return res.status(400).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllAnimes(req: Request, res: Response, next: NextFunction) {
|
||||||
|
let data: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = await requestGot(`${urls.BASE_ANIMEFLV}api/animes/list`, {
|
||||||
|
parse: true,
|
||||||
|
scrapy: false,
|
||||||
|
spoof: true,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const animes: Anime[] = data.map((item: any) => ({
|
||||||
|
index: item[0],
|
||||||
|
animeId: item[3],
|
||||||
|
title: item[1],
|
||||||
|
id: item[2],
|
||||||
|
type: item[4],
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (animes.length > 0) {
|
||||||
|
res.status(200).send({ animes });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLastEpisodes(req: Request, res: Response, next: NextFunction) {
|
||||||
|
|
||||||
|
let lastEpisodes;
|
||||||
|
let episodes: Episode[] = [];
|
||||||
|
let animeList: any[] = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
const resultQueryRedis: any = await redisClient.get(
|
||||||
|
`lastEpisodes_${hashStringMd5('lastEpisodes')}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resultQueryRedis) {
|
||||||
|
const resultRedis: any = JSON.parse(resultQueryRedis);
|
||||||
|
|
||||||
|
return res.status(200).json(resultRedis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastEpisodes = await requestGot(`${urls.BASE_ARUPPI_MONOSCHINOS}lastest`, {
|
||||||
|
scrapy: false,
|
||||||
|
parse: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const anime of lastEpisodes) {
|
||||||
|
animeList.push({
|
||||||
|
id: `ver/${anime.id}`,
|
||||||
|
title: anime.title,
|
||||||
|
image: anime.image,
|
||||||
|
episode: anime.no,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const anime of animeList) {
|
||||||
|
episodes.push({
|
||||||
|
id: anime.id,
|
||||||
|
title: anime.title,
|
||||||
|
image: await imageUrlToBase64(anime.image),
|
||||||
|
episode: anime.episode,
|
||||||
|
servers: await videoServersMonosChinos(anime.id),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (episodes.length > 0) {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
/* Set the key in the redis cache. */
|
||||||
|
|
||||||
|
redisClient.set(
|
||||||
|
`lastEpisodes_${hashStringMd5('lastEpisodes')}`,
|
||||||
|
JSON.stringify({ episodes }),
|
||||||
|
);
|
||||||
|
|
||||||
|
/* After 24hrs expire the key. */
|
||||||
|
|
||||||
|
redisClient.expireat(
|
||||||
|
`lastEpisodes_${hashStringMd5('lastEpisodes')}`,
|
||||||
|
parseInt(`${+new Date() / 1000}`, 10) + 1800,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
episodes,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getContentTv(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { type, page } = req.params;
|
||||||
|
const url = 'tv';
|
||||||
|
let data: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
const resultQueryRedis: any = await redisClient.get(
|
||||||
|
`contentTv_${hashStringMd5(`${type}:${page}`)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resultQueryRedis) {
|
||||||
|
const resultRedis: any = JSON.parse(resultQueryRedis);
|
||||||
|
|
||||||
|
return res.status(200).json(resultRedis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data = await requestGot(
|
||||||
|
`${urls.BASE_ANIMEFLV_JELU}${
|
||||||
|
url.charAt(0).toUpperCase() + url.slice(1)
|
||||||
|
}/${type}/${page}`,
|
||||||
|
{
|
||||||
|
parse: true,
|
||||||
|
scrapy: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const animes: Movie[] = data[url].map((item: any) => {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
title: item.title,
|
||||||
|
type: url,
|
||||||
|
page: page,
|
||||||
|
banner: item.banner,
|
||||||
|
image: item.poster,
|
||||||
|
synopsis: item.synopsis,
|
||||||
|
status: item.debut,
|
||||||
|
rate: item.rating,
|
||||||
|
genres: item.genres.map((genre: any) => genre),
|
||||||
|
episodes: item.episodes.map((episode: any) => episode),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (animes.length > 0) {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
/* Set the key in the redis cache. */
|
||||||
|
|
||||||
|
redisClient.set(
|
||||||
|
`contentTv_${hashStringMd5(`${type}:${page}`)}`,
|
||||||
|
JSON.stringify({ animes }),
|
||||||
|
);
|
||||||
|
|
||||||
|
/* After 24hrs expire the key. */
|
||||||
|
|
||||||
|
redisClient.expireat(
|
||||||
|
`contentTv_${hashStringMd5(`${type}:${page}`)}`,
|
||||||
|
parseInt(`${+new Date() / 1000}`, 10) + 7200,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
animes,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getContentSpecial(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { type, page } = req.params;
|
||||||
|
const url = 'special';
|
||||||
|
let data: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
const resultQueryRedis: any = await redisClient.get(
|
||||||
|
`contentSpecial_${hashStringMd5(`${type}:${page}`)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resultQueryRedis) {
|
||||||
|
const resultRedis: any = JSON.parse(resultQueryRedis);
|
||||||
|
|
||||||
|
return res.status(200).json(resultRedis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data = await requestGot(
|
||||||
|
`${urls.BASE_ANIMEFLV_JELU}${
|
||||||
|
url.charAt(0).toUpperCase() + url.slice(1)
|
||||||
|
}/${type}/${page}`,
|
||||||
|
{
|
||||||
|
parse: true,
|
||||||
|
scrapy: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const animes: Movie[] = data[url].map((item: any) => {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
title: item.title,
|
||||||
|
type: url,
|
||||||
|
page: page,
|
||||||
|
banner: item.banner,
|
||||||
|
image: item.poster,
|
||||||
|
synopsis: item.synopsis,
|
||||||
|
status: item.debut,
|
||||||
|
rate: item.rating,
|
||||||
|
genres: item.genres.map((genre: any) => genre),
|
||||||
|
episodes: item.episodes.map((episode: any) => episode),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (animes.length > 0) {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
/* Set the key in the redis cache. */
|
||||||
|
|
||||||
|
redisClient.set(
|
||||||
|
`contentSpecial_${hashStringMd5(`${type}:${page}`)}`,
|
||||||
|
JSON.stringify({ animes }),
|
||||||
|
);
|
||||||
|
|
||||||
|
/* After 24hrs expire the key. */
|
||||||
|
|
||||||
|
redisClient.expireat(
|
||||||
|
`contentSpecial_${hashStringMd5(`${type}:${page}`)}`,
|
||||||
|
parseInt(`${+new Date() / 1000}`, 10) + 7200,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
animes,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getContentOva(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { type, page } = req.params;
|
||||||
|
const url = 'ova';
|
||||||
|
let data: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
const resultQueryRedis: any = await redisClient.get(
|
||||||
|
`contentOva_${hashStringMd5(`${type}:${page}`)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resultQueryRedis) {
|
||||||
|
const resultRedis: any = JSON.parse(resultQueryRedis);
|
||||||
|
|
||||||
|
return res.status(200).json(resultRedis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data = await requestGot(
|
||||||
|
`${urls.BASE_ANIMEFLV_JELU}${
|
||||||
|
url.charAt(0).toUpperCase() + url.slice(1)
|
||||||
|
}/${type}/${page}`,
|
||||||
|
{
|
||||||
|
parse: true,
|
||||||
|
scrapy: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const animes: Movie[] = data[url].map((item: any) => {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
title: item.title,
|
||||||
|
type: url,
|
||||||
|
page: page,
|
||||||
|
banner: item.banner,
|
||||||
|
image: item.poster,
|
||||||
|
synopsis: item.synopsis,
|
||||||
|
status: item.debut,
|
||||||
|
rate: item.rating,
|
||||||
|
genres: item.genres.map((genre: any) => genre),
|
||||||
|
episodes: item.episodes.map((episode: any) => episode),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (animes.length > 0) {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
/* Set the key in the redis cache. */
|
||||||
|
|
||||||
|
redisClient.set(
|
||||||
|
`contentOva_${hashStringMd5(`${type}:${page}`)}`,
|
||||||
|
JSON.stringify({ animes }),
|
||||||
|
);
|
||||||
|
|
||||||
|
/* After 24hrs expire the key. */
|
||||||
|
|
||||||
|
redisClient.expireat(
|
||||||
|
`contentOva_${hashStringMd5(`${type}:${page}`)}`,
|
||||||
|
parseInt(`${+new Date() / 1000}`, 10) + 7200,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
animes,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getContentMovie(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { type, page } = req.params;
|
||||||
|
const url = 'movies';
|
||||||
|
let data: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
const resultQueryRedis: any = await redisClient.get(
|
||||||
|
`contentMovie_${hashStringMd5(`${type}:${page}`)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resultQueryRedis) {
|
||||||
|
const resultRedis: any = JSON.parse(resultQueryRedis);
|
||||||
|
|
||||||
|
return res.status(200).json(resultRedis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data = await requestGot(
|
||||||
|
`${urls.BASE_ANIMEFLV_JELU}${
|
||||||
|
url.charAt(0).toUpperCase() + url.slice(1)
|
||||||
|
}/${type}/${page}`,
|
||||||
|
{
|
||||||
|
parse: true,
|
||||||
|
scrapy: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const animes: Movie[] = data[url].map((item: any) => {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
title: item.title,
|
||||||
|
type: url,
|
||||||
|
page: page,
|
||||||
|
banner: item.banner,
|
||||||
|
image: item.poster,
|
||||||
|
synopsis: item.synopsis,
|
||||||
|
status: item.debut,
|
||||||
|
rate: item.rating,
|
||||||
|
genres: item.genres.map((genre: any) => genre),
|
||||||
|
episodes: item.episodes.map((episode: any) => episode),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (animes.length > 0) {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
/* Set the key in the redis cache. */
|
||||||
|
|
||||||
|
redisClient.set(
|
||||||
|
`contentMovie_${hashStringMd5(`${type}:${page}`)}`,
|
||||||
|
JSON.stringify({ animes }),
|
||||||
|
);
|
||||||
|
|
||||||
|
/* After 24hrs expire the key. */
|
||||||
|
|
||||||
|
redisClient.expireat(
|
||||||
|
`contentMovie_${hashStringMd5(`${type}:${page}`)}`,
|
||||||
|
parseInt(`${+new Date() / 1000}`, 10) + 7200,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
animes,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEpisodes(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { title } = req.params;
|
||||||
|
let searchAnime: ModelA | null;
|
||||||
|
let episodes: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
const resultQueryRedis: any = await redisClient.get(
|
||||||
|
`episodes_${hashStringMd5(title)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resultQueryRedis) {
|
||||||
|
const resultRedis: any = JSON.parse(resultQueryRedis);
|
||||||
|
|
||||||
|
return res.status(200).json(resultRedis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
searchAnime = await AnimeModel.findOne({
|
||||||
|
$or: [{ title: { $eq: title } }, { title: { $eq: `${title} (TV)` } }],
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (searchAnime?.source) {
|
||||||
|
case 'jkanime':
|
||||||
|
episodes = await jkanimeInfo(searchAnime?.id, searchAnime?.mal_id);
|
||||||
|
break;
|
||||||
|
case 'monoschinos':
|
||||||
|
episodes = await monoschinosInfo(searchAnime?.id, searchAnime?.mal_id);
|
||||||
|
break;
|
||||||
|
case 'tioanime':
|
||||||
|
episodes = await tioanimeInfo(searchAnime?.id, searchAnime?.mal_id);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
episodes = undefined;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (episodes) {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
/* Set the key in the redis cache. */
|
||||||
|
|
||||||
|
redisClient.set(
|
||||||
|
`episodes_${hashStringMd5(title)}`,
|
||||||
|
JSON.stringify({ episodes }),
|
||||||
|
);
|
||||||
|
|
||||||
|
/* After 24hrs expire the key. */
|
||||||
|
|
||||||
|
redisClient.expireat(
|
||||||
|
`episodes_${hashStringMd5(title)}`,
|
||||||
|
parseInt(`${+new Date() / 1000}`, 10) + 7200,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ episodes });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getServers(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { id } = req.params;
|
||||||
|
let data: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
const resultQueryRedis: any = await redisClient.get(
|
||||||
|
`servers_${hashStringMd5(id)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resultQueryRedis) {
|
||||||
|
const resultRedis: any = JSON.parse(resultQueryRedis);
|
||||||
|
|
||||||
|
return res.status(200).json(resultRedis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let indicator = false;
|
||||||
|
|
||||||
|
if (id.split('/')[0] === 'ver' && !indicator) {
|
||||||
|
data = await videoServersTioAnime(id);
|
||||||
|
|
||||||
|
if (!data.name) {
|
||||||
|
indicator = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.split('/')[0] === 'ver' && !indicator) {
|
||||||
|
data = await videoServersMonosChinos(id);
|
||||||
|
|
||||||
|
if (!data.name) {
|
||||||
|
console.log(data.name);
|
||||||
|
indicator = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!indicator) {
|
||||||
|
data = undefined;
|
||||||
|
|
||||||
|
indicator = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
This part is just for handling the error
|
||||||
|
if the two above doesn't complete the operation
|
||||||
|
does not make sense to have the getServers from
|
||||||
|
JKAnime.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
/* Set the key in the redis cache. */
|
||||||
|
|
||||||
|
redisClient.set(
|
||||||
|
`servers_${hashStringMd5(id)}`,
|
||||||
|
JSON.stringify({ servers: data }),
|
||||||
|
);
|
||||||
|
|
||||||
|
/* After 24hrs expire the key. */
|
||||||
|
|
||||||
|
redisClient.expireat(
|
||||||
|
`servers_${hashStringMd5(id)}`,
|
||||||
|
parseInt(`${+new Date() / 1000}`, 10) + 7200,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ servers: data });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRandomAnime(req: Request, res: Response, next: NextFunction) {
|
||||||
|
let animeQuery: ModelA[] | null;
|
||||||
|
let animeResult: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
animeQuery = await AnimeModel.aggregate([{ $sample: { size: 1 } }]);
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
animeResult = {
|
||||||
|
title: animeQuery[0].title || null,
|
||||||
|
poster: animeQuery[0].poster || null,
|
||||||
|
synopsis: animeQuery[0].description || null,
|
||||||
|
type: animeQuery[0].type || null,
|
||||||
|
rating: animeQuery[0].score || null,
|
||||||
|
genres: animeQuery[0].genres || null,
|
||||||
|
moreInfo: [await animeExtraInfo(animeQuery[0].mal_id)],
|
||||||
|
promo: await getAnimeVideoPromo(animeQuery[0].mal_id),
|
||||||
|
characters: await getAnimeCharacters(animeQuery[0].mal_id),
|
||||||
|
related: await getRelatedAnimesMAL(animeQuery[0].mal_id),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (animeResult) {
|
||||||
|
res.set('Cache-Control', 'no-store');
|
||||||
|
res.status(200).json(animeResult);
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,478 @@
|
|||||||
|
import { NextFunction, Request, Response } from 'express';
|
||||||
|
import { requestGot } from '../utils/requestCall';
|
||||||
|
import AnimeModel, { Anime } from '../database/models/anime.model';
|
||||||
|
import util from 'util';
|
||||||
|
import { hashStringMd5 } from '../utils/util';
|
||||||
|
import {
|
||||||
|
animeExtraInfo,
|
||||||
|
getAnimeVideoPromo,
|
||||||
|
getAnimeCharacters,
|
||||||
|
getRelatedAnimesMAL,
|
||||||
|
} from '../utils/util';
|
||||||
|
import urls from '../utils/urls';
|
||||||
|
import { redisClient } from '../database/connection';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
redisClient.get = util.promisify(redisClient.get);
|
||||||
|
|
||||||
|
/*
|
||||||
|
DirectoryController - async functions controlling the directory
|
||||||
|
in the database of MongoDB, functions like getAllDirectory from the DB
|
||||||
|
other functions with realation to the directory, like the season and stuff.
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface TypeAnime {
|
||||||
|
title: string;
|
||||||
|
image: string;
|
||||||
|
genres: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Season {
|
||||||
|
title: string;
|
||||||
|
image: string;
|
||||||
|
malink: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Archive {
|
||||||
|
year: string;
|
||||||
|
seasons: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class DirectoryController {
|
||||||
|
async getAllDirectory(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { genres } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (genres === 'sfw') {
|
||||||
|
await AnimeModel.find(
|
||||||
|
{
|
||||||
|
genres: { $nin: ['ecchi', 'Ecchi'] },
|
||||||
|
},
|
||||||
|
(err: any, docs: Anime[]) => {
|
||||||
|
let directory: any[] = [];
|
||||||
|
|
||||||
|
for (const item of docs) {
|
||||||
|
directory.push({
|
||||||
|
id: item.id,
|
||||||
|
title: item.title,
|
||||||
|
mal_id: item.mal_id,
|
||||||
|
poster: item.poster,
|
||||||
|
type: item.type,
|
||||||
|
genres: item.genres,
|
||||||
|
score: item.score,
|
||||||
|
source: item.source,
|
||||||
|
description: item.description,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (directory.length > 0) {
|
||||||
|
res.status(200).json({ directory });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await AnimeModel.find((err: any, docs: Anime[]) => {
|
||||||
|
let directory: any[] = [];
|
||||||
|
|
||||||
|
for (const item of docs) {
|
||||||
|
directory.push({
|
||||||
|
id: item.id,
|
||||||
|
title: item.title,
|
||||||
|
mal_id: item.mal_id,
|
||||||
|
poster: item.poster,
|
||||||
|
type: item.type,
|
||||||
|
genres: item.genres,
|
||||||
|
score: item.score,
|
||||||
|
source: item.source,
|
||||||
|
description: item.description,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (directory.length > 0) {
|
||||||
|
res.status(200).json({ directory });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSeason(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { year, type } = req.params;
|
||||||
|
let info: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
const resultQueryRedis: any = await redisClient.get(
|
||||||
|
`season_${hashStringMd5(`${year}:${type}`)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resultQueryRedis) {
|
||||||
|
const resultRedis: any = JSON.parse(resultQueryRedis);
|
||||||
|
|
||||||
|
return res.status(200).json(resultRedis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info = await requestGot(`${urls.BASE_JIKAN}seasons/${year}/${type}`, {
|
||||||
|
scrapy: false,
|
||||||
|
parse: true,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const season: TypeAnime[] = info.data.map((item: any) => {
|
||||||
|
return {
|
||||||
|
title: item.titles.find((x: { type: string; }) => x.type === "Default").title,
|
||||||
|
image: item.images.jpg.image_url,
|
||||||
|
genres: item.genres.map((genre: any) => genre.name),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (season.length > 0) {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
/* Set the key in the redis cache. */
|
||||||
|
|
||||||
|
redisClient.set(
|
||||||
|
`season_${hashStringMd5(`${year}:${type}`)}`,
|
||||||
|
JSON.stringify({ season }),
|
||||||
|
);
|
||||||
|
|
||||||
|
/* After 24hrs expire the key. */
|
||||||
|
|
||||||
|
redisClient.expireat(
|
||||||
|
`season_${hashStringMd5(`${year}:${type}`)}`,
|
||||||
|
parseInt(`${+new Date() / 1000}`, 10) + 7200,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
season,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async allSeasons(req: Request, res: Response, next: NextFunction) {
|
||||||
|
let info: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
const resultQueryRedis: any = await redisClient.get(
|
||||||
|
`allSeasons_${hashStringMd5('allSeasons')}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resultQueryRedis) {
|
||||||
|
const resultRedis: any = JSON.parse(resultQueryRedis);
|
||||||
|
|
||||||
|
return res.status(200).json(resultRedis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info = await requestGot(`${urls.BASE_JIKAN}seasons`, {
|
||||||
|
parse: true,
|
||||||
|
scrapy: false,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const archive: Archive[] = info.data.map((item: any) => {
|
||||||
|
return {
|
||||||
|
year: item.year,
|
||||||
|
seasons: item.seasons,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (archive.length > 0) {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
/* Set the key in the redis cache. */
|
||||||
|
|
||||||
|
redisClient.set(
|
||||||
|
`allSeasons_${hashStringMd5('allSeasons')}`,
|
||||||
|
JSON.stringify({ archive }),
|
||||||
|
);
|
||||||
|
|
||||||
|
/* After 24hrs expire the key. */
|
||||||
|
|
||||||
|
redisClient.expireat(
|
||||||
|
`allSeasons_${hashStringMd5('allSeasons')}`,
|
||||||
|
parseInt(`${+new Date() / 1000}`, 10) + 7200,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ archive });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async laterSeasons(req: Request, res: Response, next: NextFunction) {
|
||||||
|
let info: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
const resultQueryRedis: any = await redisClient.get(
|
||||||
|
`laterSeasons_${hashStringMd5('laterSeasons')}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resultQueryRedis) {
|
||||||
|
const resultRedis: any = JSON.parse(resultQueryRedis);
|
||||||
|
|
||||||
|
return res.status(200).json(resultRedis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info = await requestGot(`${urls.BASE_JIKAN}seasons/upcoming`, {
|
||||||
|
parse: true,
|
||||||
|
scrapy: false,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const future: Season[] = info.data.map((item: any) => {
|
||||||
|
return {
|
||||||
|
title: item.titles.find((x: { type: string; }) => x.type === "Default").title,
|
||||||
|
image: item.images.jpg.image_url,
|
||||||
|
malink: item.url,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (future.length > 0) {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
/* Set the key in the redis cache. */
|
||||||
|
|
||||||
|
redisClient.set(
|
||||||
|
`laterSeasons_${hashStringMd5('laterSeasons')}`,
|
||||||
|
JSON.stringify({ future }),
|
||||||
|
);
|
||||||
|
|
||||||
|
/* After 24hrs expire the key. */
|
||||||
|
|
||||||
|
redisClient.expireat(
|
||||||
|
`laterSeasons_${hashStringMd5('laterSeasons')}`,
|
||||||
|
parseInt(`${+new Date() / 1000}`, 10) + 7200,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ future });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMoreInfo(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { title } = req.params;
|
||||||
|
let resultQuery: Anime | null;
|
||||||
|
let resultAnime: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
const resultQueryRedis: any = await redisClient.get(
|
||||||
|
`moreInfo_${hashStringMd5(title)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resultQueryRedis) {
|
||||||
|
const resultRedis: any = JSON.parse(resultQueryRedis);
|
||||||
|
|
||||||
|
return res.status(200).json(resultRedis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resultQuery = await AnimeModel.findOne({
|
||||||
|
$or: [{ title: { $eq: title } }, { title: { $eq: `${title} (TV)` } }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const extraInfo: any = await animeExtraInfo(resultQuery!.mal_id);
|
||||||
|
|
||||||
|
resultAnime = {
|
||||||
|
//aruppi_key: hashStringMd5(title),
|
||||||
|
title: resultQuery?.title,
|
||||||
|
poster: resultQuery?.poster,
|
||||||
|
synopsis: resultQuery?.description,
|
||||||
|
status: !extraInfo.aired.to ? 'En emisión' : 'Finalizado',
|
||||||
|
type: resultQuery?.type,
|
||||||
|
rating: resultQuery?.score,
|
||||||
|
genres: resultQuery?.genres,
|
||||||
|
moreInfo: [extraInfo],
|
||||||
|
promo: await getAnimeVideoPromo(resultQuery!.mal_id),
|
||||||
|
characters: await getAnimeCharacters(resultQuery!.mal_id),
|
||||||
|
related: await getRelatedAnimesMAL(resultQuery!.mal_id),
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultAnime) {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
/* Set the key in the redis cache. */
|
||||||
|
|
||||||
|
redisClient.set(
|
||||||
|
`moreInfo_${hashStringMd5(title)}`,
|
||||||
|
JSON.stringify(resultAnime),
|
||||||
|
);
|
||||||
|
|
||||||
|
/* After 24hrs expire the key. */
|
||||||
|
|
||||||
|
redisClient.expireat(
|
||||||
|
`moreInfo_${hashStringMd5(title)}`,
|
||||||
|
parseInt(`${+new Date() / 1000}`, 10) + 7200,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json(resultAnime);
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { title } = req.params;
|
||||||
|
let results: Anime[] | null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
results = await AnimeModel.find({
|
||||||
|
title: { $regex: new RegExp(title, 'i') },
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultAnimes: any[] = results.map((item: any) => {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
title: item.title,
|
||||||
|
type: item.type,
|
||||||
|
image: item.poster,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resultAnimes.length > 0) {
|
||||||
|
res.status(200).json({ search: resultAnimes });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAnimeGenres(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { genre, order, page } = req.params;
|
||||||
|
let result: any;
|
||||||
|
|
||||||
|
const genres: any = {
|
||||||
|
accion: 'Acción',
|
||||||
|
'artes-marciales': 'Artes Marciales',
|
||||||
|
aventura: 'Aventuras',
|
||||||
|
carreras: 'Carreras',
|
||||||
|
'ciencia-ficcion': 'Ciencia Ficción',
|
||||||
|
comedia: 'Comedia',
|
||||||
|
demencia: 'Demencia',
|
||||||
|
demonios: 'Demonios',
|
||||||
|
deportes: 'Deportes',
|
||||||
|
drama: 'Drama',
|
||||||
|
ecchi: 'Ecchi',
|
||||||
|
escolares: 'Escolares',
|
||||||
|
espacial: 'Espacial',
|
||||||
|
fantasia: 'Fantasía',
|
||||||
|
harem: 'Harem',
|
||||||
|
historico: 'Historico',
|
||||||
|
infantil: 'Infantil',
|
||||||
|
josei: 'Josei',
|
||||||
|
juegos: 'Juegos',
|
||||||
|
magia: 'Magia',
|
||||||
|
mecha: 'Mecha',
|
||||||
|
militar: 'Militar',
|
||||||
|
misterio: 'Misterio',
|
||||||
|
musica: 'Música',
|
||||||
|
parodia: 'Parodia',
|
||||||
|
policia: 'Policía',
|
||||||
|
psicologico: 'Psicológico',
|
||||||
|
'recuentos-de-la-vida': 'Recuentos de la vida',
|
||||||
|
romance: 'Romance',
|
||||||
|
samurai: 'Samurai',
|
||||||
|
seinen: 'Seinen',
|
||||||
|
shoujo: 'Shoujo',
|
||||||
|
shounen: 'Shounen',
|
||||||
|
sobrenatural: 'Sobrenatural',
|
||||||
|
superpoderes: 'Superpoderes',
|
||||||
|
suspenso: 'Suspenso',
|
||||||
|
terror: 'Terror',
|
||||||
|
vampiros: 'Vampiros',
|
||||||
|
yaoi: 'Yaoi',
|
||||||
|
yuri: 'Yuri',
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (genre === undefined && order === undefined && page === undefined) {
|
||||||
|
result = await AnimeModel.aggregate([{ $sample: { size: 25 } }]);
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line no-prototype-builtins
|
||||||
|
if (genres.hasOwnProperty(genre)) {
|
||||||
|
if (page !== undefined && parseInt(page) > 1) {
|
||||||
|
if (order === 'asc') {
|
||||||
|
result = await AnimeModel.find({ genres: genres[genre] })
|
||||||
|
.limit(25)
|
||||||
|
.skip(25 * parseInt(page))
|
||||||
|
.sort({ title: 'ascending' });
|
||||||
|
} else if (order === 'desc') {
|
||||||
|
result = await AnimeModel.find({ genres: genres[genre] })
|
||||||
|
.limit(25)
|
||||||
|
.skip(25 * parseInt(page))
|
||||||
|
.sort({ title: 'descending' });
|
||||||
|
} else {
|
||||||
|
result = await AnimeModel.find({ genres: genres[genre] })
|
||||||
|
.limit(25)
|
||||||
|
.skip(25 * parseInt(page));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (order === 'asc') {
|
||||||
|
result = await AnimeModel.find({ genres: genres[genre] })
|
||||||
|
.limit(25)
|
||||||
|
.sort({ title: 'ascending' });
|
||||||
|
} else if (order === 'desc') {
|
||||||
|
result = await AnimeModel.find({ genres: genres[genre] })
|
||||||
|
.limit(25)
|
||||||
|
.sort({ title: 'descending' });
|
||||||
|
} else {
|
||||||
|
result = await AnimeModel.find({ genres: genres[genre] }).limit(
|
||||||
|
25,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const animes: any[] = result.map((item: any) => {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
title: item.title.trim(),
|
||||||
|
mention: genre,
|
||||||
|
page: page,
|
||||||
|
poster: item.poster,
|
||||||
|
banner: item.banner,
|
||||||
|
synopsis: item.synopsis,
|
||||||
|
type: item.type,
|
||||||
|
rating: item.rating,
|
||||||
|
genre: item.genre,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (animes.length > 0) {
|
||||||
|
res.status(200).json({ animes });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,818 @@
|
|||||||
|
import { NextFunction, Request, Response } from 'express';
|
||||||
|
import Parser from 'rss-parser';
|
||||||
|
import urls from '../utils/urls';
|
||||||
|
import { obtainPreviewNews } from '../utils/obtainPreviews';
|
||||||
|
import { requestGot } from '../utils/requestCall';
|
||||||
|
import RadioStationModel, {
|
||||||
|
RadioStation,
|
||||||
|
} from '../database/models/radiostation.model';
|
||||||
|
import ThemeModel, { Theme } from '../database/models/theme.model';
|
||||||
|
import ThemeParser from '../utils/animeTheme';
|
||||||
|
import { structureThemes } from '../utils/util';
|
||||||
|
import { getThemes } from '../utils/util';
|
||||||
|
import WaifuModel, { Waifu } from '../database/models/waifu.model';
|
||||||
|
import util from 'util';
|
||||||
|
import { hashStringMd5 } from '../utils/util';
|
||||||
|
import { redisClient } from '../database/connection';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
redisClient.get = util.promisify(redisClient.get);
|
||||||
|
|
||||||
|
/*
|
||||||
|
UtilsController - controller to parse the
|
||||||
|
feed and get news, all with scraping and
|
||||||
|
parsing RSS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const themeParser = new ThemeParser();
|
||||||
|
|
||||||
|
type CustomFeed = {
|
||||||
|
foo: string;
|
||||||
|
};
|
||||||
|
type CustomItem = {
|
||||||
|
bar: number;
|
||||||
|
itunes: { duration: string; image: string };
|
||||||
|
'content:encoded': string;
|
||||||
|
'content:encodedSnippet': string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const parser: Parser<CustomFeed, CustomItem> = new Parser({
|
||||||
|
customFields: {
|
||||||
|
feed: ['foo'],
|
||||||
|
item: ['bar'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface News {
|
||||||
|
title?: string;
|
||||||
|
url?: string;
|
||||||
|
author?: string;
|
||||||
|
thumbnail?: string;
|
||||||
|
content?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Podcast {
|
||||||
|
title?: string;
|
||||||
|
duration?: string;
|
||||||
|
created?: Date | string;
|
||||||
|
mp3?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface rssPage {
|
||||||
|
url: string;
|
||||||
|
author: string;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class UtilsController {
|
||||||
|
async getAnitakume(req: Request, res: Response, next: NextFunction) {
|
||||||
|
let feed: CustomFeed & Parser.Output<CustomItem>;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
const resultQueryRedis: any = await redisClient.get(
|
||||||
|
`anitakume_${hashStringMd5('anitakume')}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resultQueryRedis) {
|
||||||
|
const resultRedis: any = JSON.parse(resultQueryRedis);
|
||||||
|
|
||||||
|
return res.status(200).json(resultRedis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
feed = await parser.parseURL(urls.BASE_IVOOX);
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const podcast: Podcast[] = [];
|
||||||
|
const monthNames = [
|
||||||
|
'Enero',
|
||||||
|
'Febrero',
|
||||||
|
'Marzo',
|
||||||
|
'Abril',
|
||||||
|
'Mayo',
|
||||||
|
'Junio',
|
||||||
|
'Julio',
|
||||||
|
'Agosto',
|
||||||
|
'Septiembre',
|
||||||
|
'Octubre',
|
||||||
|
'Noviembre',
|
||||||
|
'Diciembre',
|
||||||
|
];
|
||||||
|
|
||||||
|
feed.items.forEach((item: any) => {
|
||||||
|
const date: Date = new Date(item.pubDate!);
|
||||||
|
|
||||||
|
const formattedObject: Podcast = {
|
||||||
|
title: item.title,
|
||||||
|
duration: item.itunes.duration,
|
||||||
|
created: `${date.getDate()} de ${
|
||||||
|
monthNames[date.getMonth()]
|
||||||
|
} de ${date.getFullYear()}`,
|
||||||
|
mp3: item.enclosure?.url,
|
||||||
|
};
|
||||||
|
|
||||||
|
podcast.push(formattedObject);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (podcast.length > 0) {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
/* Set the key in the redis cache. */
|
||||||
|
|
||||||
|
redisClient.set(
|
||||||
|
`anitakume_${hashStringMd5('anitakume')}`,
|
||||||
|
JSON.stringify({ podcast }),
|
||||||
|
);
|
||||||
|
|
||||||
|
/* After 24hrs expire the key. */
|
||||||
|
|
||||||
|
redisClient.expireat(
|
||||||
|
`anitakume_${hashStringMd5('anitakume')}`,
|
||||||
|
parseInt(`${+new Date() / 1000}`, 10) + 7200,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ podcast });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getNews(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const news: News[] = [];
|
||||||
|
const pagesRss: rssPage[] = [
|
||||||
|
{ url: urls.BASE_KUDASAI, author: 'Kudasai', content: 'content_encoded' },
|
||||||
|
{
|
||||||
|
url: urls.BASE_RAMENPARADOS,
|
||||||
|
author: 'Ramen para dos',
|
||||||
|
content: 'content',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: urls.BASE_CRUNCHYROLL,
|
||||||
|
author: 'Crunchyroll',
|
||||||
|
content: 'content_encoded',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
const resultQueryRedis: any = await redisClient.get(
|
||||||
|
`news_${hashStringMd5('news')}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resultQueryRedis) {
|
||||||
|
const resultRedis: any = JSON.parse(resultQueryRedis);
|
||||||
|
|
||||||
|
return res.status(200).json(resultRedis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const rssPage of pagesRss) {
|
||||||
|
const feed = await parser.parseURL(rssPage.url);
|
||||||
|
|
||||||
|
feed.items.forEach((item: any) => {
|
||||||
|
const formattedObject: News = {
|
||||||
|
title: item.title,
|
||||||
|
url: item.link,
|
||||||
|
author: feed.title?.includes('Crunchyroll')
|
||||||
|
? 'Crunchyroll'
|
||||||
|
: feed.title,
|
||||||
|
thumbnail: obtainPreviewNews(item['content:encoded']),
|
||||||
|
content: item['content:encoded'],
|
||||||
|
};
|
||||||
|
|
||||||
|
news.push(formattedObject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (news.length > 0) {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
/* Set the key in the redis cache. */
|
||||||
|
|
||||||
|
redisClient.set(
|
||||||
|
`news_${hashStringMd5('news')}`,
|
||||||
|
JSON.stringify({ news }),
|
||||||
|
);
|
||||||
|
|
||||||
|
/* After 24hrs expire the key. */
|
||||||
|
|
||||||
|
redisClient.expireat(
|
||||||
|
`news_${hashStringMd5('news')}`,
|
||||||
|
parseInt(`${+new Date() / 1000}`, 10) + 7200,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ news });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getImages(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { title } = req.params;
|
||||||
|
let data: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
const resultQueryRedis: any = await redisClient.get(
|
||||||
|
`images_${hashStringMd5(title)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resultQueryRedis) {
|
||||||
|
const resultRedis: any = JSON.parse(resultQueryRedis);
|
||||||
|
|
||||||
|
return res.status(200).json(resultRedis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data = await requestGot(
|
||||||
|
`${urls.BASE_QWANT}t=images&q=${encodeURIComponent(
|
||||||
|
title,
|
||||||
|
)}&count=51&locale=es_ES&safesearch=1`,
|
||||||
|
{ scrapy: false, parse: true, spoof: true, },
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const results: any[] = data.data.result.items.map((item: any) => {
|
||||||
|
return {
|
||||||
|
type: item.thumb_type,
|
||||||
|
thumbnail: `${item.thumbnail}`,
|
||||||
|
fullsize: `${item.media_fullsize}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (results.length > 0) {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
/* Set the key in the redis cache. */
|
||||||
|
|
||||||
|
redisClient.set(
|
||||||
|
`images_${hashStringMd5(title)}`,
|
||||||
|
JSON.stringify({ images: results }),
|
||||||
|
);
|
||||||
|
|
||||||
|
/* After 24hrs expire the key. */
|
||||||
|
|
||||||
|
redisClient.expireat(
|
||||||
|
`images_${hashStringMd5(title)}`,
|
||||||
|
parseInt(`${+new Date() / 1000}`, 10) + 7200,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.set('Cache-Control', 'max-age=604800');
|
||||||
|
res.status(200).json({ images: results });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getVideos(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { channelId } = req.params;
|
||||||
|
let data: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
const resultQueryRedis: any = await redisClient.get(
|
||||||
|
`videos_${hashStringMd5(channelId)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resultQueryRedis) {
|
||||||
|
const resultRedis: any = JSON.parse(resultQueryRedis);
|
||||||
|
|
||||||
|
return res.status(200).json(resultRedis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data = await requestGot(
|
||||||
|
`${urls.BASE_YOUTUBE}${channelId}&part=snippet,id&order=date&maxResults=50`,
|
||||||
|
{ scrapy: false, parse: true },
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const results: any[] = data.items.map((item: any) => {
|
||||||
|
return {
|
||||||
|
title: item.snippet.title,
|
||||||
|
videoId: item.id.videoId,
|
||||||
|
thumbDefault: item.snippet.thumbnails.default.url,
|
||||||
|
thumbMedium: item.snippet.thumbnails.medium.url,
|
||||||
|
thumbHigh: item.snippet.thumbnails.high.url,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (results.length > 0) {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
/* Set the key in the redis cache. */
|
||||||
|
|
||||||
|
redisClient.set(
|
||||||
|
`videos_${hashStringMd5(channelId)}`,
|
||||||
|
JSON.stringify({ videos: results }),
|
||||||
|
);
|
||||||
|
|
||||||
|
/* After 24hrs expire the key. */
|
||||||
|
|
||||||
|
redisClient.expireat(
|
||||||
|
`videos_${hashStringMd5(channelId)}`,
|
||||||
|
parseInt(`${+new Date() / 1000}`, 10) + 7200,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ videos: results });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPlaylists(req: Request, res: Response, next: NextFunction) {
|
||||||
|
|
||||||
|
const { playlistId } = req.params;
|
||||||
|
let data: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
|
||||||
|
const resultQueryRedis: any = redisClient.get(
|
||||||
|
`videos_${hashStringMd5(playlistId)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resultQueryRedis) {
|
||||||
|
const resultRedis: any = JSON.parse(resultQueryRedis);
|
||||||
|
return res.status(200).json(resultRedis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data = await requestGot(
|
||||||
|
`${urls.BASE_YOUTUBE_PLAYLIST}${playlistId}`,
|
||||||
|
{ scrapy: false, parse: true },
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const results: any[] = data.items.map((item: any) => {
|
||||||
|
return {
|
||||||
|
title: item.snippet.title,
|
||||||
|
videoId: item.id.videoId,
|
||||||
|
thumbDefault: item.snippet.thumbnails.default.url,
|
||||||
|
thumbMedium: item.snippet.thumbnails.medium.url,
|
||||||
|
thumbHigh: item.snippet.thumbnails.high.url,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (results.length > 0) {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
/!* Set the key in the redis cache. *!/
|
||||||
|
|
||||||
|
redisClient.set(
|
||||||
|
`videos_${hashStringMd5(playlistId)}`,
|
||||||
|
JSON.stringify({ videos: results }),
|
||||||
|
);
|
||||||
|
|
||||||
|
/!* After 24hrs expire the key. *!/
|
||||||
|
|
||||||
|
redisClient.expireat(
|
||||||
|
`videos_${hashStringMd5(playlistId)}`,
|
||||||
|
parseInt(`${+new Date() / 1000}`, 10) + 7200,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ videos: results });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSectionVideos(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { type } = req.params;
|
||||||
|
let y1: any, y2: any, y3: any;
|
||||||
|
let data: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (type === 'learn') {
|
||||||
|
data = await requestGot(
|
||||||
|
`${urls.BASE_YOUTUBE}UCCyQwSS6m2mVB0-H2FOFJtw&part=snippet,id&order=date&maxResults=50`,
|
||||||
|
{ parse: true, scrapy: false },
|
||||||
|
);
|
||||||
|
} else if (type === 'amv') {
|
||||||
|
y1 = await requestGot(
|
||||||
|
`${urls.BASE_YOUTUBE}UCkTFkshjAsLMKwhAe1uPC1A&part=snippet,id&order=date&maxResults=25`,
|
||||||
|
{ parse: true, scrapy: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
y2 = await requestGot(
|
||||||
|
`${urls.BASE_YOUTUBE}UC2cpvlLeowpqnR6bQofwNew&part=snippet,id&order=date&maxResults=25`,
|
||||||
|
{ parse: true, scrapy: false },
|
||||||
|
);
|
||||||
|
} else if (type === 'produccer') {
|
||||||
|
y1 = await requestGot(
|
||||||
|
`${urls.BASE_YOUTUBE}UC-5MT-BUxTzkPTWMediyV0w&part=snippet,id&order=date&maxResults=25`,
|
||||||
|
{ parse: true, scrapy: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
y2 = await requestGot(
|
||||||
|
`${urls.BASE_YOUTUBE}UCwUeTOXP3DD9DIvHttowuSA&part=snippet,id&order=date&maxResults=25`,
|
||||||
|
{ parse: true, scrapy: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
y3 = await requestGot(
|
||||||
|
`${urls.BASE_YOUTUBE}UCA8Vj7nN8bzT3rsukD2ypUg&part=snippet,id&order=date&maxResults=25`,
|
||||||
|
{ parse: true, scrapy: false },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data && !y1 && !y2 && !y3) {
|
||||||
|
const results: any[] = data.items.map((item: any) => ({
|
||||||
|
title: item.snippet.title,
|
||||||
|
videoId: item.id.videoId,
|
||||||
|
thumbDefault: item.snippet.thumbnails.default.url,
|
||||||
|
thumbMedium: item.snippet.thumbnails.medium.url,
|
||||||
|
thumbHigh: item.snippet.thumbnails.high.url,
|
||||||
|
}));
|
||||||
|
|
||||||
|
res.status(200).json({ videos: results });
|
||||||
|
} else if (!data && y1 && y2 && !y3) {
|
||||||
|
const results: any[] = y1.items.concat(y2.items).map((item: any) => ({
|
||||||
|
title: item.snippet.title,
|
||||||
|
videoId: item.id.videoId,
|
||||||
|
thumbDefault: item.snippet.thumbnails.default.url,
|
||||||
|
thumbMedium: item.snippet.thumbnails.medium.url,
|
||||||
|
thumbHigh: item.snippet.thumbnails.high.url,
|
||||||
|
}));
|
||||||
|
|
||||||
|
res.status(200).json({ videos: results });
|
||||||
|
} else if (!data && y1 && y2 && y3) {
|
||||||
|
const results: any[] = y1.items
|
||||||
|
.concat(y2.items.concat(y3.items))
|
||||||
|
.map((item: any) => ({
|
||||||
|
title: item.snippet.title,
|
||||||
|
videoId: item.id.videoId,
|
||||||
|
thumbDefault: item.snippet.thumbnails.default.url,
|
||||||
|
thumbMedium: item.snippet.thumbnails.medium.url,
|
||||||
|
thumbHigh: item.snippet.thumbnails.high.url,
|
||||||
|
}));
|
||||||
|
|
||||||
|
res.status(200).json({ videos: results });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRadioStations(req: Request, res: Response, next: NextFunction) {
|
||||||
|
let data: RadioStation[];
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = await RadioStationModel.find();
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const results: any[] = data.map((item: RadioStation) => {
|
||||||
|
return {
|
||||||
|
name: item.name,
|
||||||
|
url: item.url,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (results.length > 0) {
|
||||||
|
res.status(200).json({ stations: results });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllThemes(req: Request, res: Response, next: NextFunction) {
|
||||||
|
let data: Theme[];
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = await ThemeModel.find();
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const results: any[] = data.map((item: Theme) => {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
title: item.title,
|
||||||
|
year: item.year,
|
||||||
|
themes: item.themes,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (results.length > 0) {
|
||||||
|
res.status(200).json({ themes: results });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOpAndEd(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { title } = req.params;
|
||||||
|
let themes: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
const resultQueryRedis: any = await redisClient.get(
|
||||||
|
`oped_${hashStringMd5(title)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (resultQueryRedis) {
|
||||||
|
const resultRedis: any = JSON.parse(resultQueryRedis);
|
||||||
|
|
||||||
|
return res.status(200).json(resultRedis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
themes = await structureThemes(await themeParser.serie(title), true);
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (themes) {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
/* Set the key in the redis cache. */
|
||||||
|
|
||||||
|
redisClient.set(
|
||||||
|
`oped_${hashStringMd5(title)}`,
|
||||||
|
JSON.stringify({ themes }),
|
||||||
|
);
|
||||||
|
|
||||||
|
/* After 24hrs expire the key. */
|
||||||
|
|
||||||
|
redisClient.expireat(
|
||||||
|
`oped_${hashStringMd5(title)}`,
|
||||||
|
parseInt(`${+new Date() / 1000}`, 10) + 7200,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ themes });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getThemesYear(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { year } = req.params;
|
||||||
|
let themes: any;
|
||||||
|
let resultQueryRedis: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
if (year) {
|
||||||
|
resultQueryRedis = await redisClient.get(
|
||||||
|
`themesyear_${hashStringMd5(year)}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
resultQueryRedis = await redisClient.get(
|
||||||
|
`themesyear_${hashStringMd5('allYear')}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultQueryRedis) {
|
||||||
|
const resultRedis: any = JSON.parse(resultQueryRedis);
|
||||||
|
|
||||||
|
return res.status(200).json(resultRedis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (year === undefined) {
|
||||||
|
themes = await themeParser.allYears();
|
||||||
|
} else {
|
||||||
|
themes = await structureThemes(await themeParser.year(year), false);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (themes.length > 0) {
|
||||||
|
if (redisClient.connected) {
|
||||||
|
/* Set the key in the redis cache. */
|
||||||
|
|
||||||
|
if (year) {
|
||||||
|
redisClient.set(
|
||||||
|
`themesyear_${hashStringMd5(year)}`,
|
||||||
|
JSON.stringify({ themes }),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
redisClient.set(
|
||||||
|
`themesyear_${hashStringMd5('allYear')}`,
|
||||||
|
JSON.stringify({ themes }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* After 24hrs expire the key. */
|
||||||
|
|
||||||
|
if (year) {
|
||||||
|
redisClient.expireat(
|
||||||
|
`themesyear_${hashStringMd5(year)}`,
|
||||||
|
parseInt(`${+new Date() / 1000}`, 10) + 7200,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
redisClient.expireat(
|
||||||
|
`themesyear_${hashStringMd5('allYear')}`,
|
||||||
|
parseInt(`${+new Date() / 1000}`, 10) + 7200,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({ themes });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async randomTheme(req: Request, res: Response, next: NextFunction) {
|
||||||
|
let data: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = await requestGot(`${urls.BASE_THEMEMOE}roulette`, {
|
||||||
|
parse: true,
|
||||||
|
scrapy: false,
|
||||||
|
spoof: true,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const random: any[] = getThemes(data.themes);
|
||||||
|
|
||||||
|
if (random.length > 0) {
|
||||||
|
res.set('Cache-Control', 'no-store');
|
||||||
|
res.status(200).json({ random });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getArtist(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { id } = req.params;
|
||||||
|
let artists: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (id === undefined) {
|
||||||
|
artists = await themeParser.artists();
|
||||||
|
} else {
|
||||||
|
artists = await structureThemes(await themeParser.artist(id), false);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (artists.length > 0) {
|
||||||
|
res.status(200).json({ artists });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDestAnimePlatforms(req: Request, res: Response, next: NextFunction) {
|
||||||
|
let data: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = await requestGot(
|
||||||
|
`${urls.BASE_ARUPPI}res/documents/animelegal/top.json`,
|
||||||
|
{ parse: true, scrapy: false },
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const destPlatforms: any[] = data.map((item: any) => {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
logo: item.logo,
|
||||||
|
link: item.link,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (destPlatforms.length > 0) {
|
||||||
|
res.status(200).json({ destPlatforms });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPlatforms(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { id } = req.params;
|
||||||
|
let data: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (id === undefined) {
|
||||||
|
data = await requestGot(
|
||||||
|
`${urls.BASE_ARUPPI}res/documents/animelegal/typeplatforms.json`,
|
||||||
|
{ parse: true, scrapy: false },
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
id === 'producers' ||
|
||||||
|
id === 'apps' ||
|
||||||
|
id === 'publishers' ||
|
||||||
|
'events'
|
||||||
|
) {
|
||||||
|
data = await requestGot(
|
||||||
|
`${urls.BASE_ARUPPI}res/documents/animelegal/type/${id}.json`,
|
||||||
|
{ parse: true, scrapy: false },
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
data = await requestGot(
|
||||||
|
`${urls.BASE_ARUPPI}res/documents/animelegal/type/${id}.json`,
|
||||||
|
{ parse: true, scrapy: false },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const platforms: any[] = data.map((item: any) => {
|
||||||
|
if (id === undefined) {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
comming: item.comming || false,
|
||||||
|
cover: item.cover,
|
||||||
|
};
|
||||||
|
} else if (
|
||||||
|
id === 'producers' ||
|
||||||
|
id === 'apps' ||
|
||||||
|
id === 'publishers' ||
|
||||||
|
'events'
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
logo: item.logo,
|
||||||
|
cover: item.cover,
|
||||||
|
description: item.description,
|
||||||
|
type: item.type,
|
||||||
|
moreInfo: item.moreInfo,
|
||||||
|
facebook: item.facebook,
|
||||||
|
twitter: item.twitter,
|
||||||
|
instagram: item.instagram,
|
||||||
|
webInfo: item.webInfo,
|
||||||
|
webpage: item.webpage,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
type: item.type,
|
||||||
|
logo: item.logo,
|
||||||
|
cover: item.cover,
|
||||||
|
webpage: item.webpage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (platforms.length > 0) {
|
||||||
|
res.status(200).json({ platforms });
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getWaifuRandom(req: Request, res: Response, next: NextFunction) {
|
||||||
|
let waifuQuery: Waifu[] | null;
|
||||||
|
let waifuResult: any;
|
||||||
|
|
||||||
|
try {
|
||||||
|
waifuQuery = await WaifuModel.aggregate([{ $sample: { size: 1 } }]);
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (waifuQuery.length > 0) {
|
||||||
|
waifuResult = {
|
||||||
|
id: waifuQuery[0].id,
|
||||||
|
name: waifuQuery[0].name,
|
||||||
|
weight: waifuQuery[0].weight,
|
||||||
|
series: waifuQuery[0].series,
|
||||||
|
height: waifuQuery[0].height,
|
||||||
|
birthday: waifuQuery[0].birthday,
|
||||||
|
likes: waifuQuery[0].likes,
|
||||||
|
trash: waifuQuery[0].trash,
|
||||||
|
blood_type: waifuQuery[0].blood_type,
|
||||||
|
hip: waifuQuery[0].hip,
|
||||||
|
bust: waifuQuery[0].bust,
|
||||||
|
description: waifuQuery[0].description,
|
||||||
|
display_picture: waifuQuery[0].display_picture,
|
||||||
|
waist: waifuQuery[0].waist,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (waifuResult) {
|
||||||
|
res.set('Cache-Control', 'no-store');
|
||||||
|
res.status(200).json(waifuResult);
|
||||||
|
} else {
|
||||||
|
res.status(500).json({ message: 'Aruppi lost in the shell' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
import mongoose from 'mongoose';
|
||||||
|
import redis, { RedisClient } from 'redis';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
|
||||||
|
// Configuring dotenv to read the variable from .env file
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Create the connection to the database
|
||||||
|
of mongodb.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const createConnectionMongo = (databaseObj: {
|
||||||
|
port: string | undefined;
|
||||||
|
host: string | undefined;
|
||||||
|
}) => {
|
||||||
|
mongoose.connect(
|
||||||
|
`mongodb://${databaseObj.host}:${databaseObj.port}/anime-directory`,
|
||||||
|
{
|
||||||
|
useNewUrlParser: true,
|
||||||
|
useUnifiedTopology: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
mongoose.connection.on('error', err => {
|
||||||
|
console.log('err', err);
|
||||||
|
});
|
||||||
|
mongoose.connection.on('connected', (err, res) => {
|
||||||
|
console.log('Database connected: mongoose.');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Create the connection to the cache of
|
||||||
|
redis, and exporting the redis client
|
||||||
|
with the call of this file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const redisClient: RedisClient = redis.createClient({
|
||||||
|
host: process.env.REDIS_HOST,
|
||||||
|
port: parseInt(process.env.REDIS_PORT!),
|
||||||
|
password: process.env.REDIS_PASSWORD,
|
||||||
|
retry_strategy: function (options) {
|
||||||
|
if (options.error && options.error.code === 'ECONNREFUSED') {
|
||||||
|
// End reconnecting on a specific error and flush all commands with
|
||||||
|
// a individual error
|
||||||
|
return new Error('The server refused the connection');
|
||||||
|
}
|
||||||
|
if (options.total_retry_time > 1000 * 60 * 60) {
|
||||||
|
// End reconnecting after a specific timeout and flush all commands
|
||||||
|
// with a individual error
|
||||||
|
return new Error('Retry time exhausted');
|
||||||
|
}
|
||||||
|
if (options.attempt > 10) {
|
||||||
|
// End reconnecting with built in error
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
// reconnect after
|
||||||
|
return Math.min(options.attempt * 100, 3000);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
redisClient.on('connect', () => {
|
||||||
|
console.log('Redis connected: redis.');
|
||||||
|
});
|
||||||
|
|
||||||
|
redisClient.on('error', function (err) {
|
||||||
|
console.log('Redis error: ' + err);
|
||||||
|
});
|
@ -0,0 +1,33 @@
|
|||||||
|
import { Document, model, Types, Schema } from 'mongoose';
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is the model for each anime
|
||||||
|
of the directory, the anime model.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface Anime extends Document {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
mal_id: number;
|
||||||
|
poster: string;
|
||||||
|
type: string;
|
||||||
|
genres: Types.Array<string>;
|
||||||
|
score: string;
|
||||||
|
source: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schema for the anime
|
||||||
|
const AnimeSchema: Schema = new Schema({
|
||||||
|
id: { type: String },
|
||||||
|
title: { type: String },
|
||||||
|
mal_id: { type: Number },
|
||||||
|
poster: { type: String },
|
||||||
|
type: { type: String },
|
||||||
|
genres: [{ type: String }],
|
||||||
|
score: { type: String },
|
||||||
|
source: { type: String },
|
||||||
|
description: { type: String },
|
||||||
|
});
|
||||||
|
|
||||||
|
export default model<Anime>('Anime', AnimeSchema);
|
@ -0,0 +1,19 @@
|
|||||||
|
import { Document, model, Types, Schema } from 'mongoose';
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is the model for each genre
|
||||||
|
of the directory, the genre model.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface Genre extends Document {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schema for the theme
|
||||||
|
const GenreSchema: Schema = new Schema({
|
||||||
|
name: { type: String },
|
||||||
|
value: { type: String },
|
||||||
|
});
|
||||||
|
|
||||||
|
export default model<Genre>('Genre', GenreSchema);
|
@ -0,0 +1,19 @@
|
|||||||
|
import { Document, model, Schema } from 'mongoose';
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is the model for each radiostation
|
||||||
|
of the directory, the radiostation model.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface RadioStation extends Document {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schema for the theme
|
||||||
|
const RadioStationSchema: Schema = new Schema({
|
||||||
|
name: { type: String },
|
||||||
|
url: { type: String },
|
||||||
|
});
|
||||||
|
|
||||||
|
export default model<RadioStation>('RadioStation', RadioStationSchema);
|
@ -0,0 +1,29 @@
|
|||||||
|
import { Document, model, Types, Schema } from 'mongoose';
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is the model for each theme
|
||||||
|
of the directory, the theme model.
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface TInterface {
|
||||||
|
title: string;
|
||||||
|
video: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Theme extends Document {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
year: string;
|
||||||
|
themes: Types.Array<TInterface>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schema for the theme
|
||||||
|
const ThemeSchema: Schema = new Schema({
|
||||||
|
id: { type: String },
|
||||||
|
title: { type: String },
|
||||||
|
year: { type: String },
|
||||||
|
themes: [{ type: Object }],
|
||||||
|
});
|
||||||
|
|
||||||
|
export default model<Theme>('Theme', ThemeSchema);
|
@ -0,0 +1,43 @@
|
|||||||
|
import { Document, model, Schema } from 'mongoose';
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is the model for each anime
|
||||||
|
of the directory, the anime model.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface Waifu extends Document {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
weight: string;
|
||||||
|
series: object;
|
||||||
|
height: string;
|
||||||
|
birthday: string;
|
||||||
|
likes: number;
|
||||||
|
trash: number;
|
||||||
|
blood_type: string;
|
||||||
|
hip: string;
|
||||||
|
bust: string;
|
||||||
|
description: string;
|
||||||
|
display_picture: string;
|
||||||
|
waist: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schema for the Waifu
|
||||||
|
const WaifuSchema: Schema = new Schema({
|
||||||
|
id: { type: String },
|
||||||
|
name: { type: String },
|
||||||
|
weight: { type: String },
|
||||||
|
series: { type: Object },
|
||||||
|
height: { type: String },
|
||||||
|
birthday: { type: String },
|
||||||
|
likes: { type: Number },
|
||||||
|
trash: { type: Number },
|
||||||
|
blood_type: { type: String },
|
||||||
|
hip: { type: String },
|
||||||
|
bust: { type: String },
|
||||||
|
description: { type: String },
|
||||||
|
display_picture: { type: String },
|
||||||
|
waist: { type: String },
|
||||||
|
});
|
||||||
|
|
||||||
|
export default model<Waifu>('Waifu', WaifuSchema);
|
@ -1,15 +0,0 @@
|
|||||||
@file:Suppress("unused")
|
|
||||||
|
|
||||||
package com.jeluchu
|
|
||||||
|
|
||||||
import com.jeluchu.core.configuration.initInstallers
|
|
||||||
import com.jeluchu.core.configuration.initRoutes
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.netty.*
|
|
||||||
|
|
||||||
fun main(args: Array<String>) = EngineMain.main(args)
|
|
||||||
|
|
||||||
fun Application.module() {
|
|
||||||
initInstallers()
|
|
||||||
initRoutes()
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package com.jeluchu.core.configuration
|
|
||||||
|
|
||||||
import com.jeluchu.core.messages.ErrorMessages
|
|
||||||
import com.jeluchu.core.models.ErrorResponse
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.serialization.kotlinx.json.*
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.plugins.contentnegotiation.*
|
|
||||||
import io.ktor.server.plugins.statuspages.*
|
|
||||||
import io.ktor.server.response.*
|
|
||||||
|
|
||||||
fun Application.initInstallers() {
|
|
||||||
install(StatusPages) {
|
|
||||||
status(HttpStatusCode.NotFound) { call, _ ->
|
|
||||||
call.respond(HttpStatusCode.NotFound,
|
|
||||||
ErrorResponse(ErrorMessages.NotFound.message)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
install(ContentNegotiation) {
|
|
||||||
json()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package com.jeluchu.core.configuration
|
|
||||||
|
|
||||||
import com.mongodb.client.MongoClients
|
|
||||||
import com.mongodb.client.MongoDatabase
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.config.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Establishes connection with a MongoDB database.
|
|
||||||
*
|
|
||||||
* The following configuration properties (in application.yaml/application.conf) can be specified:
|
|
||||||
* * `db.mongo.connectionStrings` connectionStrings for your MongoDB
|
|
||||||
* * `db.mongo.database.name` name of the database
|
|
||||||
*
|
|
||||||
* IMPORTANT NOTE: in order to make MongoDB connection working, you have to start a MongoDB server first.
|
|
||||||
* See the instructions here: https://www.mongodb.com/docs/manual/administration/install-community/
|
|
||||||
* all the paramaters above
|
|
||||||
*
|
|
||||||
* @returns [MongoDatabase] instance
|
|
||||||
* */
|
|
||||||
fun Application.connectToMongoDB(): MongoDatabase {
|
|
||||||
val connectionStrings = environment.config.tryGetString("db.mongo.connectionStrings") ?: "mongodb://"
|
|
||||||
val databaseName = environment.config.tryGetString("db.mongo.database.name") ?: "aruppi"
|
|
||||||
|
|
||||||
val mongoClient = MongoClients.create(connectionStrings)
|
|
||||||
val database = mongoClient.getDatabase(databaseName)
|
|
||||||
|
|
||||||
monitor.subscribe(ApplicationStopped) {
|
|
||||||
mongoClient.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
return database
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package com.jeluchu.core.configuration
|
|
||||||
|
|
||||||
import com.jeluchu.features.anime.routes.animeEndpoints
|
|
||||||
import com.mongodb.client.MongoDatabase
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
|
|
||||||
fun Application.initRoutes(
|
|
||||||
mongoDatabase: MongoDatabase = connectToMongoDB()
|
|
||||||
) = routing {
|
|
||||||
route("api/v5") {
|
|
||||||
initDocumentation()
|
|
||||||
animeEndpoints(mongoDatabase)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
package com.jeluchu.core.configuration
|
|
||||||
|
|
||||||
import io.ktor.server.plugins.swagger.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
|
|
||||||
fun Route.initDocumentation() {
|
|
||||||
swaggerUI(
|
|
||||||
path = "/",
|
|
||||||
swaggerFile = "openapi/documentation.yaml"
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,115 +0,0 @@
|
|||||||
package com.jeluchu.core.extensions
|
|
||||||
|
|
||||||
import org.bson.Document
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
fun Document.getStringSafe(key: String, defaultValue: String = ""): String {
|
|
||||||
return try {
|
|
||||||
when (val value = this[key]) {
|
|
||||||
is String -> value
|
|
||||||
is Number, is Boolean -> value.toString()
|
|
||||||
else -> defaultValue
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
defaultValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Document.getIntSafe(key: String, defaultValue: Int = 0): Int {
|
|
||||||
return try {
|
|
||||||
when (val value = this[key]) {
|
|
||||||
is Int -> value
|
|
||||||
is Number -> value.toInt()
|
|
||||||
is String -> value.toIntOrNull() ?: defaultValue
|
|
||||||
else -> defaultValue
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
defaultValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Document.getDoubleSafe(key: String, defaultValue: Double = 0.0): Double {
|
|
||||||
return try {
|
|
||||||
when (val value = this[key]) {
|
|
||||||
is Double -> value
|
|
||||||
is Number -> value.toDouble()
|
|
||||||
is String -> value.toDoubleOrNull() ?: defaultValue
|
|
||||||
else -> defaultValue
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
defaultValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Document.getFloatSafe(key: String, defaultValue: Float = 0.0f): Float {
|
|
||||||
return try {
|
|
||||||
when (val value = this[key]) {
|
|
||||||
is Float -> value
|
|
||||||
is Number -> value.toFloat()
|
|
||||||
is String -> value.toFloatOrNull() ?: defaultValue
|
|
||||||
else -> defaultValue
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
defaultValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Document.getLongSafe(key: String, defaultValue: Long = 0L): Long {
|
|
||||||
return try {
|
|
||||||
val value = this.get(key)
|
|
||||||
when (value) {
|
|
||||||
is Long -> value
|
|
||||||
is Number -> value.toLong()
|
|
||||||
is String -> value.toLongOrNull() ?: defaultValue
|
|
||||||
else -> defaultValue
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
defaultValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Document.getBooleanSafe(key: String, defaultValue: Boolean = false): Boolean {
|
|
||||||
return try {
|
|
||||||
when (val value = this[key]) {
|
|
||||||
is Boolean -> value
|
|
||||||
is String -> value.toBoolean()
|
|
||||||
is Number -> value.toInt() != 0
|
|
||||||
else -> defaultValue
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
defaultValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Document.getDateSafe(key: String, defaultValue: Date = Date(0)): Date {
|
|
||||||
return try {
|
|
||||||
when (val value = this[key]) {
|
|
||||||
is Date -> value
|
|
||||||
is String -> SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").parse(value) ?: defaultValue
|
|
||||||
else -> defaultValue
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
defaultValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <reified T> Document.getListSafe(key: String, defaultValue: List<T> = emptyList()): List<T> {
|
|
||||||
return try {
|
|
||||||
when (val value = this[key]) {
|
|
||||||
is List<*> -> value.filterIsInstance<T>()
|
|
||||||
else -> defaultValue
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
defaultValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Document.getDocumentSafe(key: String): Document? {
|
|
||||||
return try {
|
|
||||||
val value = this[key]
|
|
||||||
if (value is Document) value else null
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package com.jeluchu.core.extensions
|
|
||||||
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
fun Route.getToJson(
|
|
||||||
path: String,
|
|
||||||
request: suspend RoutingContext.() -> Unit
|
|
||||||
): Route = get(path) {
|
|
||||||
call.response.headers.append(HttpHeaders.ContentType, ContentType.Application.Json.toString())
|
|
||||||
withContext(Dispatchers.IO) { request() }
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package com.jeluchu.core.messages
|
|
||||||
|
|
||||||
sealed class ErrorMessages(val message: String) {
|
|
||||||
data class Custom(val error: String) : ErrorMessages(error)
|
|
||||||
data object NotFound : ErrorMessages("Nyaaaaaaaan! This request has not been found by our alpaca-neko")
|
|
||||||
data object AnimeNotFound : ErrorMessages("This malId is not in our database")
|
|
||||||
data object InvalidMalId : ErrorMessages("The provided id of malId is invalid")
|
|
||||||
data object InvalidInput : ErrorMessages("Invalid input provided")
|
|
||||||
data object UnauthorizedMongo : ErrorMessages("Check the MongoDb Connection String to be able to correctly access this request.")
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
package com.jeluchu.core.messages
|
|
||||||
|
|
||||||
sealed class GeneralMessages(val message: String) {
|
|
||||||
data class Custom(val info: String) : GeneralMessages(info)
|
|
||||||
data object DocumentationReference : GeneralMessages("More information on the Aruppi API can be found in the official documentation at: http://0.0.0.0:8080/swagger")
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
package com.jeluchu.core.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class ErrorResponse(
|
|
||||||
val error: String
|
|
||||||
)
|
|
@ -1,8 +0,0 @@
|
|||||||
package com.jeluchu.core.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class MessageResponse(
|
|
||||||
val message: String
|
|
||||||
)
|
|
@ -1,8 +0,0 @@
|
|||||||
package com.jeluchu.core.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class SuccessResponse(
|
|
||||||
val success: String
|
|
||||||
)
|
|
@ -1,6 +0,0 @@
|
|||||||
package com.jeluchu.core.utils
|
|
||||||
|
|
||||||
object Routes {
|
|
||||||
const val DIRECTORY = "/directory"
|
|
||||||
const val ANIME_DETAILS = "/anime/{id}"
|
|
||||||
}
|
|
@ -1,213 +0,0 @@
|
|||||||
package com.jeluchu.features.anime.mappers
|
|
||||||
|
|
||||||
import com.example.models.*
|
|
||||||
import com.jeluchu.core.extensions.*
|
|
||||||
import com.jeluchu.features.anime.models.directory.AnimeDirectoryEntity
|
|
||||||
import org.bson.Document
|
|
||||||
|
|
||||||
fun documentToAnimeDirectoryEntity(doc: Document) = AnimeDirectoryEntity(
|
|
||||||
rank = doc.getIntSafe("rank"),
|
|
||||||
year = doc.getIntSafe("year"),
|
|
||||||
url = doc.getStringSafe("url"),
|
|
||||||
malId = doc.getIntSafe("malId"),
|
|
||||||
type = doc.getStringSafe("type"),
|
|
||||||
score = doc.getStringSafe("score"),
|
|
||||||
title = doc.getStringSafe("title"),
|
|
||||||
status = doc.getStringSafe("status"),
|
|
||||||
season = doc.getStringSafe("season"),
|
|
||||||
poster = doc.getStringSafe("poster"),
|
|
||||||
airing = doc.getBooleanSafe("airing"),
|
|
||||||
genres = doc.getListSafe<String>("genres"),
|
|
||||||
episodesCount = doc.getIntSafe("episodesCount")
|
|
||||||
)
|
|
||||||
|
|
||||||
fun documentToMoreInfoEntity(doc: Document): MoreInfoEntity {
|
|
||||||
return MoreInfoEntity(
|
|
||||||
id = doc.getLongSafe("id"),
|
|
||||||
malId = doc.getIntSafe("malId"),
|
|
||||||
title = doc.getStringSafe("title"),
|
|
||||||
poster = doc.getStringSafe("poster"),
|
|
||||||
cover = doc.getStringSafe("cover"),
|
|
||||||
genres = doc.getListSafe<String>("genres"),
|
|
||||||
synopsis = doc.getStringSafe("synopsis"),
|
|
||||||
episodes = doc.getListSafe<Document>("episodes").map { documentToMergedEpisode(it) },
|
|
||||||
episodesCount = doc.getIntSafe("episodesCount", 0),
|
|
||||||
score = doc.getStringSafe("score"),
|
|
||||||
staff = doc.getListSafe<Document>("staff").map { documentToStaff(it) },
|
|
||||||
characters = doc.getListSafe<Document>("characters").map { documentToCharacter(it) },
|
|
||||||
status = doc.getStringSafe("status"),
|
|
||||||
type = doc.getStringSafe("type"),
|
|
||||||
url = doc.getStringSafe("url"),
|
|
||||||
promo = doc.getDocumentSafe("promo")?.let { documentToVideoPromo(it) } ?: VideoPromo(),
|
|
||||||
source = doc.getStringSafe("source"),
|
|
||||||
duration = doc.getStringSafe("duration"),
|
|
||||||
rank = doc.getIntSafe("rank", 0),
|
|
||||||
titles = doc.getListSafe<Document>("titles").map { documentToAlternativeTitles(it) },
|
|
||||||
airing = doc.getBooleanSafe("airing"),
|
|
||||||
aired = doc.getDocumentSafe("aired")?.let { documentToAiringTime(it) } ?: AiringTime(),
|
|
||||||
broadcast = doc.getDocumentSafe("broadcast")?.let { documentToAnimeBroadcast(it) } ?: AnimeBroadcast(),
|
|
||||||
season = doc.getStringSafe("season"),
|
|
||||||
year = doc.getIntSafe("year", 0),
|
|
||||||
external = doc.getListSafe<Document>("external").map { documentToExternalLinks(it) },
|
|
||||||
streaming = doc.getListSafe<Document>("streaming").map { documentToExternalLinks(it) },
|
|
||||||
studios = doc.getListSafe<Document>("studios").map { documentToCompanies(it) },
|
|
||||||
licensors = doc.getListSafe<Document>("licensors").map { documentToCompanies(it) },
|
|
||||||
producers = doc.getListSafe<Document>("producers").map { documentToCompanies(it) },
|
|
||||||
theme = doc.getDocumentSafe("theme")?.let { documentToThemes(it) } ?: Themes(),
|
|
||||||
relations = doc.getListSafe<Document>("relations").map { documentToRelated(it) },
|
|
||||||
stats = doc.getDocumentSafe("stats")?.let { documentToStatistics(it) } ?: Statistics(),
|
|
||||||
gallery = doc.getListSafe<Document>("gallery").map { documentToImageMediaEntity(it) },
|
|
||||||
episodeSource = doc.getStringSafe("episodeSource")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun documentToActor(doc: Document): Actor {
|
|
||||||
return Actor(
|
|
||||||
person = doc.getDocumentSafe("person")?.let { documentToIndividual(it) } ?: Individual(),
|
|
||||||
language = doc.getStringSafe("language")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun documentToAiringTime(doc: Document): AiringTime {
|
|
||||||
return AiringTime(
|
|
||||||
from = doc.getStringSafe("from"),
|
|
||||||
to = doc.getStringSafe("to")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun documentToAlternativeTitles(doc: Document): AlternativeTitles {
|
|
||||||
return AlternativeTitles(
|
|
||||||
title = doc.getStringSafe("title"),
|
|
||||||
type = doc.getStringSafe("type")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun documentToAnimeBroadcast(doc: Document): AnimeBroadcast {
|
|
||||||
return AnimeBroadcast(
|
|
||||||
day = doc.getStringSafe("day"),
|
|
||||||
time = doc.getStringSafe("time"),
|
|
||||||
timezone = doc.getStringSafe("timezone")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun documentToAnimeSource(doc: Document): AnimeSource {
|
|
||||||
return AnimeSource(
|
|
||||||
id = doc.getStringSafe("id"),
|
|
||||||
source = doc.getStringSafe("source")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun documentToCharacter(doc: Document): Character {
|
|
||||||
return Character(
|
|
||||||
character = doc.getDocumentSafe("character")?.let { documentToIndividual(it) } ?: Individual(),
|
|
||||||
role = doc.getStringSafe("role"),
|
|
||||||
voiceActor = doc.getListSafe<Document>("voiceActor").map { documentToActor(it) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun documentToCompanies(doc: Document): Companies {
|
|
||||||
return Companies(
|
|
||||||
malId = doc.getIntSafe("malId", 0),
|
|
||||||
name = doc.getStringSafe("name"),
|
|
||||||
type = doc.getStringSafe("type"),
|
|
||||||
url = doc.getStringSafe("url")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun documentToExternalLinks(doc: Document): ExternalLinks {
|
|
||||||
return ExternalLinks(
|
|
||||||
url = doc.getStringSafe("url"),
|
|
||||||
name = doc.getStringSafe("name")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun documentToImageMediaEntity(doc: Document): ImageMediaEntity {
|
|
||||||
return ImageMediaEntity(
|
|
||||||
media = doc.getStringSafe("media"),
|
|
||||||
thumbnail = doc.getStringSafe("thumbnail"),
|
|
||||||
width = doc.getIntSafe("width", 0),
|
|
||||||
height = doc.getIntSafe("height", 0),
|
|
||||||
url = doc.getStringSafe("url")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun documentToImages(doc: Document): Images {
|
|
||||||
return Images(
|
|
||||||
generic = doc.getStringSafe("generic"),
|
|
||||||
small = doc.getStringSafe("small"),
|
|
||||||
medium = doc.getStringSafe("medium"),
|
|
||||||
large = doc.getStringSafe("large"),
|
|
||||||
maximum = doc.getStringSafe("maximum")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun documentToIndividual(doc: Document): Individual {
|
|
||||||
return Individual(
|
|
||||||
malId = doc.getIntSafe("malId", 0),
|
|
||||||
url = doc.getStringSafe("url"),
|
|
||||||
name = doc.getStringSafe("name"),
|
|
||||||
images = doc.getStringSafe("images")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun documentToMergedEpisode(doc: Document): MergedEpisode {
|
|
||||||
return MergedEpisode(
|
|
||||||
number = doc.getIntSafe("number", 0),
|
|
||||||
ids = doc.getListSafe<Document>("ids").map { documentToAnimeSource(it) }.toMutableList(),
|
|
||||||
nextEpisodeDate = doc.getStringSafe("nextEpisodeDate")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun documentToRelated(doc: Document): Related {
|
|
||||||
return Related(
|
|
||||||
entry = doc.getListSafe<Document>("entry").map { documentToCompanies(it) },
|
|
||||||
relation = doc.getStringSafe("relation")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun documentToScore(doc: Document): Score {
|
|
||||||
return Score(
|
|
||||||
percentage = when (val value = doc["percentage"]) {
|
|
||||||
is Double -> value
|
|
||||||
is Int -> value.toDouble()
|
|
||||||
else -> 0.0
|
|
||||||
},
|
|
||||||
score = doc.getIntSafe("score", 0),
|
|
||||||
votes = doc.getIntSafe("votes", 0)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun documentToStaff(doc: Document): Staff {
|
|
||||||
return Staff(
|
|
||||||
person = doc.get("person", Document::class.java)?.let { documentToIndividual(it) } ?: Individual(),
|
|
||||||
positions = doc.getListSafe<String>("positions")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun documentToStatistics(doc: Document): Statistics {
|
|
||||||
return Statistics(
|
|
||||||
completed = doc.getIntSafe("completed"),
|
|
||||||
dropped = doc.getIntSafe("dropped"),
|
|
||||||
onHold = doc.getIntSafe("onHold"),
|
|
||||||
planToWatch = doc.getIntSafe("planToWatch"),
|
|
||||||
scores = doc.getListSafe<Document>("scores").map { documentToScore(it) },
|
|
||||||
total = doc.getIntSafe("total"),
|
|
||||||
watching = doc.getIntSafe("watching")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun documentToThemes(doc: Document): Themes {
|
|
||||||
return Themes(
|
|
||||||
endings = doc.getListSafe<String>("endings"),
|
|
||||||
openings = doc.getListSafe<String>("openings")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun documentToVideoPromo(doc: Document): VideoPromo {
|
|
||||||
return VideoPromo(
|
|
||||||
embedUrl = doc.getStringSafe("embedUrl"),
|
|
||||||
url = doc.getStringSafe("url"),
|
|
||||||
youtubeId = doc.getStringSafe("youtubeId"),
|
|
||||||
images = doc.get("images", Document::class.java)?.let { documentToImages(it) } ?: Images()
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
package com.example.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Actor(
|
|
||||||
/**
|
|
||||||
* Person generic info.
|
|
||||||
* @see Individual
|
|
||||||
*/
|
|
||||||
val person: Individual = Individual(),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Language of the person.
|
|
||||||
*/
|
|
||||||
val language: String = ""
|
|
||||||
)
|
|
@ -1,16 +0,0 @@
|
|||||||
package com.example.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class AiringTime(
|
|
||||||
/**
|
|
||||||
* Start date airing.
|
|
||||||
*/
|
|
||||||
val from: String = "",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* End date airing.
|
|
||||||
*/
|
|
||||||
val to: String = ""
|
|
||||||
)
|
|
@ -1,16 +0,0 @@
|
|||||||
package com.example.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class AlternativeTitles(
|
|
||||||
/**
|
|
||||||
* Title for anime.
|
|
||||||
*/
|
|
||||||
val title: String = "",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Title type for anime.
|
|
||||||
*/
|
|
||||||
val type: String = ""
|
|
||||||
)
|
|
@ -1,21 +0,0 @@
|
|||||||
package com.example.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class AnimeBroadcast(
|
|
||||||
/**
|
|
||||||
* Day in broadcast.
|
|
||||||
*/
|
|
||||||
val day: String = "",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Time date in broadcast.
|
|
||||||
*/
|
|
||||||
val time: String = "",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Timezone in broadcast.
|
|
||||||
*/
|
|
||||||
val timezone: String = ""
|
|
||||||
)
|
|
@ -1,10 +0,0 @@
|
|||||||
package com.example.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class AnimeSource(
|
|
||||||
val id: String,
|
|
||||||
val source: String
|
|
||||||
)
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
|||||||
package com.example.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Character(
|
|
||||||
var character: Individual = Individual(),
|
|
||||||
var role: String = "",
|
|
||||||
var voiceActor: List<Actor> = emptyList()
|
|
||||||
)
|
|
@ -1,26 +0,0 @@
|
|||||||
package com.example.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Companies(
|
|
||||||
/**
|
|
||||||
* ID associated with MyAnimeList.
|
|
||||||
*/
|
|
||||||
val malId: Int = 0,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Name for company.
|
|
||||||
*/
|
|
||||||
val name: String = "",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type for company.
|
|
||||||
*/
|
|
||||||
val type: String = "",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Url for company.
|
|
||||||
*/
|
|
||||||
val url: String = ""
|
|
||||||
)
|
|
@ -1,16 +0,0 @@
|
|||||||
package com.example.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class ExternalLinks(
|
|
||||||
/**
|
|
||||||
* Url for trailer.
|
|
||||||
*/
|
|
||||||
val url: String = "",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Name of external info.
|
|
||||||
*/
|
|
||||||
val name: String = ""
|
|
||||||
)
|
|
@ -1,12 +0,0 @@
|
|||||||
package com.example.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class ImageMediaEntity(
|
|
||||||
val media: String,
|
|
||||||
val thumbnail: String,
|
|
||||||
val width: Int,
|
|
||||||
val height: Int,
|
|
||||||
val url: String
|
|
||||||
)
|
|
@ -1,12 +0,0 @@
|
|||||||
package com.example.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Images(
|
|
||||||
val generic: String = "",
|
|
||||||
val small: String = "",
|
|
||||||
val medium: String = "",
|
|
||||||
val large: String = "",
|
|
||||||
val maximum: String = ""
|
|
||||||
)
|
|
@ -1,11 +0,0 @@
|
|||||||
package com.example.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Individual(
|
|
||||||
val malId: Int = 0,
|
|
||||||
val url: String = "",
|
|
||||||
val name: String = "",
|
|
||||||
val images: String = ""
|
|
||||||
)
|
|
@ -1,10 +0,0 @@
|
|||||||
package com.example.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class MergedEpisode(
|
|
||||||
var number: Int,
|
|
||||||
var ids: MutableList<AnimeSource> = mutableListOf(),
|
|
||||||
var nextEpisodeDate: String = ""
|
|
||||||
)
|
|
@ -1,53 +0,0 @@
|
|||||||
package com.example.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import org.bson.Document
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class MoreInfoEntity(
|
|
||||||
val id: Long? = null,
|
|
||||||
var malId: Int = 0,
|
|
||||||
var title: String = "",
|
|
||||||
var poster: String = "",
|
|
||||||
var cover: String = "",
|
|
||||||
var genres: List<String> = emptyList(),
|
|
||||||
var synopsis: String = "",
|
|
||||||
var episodes: List<MergedEpisode> = emptyList(),
|
|
||||||
var episodesCount: Int = 0,
|
|
||||||
var score: String = "",
|
|
||||||
var staff: List<Staff> = emptyList(),
|
|
||||||
var characters: List<Character> = emptyList(),
|
|
||||||
var status: String = "",
|
|
||||||
var type: String = "",
|
|
||||||
val url: String = "",
|
|
||||||
val promo: VideoPromo = VideoPromo(),
|
|
||||||
val source: String = "",
|
|
||||||
val duration: String = "",
|
|
||||||
val rank: Int = 0,
|
|
||||||
val titles: List<AlternativeTitles> = emptyList(),
|
|
||||||
val airing: Boolean = false,
|
|
||||||
val aired: AiringTime = AiringTime(),
|
|
||||||
val broadcast: AnimeBroadcast = AnimeBroadcast(),
|
|
||||||
val season: String = "",
|
|
||||||
val year: Int = 0,
|
|
||||||
val external: List<ExternalLinks> = emptyList(),
|
|
||||||
val streaming: List<ExternalLinks> = emptyList(),
|
|
||||||
val studios: List<Companies> = emptyList(),
|
|
||||||
val licensors: List<Companies> = emptyList(),
|
|
||||||
val producers: List<Companies> = emptyList(),
|
|
||||||
val theme: Themes = Themes(),
|
|
||||||
val relations: List<Related> = emptyList(),
|
|
||||||
val stats: Statistics = Statistics(),
|
|
||||||
val gallery: List<ImageMediaEntity> = emptyList(),
|
|
||||||
val episodeSource: String = ""
|
|
||||||
) {
|
|
||||||
fun toDocument(): Document = Document.parse(Json.encodeToString(this))
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val json = Json { ignoreUnknownKeys = true }
|
|
||||||
|
|
||||||
fun fromDocument(document: Document): MoreInfoEntity = json.decodeFromString(document.toJson())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
package com.example.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Related(
|
|
||||||
/**
|
|
||||||
* List of entries for relation in anime.
|
|
||||||
* @see Companies
|
|
||||||
*/
|
|
||||||
val entry: List<Companies>,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Relation for anime.
|
|
||||||
*/
|
|
||||||
val relation: String
|
|
||||||
)
|
|
@ -1,10 +0,0 @@
|
|||||||
package com.example.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Score(
|
|
||||||
val percentage: Double,
|
|
||||||
val score: Int,
|
|
||||||
val votes: Int
|
|
||||||
)
|
|
@ -1,9 +0,0 @@
|
|||||||
package com.example.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Staff(
|
|
||||||
var person: Individual = Individual(),
|
|
||||||
var positions: List<String> = emptyList()
|
|
||||||
)
|
|
@ -1,14 +0,0 @@
|
|||||||
package com.example.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Statistics(
|
|
||||||
val completed: Int? = null,
|
|
||||||
val dropped: Int? = null,
|
|
||||||
val onHold: Int? = null,
|
|
||||||
val planToWatch: Int? = null,
|
|
||||||
val scores: List<Score>? = emptyList(),
|
|
||||||
val total: Int? = null,
|
|
||||||
val watching: Int? = null
|
|
||||||
)
|
|
@ -1,16 +0,0 @@
|
|||||||
package com.example.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
open class Themes(
|
|
||||||
/**
|
|
||||||
* List of endings.
|
|
||||||
*/
|
|
||||||
val endings: List<String> = emptyList(),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of openings.
|
|
||||||
*/
|
|
||||||
val openings: List<String> = emptyList()
|
|
||||||
)
|
|
@ -1,26 +0,0 @@
|
|||||||
package com.example.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
open class VideoPromo(
|
|
||||||
/**
|
|
||||||
* Embed url for trailer.
|
|
||||||
*/
|
|
||||||
val embedUrl: String = "",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Url for trailer.
|
|
||||||
*/
|
|
||||||
val url: String = "",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Youtube id for trailer.
|
|
||||||
*/
|
|
||||||
val youtubeId: String = "",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Images for trailer.
|
|
||||||
*/
|
|
||||||
val images: Images = Images()
|
|
||||||
)
|
|
@ -1,20 +0,0 @@
|
|||||||
package com.jeluchu.features.anime.models.directory
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class AnimeDirectoryEntity(
|
|
||||||
val rank: Int = 0,
|
|
||||||
val year: Int = 0,
|
|
||||||
var malId: Int = 0,
|
|
||||||
val url: String = "",
|
|
||||||
var type: String = "",
|
|
||||||
var score: String = "",
|
|
||||||
var title: String = "",
|
|
||||||
var status: String = "",
|
|
||||||
val season: String = "",
|
|
||||||
var poster: String = "",
|
|
||||||
var episodesCount: Int = 0,
|
|
||||||
val airing: Boolean = false,
|
|
||||||
var genres: List<String> = emptyList()
|
|
||||||
)
|
|
@ -1,15 +0,0 @@
|
|||||||
package com.jeluchu.features.anime.routes
|
|
||||||
|
|
||||||
import com.jeluchu.core.extensions.getToJson
|
|
||||||
import com.jeluchu.core.utils.Routes
|
|
||||||
import com.jeluchu.features.anime.services.AnimeService
|
|
||||||
import com.mongodb.client.MongoDatabase
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
|
|
||||||
fun Route.animeEndpoints(
|
|
||||||
mongoDatabase: MongoDatabase,
|
|
||||||
service: AnimeService = AnimeService(mongoDatabase)
|
|
||||||
) {
|
|
||||||
getToJson(Routes.DIRECTORY) { service.getDirectory(call) }
|
|
||||||
getToJson(Routes.ANIME_DETAILS) { service.getAnimeByMalId(call) }
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
package com.jeluchu.features.anime.services
|
|
||||||
|
|
||||||
import com.jeluchu.core.messages.ErrorMessages
|
|
||||||
import com.jeluchu.core.models.ErrorResponse
|
|
||||||
import com.jeluchu.features.anime.mappers.documentToAnimeDirectoryEntity
|
|
||||||
import com.jeluchu.features.anime.mappers.documentToMoreInfoEntity
|
|
||||||
import com.mongodb.client.MongoDatabase
|
|
||||||
import com.mongodb.client.model.Filters
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.server.response.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
|
|
||||||
class AnimeService(
|
|
||||||
database: MongoDatabase
|
|
||||||
) {
|
|
||||||
private val directoryCollection = database.getCollection("animedetails")
|
|
||||||
|
|
||||||
suspend fun getDirectory(call: RoutingCall) = try {
|
|
||||||
val elements = directoryCollection.find().toList()
|
|
||||||
val directory = elements.map { documentToAnimeDirectoryEntity(it) }
|
|
||||||
val json = Json.encodeToString(directory)
|
|
||||||
call.respond(HttpStatusCode.OK, json)
|
|
||||||
} catch (ex: Exception) {
|
|
||||||
call.respond(HttpStatusCode.Unauthorized, ErrorResponse(ErrorMessages.UnauthorizedMongo.message))
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getAnimeByMalId(call: RoutingCall) = try {
|
|
||||||
val id = call.parameters["id"]?.toInt() ?: throw IllegalArgumentException(ErrorMessages.InvalidMalId.message)
|
|
||||||
directoryCollection.find(Filters.eq("malId", id)).firstOrNull()?.let { anime ->
|
|
||||||
val info = documentToMoreInfoEntity(anime)
|
|
||||||
call.respond(HttpStatusCode.OK, Json.encodeToString(info))
|
|
||||||
} ?: call.respond(HttpStatusCode.NotFound, ErrorResponse(ErrorMessages.AnimeNotFound.message))
|
|
||||||
} catch (ex: Exception) {
|
|
||||||
call.respond(HttpStatusCode.NotFound, ErrorResponse(ErrorMessages.InvalidInput.message))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
|||||||
ktor:
|
|
||||||
application:
|
|
||||||
modules:
|
|
||||||
- com.jeluchu.ApplicationKt.module
|
|
||||||
deployment:
|
|
||||||
port: 8080
|
|
||||||
host: "0.0.0.0"
|
|
||||||
|
|
||||||
db:
|
|
||||||
mongo:
|
|
||||||
connectionStrings: ${?MONGO_CONNECTION_STRING}
|
|
||||||
database:
|
|
||||||
name: ${?MONGO_DATABASE_NAME}
|
|
@ -1,11 +0,0 @@
|
|||||||
<configuration>
|
|
||||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
|
||||||
<encoder>
|
|
||||||
<pattern>%date %-5level [%thread] %logger{0}: %msg%n</pattern>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<root level="INFO">
|
|
||||||
<appender-ref ref="STDOUT" />
|
|
||||||
</root>
|
|
||||||
</configuration>
|
|
@ -1,36 +0,0 @@
|
|||||||
openapi: "3.0.3"
|
|
||||||
info:
|
|
||||||
title: "Aruppi API"
|
|
||||||
description: "Application API"
|
|
||||||
version: "5.0.0-preview"
|
|
||||||
servers:
|
|
||||||
- url: "http://0.0.0.0:8080/api/v5"
|
|
||||||
paths:
|
|
||||||
/directory:
|
|
||||||
get:
|
|
||||||
description: "Hello World!"
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: "OK"
|
|
||||||
content:
|
|
||||||
text/plain:
|
|
||||||
schema:
|
|
||||||
type: "string"
|
|
||||||
examples:
|
|
||||||
Example#1:
|
|
||||||
value: "Hello World!"
|
|
||||||
/anime/{id}:
|
|
||||||
get:
|
|
||||||
description: "Hello World!"
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: "OK"
|
|
||||||
content:
|
|
||||||
text/plain:
|
|
||||||
schema:
|
|
||||||
type: "string"
|
|
||||||
examples:
|
|
||||||
Example#1:
|
|
||||||
value: "Hello World!"
|
|
||||||
components:
|
|
||||||
schemas: {}
|
|
@ -0,0 +1,48 @@
|
|||||||
|
import {
|
||||||
|
Request,
|
||||||
|
Response,
|
||||||
|
NextFunction,
|
||||||
|
ErrorRequestHandler,
|
||||||
|
RequestHandler,
|
||||||
|
} from 'express';
|
||||||
|
|
||||||
|
/*
|
||||||
|
Error handler and notFound handler
|
||||||
|
for all the API like a middleware with
|
||||||
|
the function next of express.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const errorHandler: ErrorRequestHandler = (
|
||||||
|
err: any,
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction,
|
||||||
|
) => {
|
||||||
|
const statusCode = res.statusCode !== 200 ? res.statusCode : 500;
|
||||||
|
res.status(statusCode).json({
|
||||||
|
message: err.message,
|
||||||
|
stack: process.env.NODE_ENV === 'production' ? '🥞' : err.stack,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const notFound: any = (
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction,
|
||||||
|
) => {
|
||||||
|
res.status(404);
|
||||||
|
const error = new Error(`🔍 - Not Found - ${req.originalUrl}`);
|
||||||
|
next(error);
|
||||||
|
};
|
||||||
|
|
||||||
|
// export const requestLoggerMiddleWare: RequestHandler = (req, res, next) => {
|
||||||
|
// console.log(`${req.method} ${req.originalUrl}`);
|
||||||
|
// const start: number = new Date().getTime();
|
||||||
|
// res.on('finish', () => {
|
||||||
|
// const elapsed: number = new Date().getTime() - start;
|
||||||
|
// console.info(
|
||||||
|
// `${req.method} ${req.originalUrl} ${req.statusCode} ${elapsed}ms`,
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
// next();
|
||||||
|
// };
|
@ -0,0 +1,144 @@
|
|||||||
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
|
import AnimeController from './controllers/AnimeController';
|
||||||
|
import DirectoryController from './controllers/DirectoryController';
|
||||||
|
import UtilsController from './controllers/UtilsController';
|
||||||
|
|
||||||
|
const routes = Router();
|
||||||
|
const animeController = new AnimeController();
|
||||||
|
const directoryController = new DirectoryController();
|
||||||
|
const utilsController = new UtilsController();
|
||||||
|
|
||||||
|
routes.get('/', (req: Request, res: Response) => {
|
||||||
|
// We dont want to enforce the redirect storing, so just check
|
||||||
|
res.set('Cache-Control', 'no-cache,proxy-revalidate');
|
||||||
|
res.redirect('/api/v4/');
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
Routes - JSON
|
||||||
|
Message with the JSON of all the routes in the
|
||||||
|
/api, parameters and some examples, how to call the
|
||||||
|
endpoints of the /api.
|
||||||
|
*/
|
||||||
|
|
||||||
|
routes.get('/api/v4/', (req: Request, res: Response) => {
|
||||||
|
res.set('Cache-Control', 'no-store');
|
||||||
|
res.json({
|
||||||
|
message: 'Aruppi /api - 🎏',
|
||||||
|
author: 'Jéluchu',
|
||||||
|
version: '4.2.2',
|
||||||
|
credits: 'The bitch loves /apis that offers data to Aruppi App',
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
Schedule: '/api/v4/schedule/:day',
|
||||||
|
Top: '/api/v4/top/:type/:page/:subtype',
|
||||||
|
AllAnimes: '/api/v4/allAnimes',
|
||||||
|
RandomAnime: '/api/v4/randomAnime',
|
||||||
|
Anitakume: '/api/v4/anitakume',
|
||||||
|
News: '/api/v4/news',
|
||||||
|
Season: '/api/v4/season/:year/:type',
|
||||||
|
'All Seasons': '/api/v4/allSeasons',
|
||||||
|
'All Directory': '/api/v4/allDirectory/:type',
|
||||||
|
Genres: '/api/v4/getByGenres/:genre?/:order?/:page?',
|
||||||
|
'Futures Seasons': '/api/v4/laterSeasons',
|
||||||
|
LastEpisodes: '/api/v4/lastEpisodes',
|
||||||
|
Movies: '/api/v4/movies/:type/:page',
|
||||||
|
Ovas: '/api/v4/ova/:type/:page',
|
||||||
|
Specials: '/api/v4/special/:type/:page',
|
||||||
|
Tv: '/api/v4/tv/:type/:page',
|
||||||
|
MoreInfo: '/api/v4/moreInfo/:title',
|
||||||
|
GetEpisodes: '/api/v4/getEpisodes/:title',
|
||||||
|
GetAnimeServers: '/api/v4/getAnimeServers/:id',
|
||||||
|
Search: '/api/v4/search/:title',
|
||||||
|
Images: '/api/v4/images/:query',
|
||||||
|
Videos: '/api/v4/videos/:channelId',
|
||||||
|
Playlist: '/api/v4/playlistVideos/:playlistId',
|
||||||
|
'Type Videos': '/api/v4/sectionedVideos/:type',
|
||||||
|
Radios: '/api/v4/radio',
|
||||||
|
'All Themes': '/api/v4/allThemes',
|
||||||
|
Themes: '/api/v4/themes/:title',
|
||||||
|
'Year Themes': '/api/v4/themesYear/:year?',
|
||||||
|
'Random Theme': '/api/v4/randomTheme',
|
||||||
|
'Artists Theme': '/api/v4/artists/:id?',
|
||||||
|
'Famous Platforms': '/api/v4/destAnimePlatforms',
|
||||||
|
'Legal Platforms': '/api/v4/platforms/:id?',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Routes of the app below */
|
||||||
|
|
||||||
|
/* Anime Controller */
|
||||||
|
routes.get('/api/v4/schedule/:day', animeController.schedule);
|
||||||
|
routes.get('/api/v4/top/:type/:subtype?/:page', animeController.top);
|
||||||
|
routes.get('/api/v4/allAnimes', animeController.getAllAnimes);
|
||||||
|
routes.get('/api/v4/lastEpisodes', animeController.getLastEpisodes);
|
||||||
|
routes.get('/api/v4/movies/:type/:page', animeController.getContentMovie);
|
||||||
|
routes.get('/api/v4/ova/:type/:page', animeController.getContentOva);
|
||||||
|
routes.get('/api/v4/special/:type/:page', animeController.getContentSpecial);
|
||||||
|
routes.get('/api/v4/tv/:type/:page', animeController.getContentTv);
|
||||||
|
routes.get('/api/v4/getEpisodes/:title', animeController.getEpisodes);
|
||||||
|
routes.get(
|
||||||
|
'/api/v4/getAnimeServers/:id([^/]+/[^/]+)',
|
||||||
|
animeController.getServers,
|
||||||
|
);
|
||||||
|
routes.get('/api/v4/randomAnime', animeController.getRandomAnime);
|
||||||
|
|
||||||
|
/* Directory Controller */
|
||||||
|
routes.get(
|
||||||
|
'/api/v4/allDirectory/:genres?',
|
||||||
|
directoryController.getAllDirectory,
|
||||||
|
);
|
||||||
|
routes.get('/api/v4/season/:year/:type', directoryController.getSeason);
|
||||||
|
routes.get('/api/v4/allSeasons', directoryController.allSeasons);
|
||||||
|
routes.get('/api/v4/laterSeasons', directoryController.laterSeasons);
|
||||||
|
routes.get('/api/v4/moreInfo/:title', directoryController.getMoreInfo);
|
||||||
|
routes.get('/api/v4/search/:title', directoryController.search);
|
||||||
|
routes.get(
|
||||||
|
'/api/v4/getByGenres/:genre?/:order?/:page?',
|
||||||
|
directoryController.getAnimeGenres,
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Utils Controller */
|
||||||
|
routes.get('/api/v4/anitakume', utilsController.getAnitakume);
|
||||||
|
routes.get('/api/v4/news', utilsController.getNews);
|
||||||
|
routes.get('/api/v4/images/:title', utilsController.getImages);
|
||||||
|
routes.get('/api/v4/videos/:channelId', utilsController.getVideos);
|
||||||
|
routes.get('/api/v4/playlistVideos/:playlistId', utilsController.getPlaylists);
|
||||||
|
routes.get('/api/v4/sectionedVideos/:type', utilsController.getSectionVideos);
|
||||||
|
routes.get('/api/v4/radio', utilsController.getRadioStations);
|
||||||
|
routes.get('/api/v4/allThemes', utilsController.getAllThemes);
|
||||||
|
routes.get('/api/v4/themes/:title', utilsController.getOpAndEd);
|
||||||
|
routes.get('/api/v4/themesYear/:year?', utilsController.getThemesYear);
|
||||||
|
routes.get('/api/v4/randomTheme', utilsController.randomTheme);
|
||||||
|
routes.get('/api/v4/artists/:id?', utilsController.getArtist);
|
||||||
|
routes.get('/api/v4/destAnimePlatforms', utilsController.getDestAnimePlatforms);
|
||||||
|
routes.get('/api/v4/platforms/:id?', utilsController.getPlatforms);
|
||||||
|
routes.get('/api/v4/generateWaifu/', utilsController.getWaifuRandom);
|
||||||
|
|
||||||
|
/* Routes to handling the v3 deprecated */
|
||||||
|
routes.get('/api/v3/*', (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
res.status(302).redirect('/api/v3');
|
||||||
|
});
|
||||||
|
|
||||||
|
routes.get('/api/v3', (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
res.status(200).json({
|
||||||
|
message:
|
||||||
|
'Sorry, version v3 is not avaiable, if you want to see content go to v4',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Routes to handling the v2 deprecated */
|
||||||
|
routes.get('/api/v2/*', (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
res.status(302).redirect('/api/v2');
|
||||||
|
});
|
||||||
|
|
||||||
|
routes.get('/api/v2', (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
res.status(200).json({
|
||||||
|
message:
|
||||||
|
'Sorry, version v2 is not avaiable, if you want to see content go to v4',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default routes;
|
@ -0,0 +1,41 @@
|
|||||||
|
import express, { Application } from 'express';
|
||||||
|
import cors from 'cors';
|
||||||
|
import helmet from 'helmet';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
import { errorHandler, notFound } from './middlewares/middleware';
|
||||||
|
import {
|
||||||
|
createConnectionMongo,
|
||||||
|
} from './database/connection';
|
||||||
|
import routes from './routes';
|
||||||
|
|
||||||
|
const app: Application = express();
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
createConnectionMongo({
|
||||||
|
host: process.env.DATABASE_HOST,
|
||||||
|
port: process.env.DATABASE_PORT,
|
||||||
|
});
|
||||||
|
app.use(cors());
|
||||||
|
app.use(helmet());
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(express.urlencoded({ extended: false }));
|
||||||
|
app.use(routes);
|
||||||
|
app.use(notFound);
|
||||||
|
app.use(errorHandler);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Starting the server on the .env process
|
||||||
|
you can define the PORT where the server
|
||||||
|
is going to listen in the server.
|
||||||
|
ex: PORT=3000.
|
||||||
|
*/
|
||||||
|
const server = app.listen(process.env.PORT_LISTEN || 3000);
|
||||||
|
|
||||||
|
function shutdown(): void {
|
||||||
|
server.close();
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on('SIGINT', shutdown);
|
||||||
|
process.on('SIGQUIT', shutdown);
|
||||||
|
process.on('SIGTERM', shutdown);
|
@ -0,0 +1,302 @@
|
|||||||
|
import cheerio from 'cheerio';
|
||||||
|
import { requestGot } from './requestCall';
|
||||||
|
import urls from './urls';
|
||||||
|
|
||||||
|
export default class ThemeParser {
|
||||||
|
animes: any[] = [];
|
||||||
|
$: any = '';
|
||||||
|
|
||||||
|
async all() {
|
||||||
|
try {
|
||||||
|
this.animes = [];
|
||||||
|
this.$ = await redditocall('year_index');
|
||||||
|
return await this.parseLinks();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async allYears() {
|
||||||
|
try {
|
||||||
|
this.animes = [];
|
||||||
|
this.$ = await redditocall('year_index');
|
||||||
|
return await this.parseYears();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async serie(title: string) {
|
||||||
|
try {
|
||||||
|
this.animes = [];
|
||||||
|
this.$ = await redditocall('anime_index');
|
||||||
|
return await this.parseSerie(title);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async artists() {
|
||||||
|
try {
|
||||||
|
this.animes = [];
|
||||||
|
this.$ = await redditocall('artist');
|
||||||
|
return await this.parseArtists();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async artist(id: string) {
|
||||||
|
try {
|
||||||
|
this.animes = [];
|
||||||
|
this.$ = await redditocall(`artist/${id}`);
|
||||||
|
return await this.parseArtist();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async random() {
|
||||||
|
try {
|
||||||
|
this.animes = [];
|
||||||
|
this.$ = await redditocall('anime_index');
|
||||||
|
return await this.parseRandom();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async year(date: string) {
|
||||||
|
let animes: any = [];
|
||||||
|
|
||||||
|
this.$ = await redditocall(date);
|
||||||
|
this.$('h3').each((index: number, element: cheerio.Element) => {
|
||||||
|
let parsed = this.parseAnime(this.$(element));
|
||||||
|
parsed.year = date;
|
||||||
|
animes.push(parsed);
|
||||||
|
});
|
||||||
|
return animes;
|
||||||
|
}
|
||||||
|
|
||||||
|
parseRandom() {
|
||||||
|
return new Promise(async resolve => {
|
||||||
|
let data = this.$('p a');
|
||||||
|
const origin: any = '1';
|
||||||
|
let randomize = Math.round(
|
||||||
|
Math.random() * (data.length - 1 - origin) + parseInt(origin),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.$ = await redditocall(
|
||||||
|
this.$('p a')
|
||||||
|
[randomize].attribs.href.split('/r/AnimeThemes/wiki/')[1]
|
||||||
|
.split('#wiki')[0],
|
||||||
|
);
|
||||||
|
|
||||||
|
let rand = Math.round(Math.random() * this.$('h3').length - 1);
|
||||||
|
let parsed = this.parseAnime(this.$('h3')[rand]);
|
||||||
|
resolve(parsed);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -ParseYears
|
||||||
|
Get the data from the year
|
||||||
|
get the name and the id to do the respective
|
||||||
|
scrapping.
|
||||||
|
*/
|
||||||
|
parseYears() {
|
||||||
|
return new Promise(async resolve => {
|
||||||
|
let years: any[] = [];
|
||||||
|
|
||||||
|
this.$('h3 a').each((index: number, element: cheerio.Element) => {
|
||||||
|
years.push({
|
||||||
|
id: this.$(element).attr('href').split('/')[4],
|
||||||
|
name: this.$(element).text(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve(years);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parseArtists() {
|
||||||
|
return new Promise(async resolve => {
|
||||||
|
let promises = [];
|
||||||
|
let data = this.$('p a').filter((x: any) => x > 0);
|
||||||
|
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
promises.push({
|
||||||
|
id: data[i].children[0].parent.attribs.href.split('/')[5],
|
||||||
|
name: data[i].children[0].data,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (i === data.length - 1) {
|
||||||
|
resolve(promises);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parseArtist() {
|
||||||
|
return new Promise(async resolve => {
|
||||||
|
let promises = [];
|
||||||
|
let data = this.$('h3');
|
||||||
|
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
let parsed = await this.parseAnime(data[i]);
|
||||||
|
promises.push(parsed);
|
||||||
|
|
||||||
|
if (i === data.length - 1) {
|
||||||
|
resolve(promises);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* - ParseSerie
|
||||||
|
Parse the HTML from the redditocall
|
||||||
|
and search for the h3 tag to be the
|
||||||
|
same of the title and resolve a object.
|
||||||
|
*/
|
||||||
|
parseSerie(title: string) {
|
||||||
|
return new Promise(async resolve => {
|
||||||
|
let data = this.$('p a');
|
||||||
|
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
let serieElement = data[i].children[0].data;
|
||||||
|
|
||||||
|
if (serieElement.split(' (')[0] === title) {
|
||||||
|
let year = this.$('p a')
|
||||||
|
[i].attribs.href.split('/r/AnimeThemes/wiki/')[1]
|
||||||
|
.split('#wiki')[0];
|
||||||
|
this.$ = await redditocall(
|
||||||
|
this.$('p a')
|
||||||
|
[i].attribs.href.split('/r/AnimeThemes/wiki/')[1]
|
||||||
|
.split('#wiki')[0],
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let i = 0; i < this.$('h3').length; i++) {
|
||||||
|
if (this.$('h3')[i].children[0].children[0].data === title) {
|
||||||
|
let parsed = this.parseAnime(this.$('h3')[i]);
|
||||||
|
parsed.year = year;
|
||||||
|
resolve(parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parseLinks() {
|
||||||
|
return new Promise(async resolve => {
|
||||||
|
let years = this.$('h3 a');
|
||||||
|
|
||||||
|
for (let i = 0; i < years.length; i++) {
|
||||||
|
let yearElement = years[i];
|
||||||
|
|
||||||
|
await this.year(this.$(yearElement).attr('href').split('/')[4]).then(
|
||||||
|
async animes => {
|
||||||
|
this.animes = this.animes.concat(animes);
|
||||||
|
if (i === years.length - 1) {
|
||||||
|
resolve(this.animes);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* - ParseAnime
|
||||||
|
Parse the h3 tag and get the table
|
||||||
|
for the next function to parse the table
|
||||||
|
and get the information about the ending and
|
||||||
|
openings.
|
||||||
|
*/
|
||||||
|
parseAnime(element: cheerio.Element) {
|
||||||
|
let el = this.$(element).find('a');
|
||||||
|
let title = this.$(el).text();
|
||||||
|
let mal_id = this.$(el).attr('href').split('/')[4];
|
||||||
|
let next = this.$(element).next();
|
||||||
|
|
||||||
|
let theme: any = {
|
||||||
|
id: mal_id,
|
||||||
|
title,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.$(next).prop('tagName') === 'TABLE') {
|
||||||
|
theme.themes = this.parseTable(this.$(next));
|
||||||
|
} else if (this.$(next).prop('tagName') === 'P') {
|
||||||
|
theme.themes = this.parseTable(this.$(next).next());
|
||||||
|
}
|
||||||
|
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* - ParseTable
|
||||||
|
Parse the table tag from the HTML
|
||||||
|
and returns a object with all the
|
||||||
|
information.
|
||||||
|
*/
|
||||||
|
parseTable(element: cheerio.Element): any {
|
||||||
|
if (this.$(element).prop('tagName') !== 'TABLE') {
|
||||||
|
return this.parseTable(this.$(element).next());
|
||||||
|
}
|
||||||
|
|
||||||
|
let themes: any = [];
|
||||||
|
|
||||||
|
this.$(element)
|
||||||
|
.find('tbody')
|
||||||
|
.find('tr')
|
||||||
|
.each((index: number, element: cheerio.Element) => {
|
||||||
|
let name = replaceAll(
|
||||||
|
this.$(element).find('td').eq(0).text(),
|
||||||
|
'"',
|
||||||
|
'"',
|
||||||
|
);
|
||||||
|
let link = this.$(element).find('td').eq(1).find('a').attr('href');
|
||||||
|
let linkDesc = this.$(element).find('td').eq(1).find('a').text();
|
||||||
|
let episodes =
|
||||||
|
this.$(element).find('td').eq(2).text().length > 0
|
||||||
|
? this.$(element).find('td').eq(2).text()
|
||||||
|
: '';
|
||||||
|
let notes =
|
||||||
|
this.$(element).find('td').eq(3).text().length > 0
|
||||||
|
? this.$(element).find('td').eq(3).text()
|
||||||
|
: '';
|
||||||
|
|
||||||
|
themes.push({
|
||||||
|
name,
|
||||||
|
link,
|
||||||
|
desc: linkDesc,
|
||||||
|
type: name.startsWith('OP')
|
||||||
|
? `OP${name[2]}`
|
||||||
|
: name.startsWith('ED')
|
||||||
|
? `ED${name[2]}`
|
||||||
|
: 'OP/ED',
|
||||||
|
episodes,
|
||||||
|
notes,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return themes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function redditocall(href: string) {
|
||||||
|
const resp = await requestGot(urls.REDDIT_ANIMETHEMES + href + '.json', {
|
||||||
|
parse: true,
|
||||||
|
scrapy: false,
|
||||||
|
spoof: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return cheerio.load(getHTML(resp.data.content_html));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHTML(str: string) {
|
||||||
|
let html = replaceAll(str, '<', '<');
|
||||||
|
html = replaceAll(html, '>', '>');
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceAll(str: string, find: string, replace: string) {
|
||||||
|
return str.replace(new RegExp(find, 'g'), replace);
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
import urls from './urls';
|
||||||
|
|
||||||
|
export const obtainPreviewNews = (encoded: string) => {
|
||||||
|
let image: string;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (encoded.includes('src="https://img1.ak.crunchyroll.com/')) {
|
||||||
|
if (
|
||||||
|
encoded.split('https://img1.ak.crunchyroll.com/')[1].includes('.jpg')
|
||||||
|
) {
|
||||||
|
image = `https://img1.ak.crunchyroll.com/${
|
||||||
|
encoded.split('https://img1.ak.crunchyroll.com/')[1].split('.jpg')[0]
|
||||||
|
}.jpg`;
|
||||||
|
} else {
|
||||||
|
image = `https://img1.ak.crunchyroll.com/${
|
||||||
|
encoded.split('https://img1.ak.crunchyroll.com/')[1].split('.png')[0]
|
||||||
|
}.png`;
|
||||||
|
}
|
||||||
|
} else if (encoded.includes('<img title=')) {
|
||||||
|
image = encoded
|
||||||
|
.substring(encoded.indexOf('<img title="'), encoded.indexOf('" alt'))
|
||||||
|
.split('src="')[1];
|
||||||
|
} else if (encoded.includes('<img src=')) {
|
||||||
|
image = encoded
|
||||||
|
.substring(encoded.indexOf('<img src="'), encoded.indexOf('" alt'))
|
||||||
|
.substring(10)
|
||||||
|
.replace('http', 'https')
|
||||||
|
.replace('httpss', 'https');
|
||||||
|
} else if (encoded.includes('<img')) {
|
||||||
|
image = encoded
|
||||||
|
.split('src=')[1]
|
||||||
|
.split(' class=')[0]
|
||||||
|
.replace('"', '')
|
||||||
|
.replace('"', '');
|
||||||
|
} else if (encoded.includes('https://www.youtube.com/embed/')) {
|
||||||
|
let getSecondThumb = encoded
|
||||||
|
.split('https://www.youtube.com/embed/')[1]
|
||||||
|
.split('?feature')[0];
|
||||||
|
image = `https://img.youtube.com/vi/${getSecondThumb}/0.jpg`;
|
||||||
|
} else if (encoded.includes('https://www.dailymotion.com/')) {
|
||||||
|
let getDailymotionThumb = encoded
|
||||||
|
.substring(encoded.indexOf('" src="'), encoded.indexOf('" a'))
|
||||||
|
.substring(47);
|
||||||
|
image = `https://www.dailymotion.com/thumbnail/video/${getDailymotionThumb}`;
|
||||||
|
} else {
|
||||||
|
let number = Math.floor(Math.random() * 30);
|
||||||
|
image = `${urls.BASE_ARUPPI}news/${number}.png`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return image;
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,52 @@
|
|||||||
|
import got from 'got';
|
||||||
|
import cheerio from 'cheerio';
|
||||||
|
import { CookieJar } from 'tough-cookie';
|
||||||
|
// @ts-ignore
|
||||||
|
import * as got_pjson from 'got/package.json'
|
||||||
|
const pjson = require('../../package.json');
|
||||||
|
|
||||||
|
const cookieJar = new CookieJar();
|
||||||
|
const aruppi_options: any = {
|
||||||
|
cookieJar,
|
||||||
|
'headers': {
|
||||||
|
'user-agent': `Aruppi-API/${pjson.version} ${got_pjson.name}/${got_pjson.version}`,
|
||||||
|
'x-client': 'aruppi-api'
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Options {
|
||||||
|
scrapy?: boolean,
|
||||||
|
parse?: boolean,
|
||||||
|
spoof?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const requestGot = async (
|
||||||
|
url: string,
|
||||||
|
options?: Options,
|
||||||
|
): Promise<any> => {
|
||||||
|
const got_options: any = {...got.defaults.options, ...aruppi_options}
|
||||||
|
if (options) {
|
||||||
|
if (options.spoof != null) {
|
||||||
|
got_options.headers["user-agent"] = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:71.0) Gecko/20100101 Firefox/69.0";
|
||||||
|
delete got_options.headers['x-client'];
|
||||||
|
if (!options.spoof)
|
||||||
|
got_options.headers['user-agent'] = got.defaults.options.headers['user-agent'];
|
||||||
|
} else if (process.env.ALPI_KEY && (new URL(url)).hostname.match(/\.jeluchu\.xyz$/)) {
|
||||||
|
got_options.headers['x-aruppi-key'] = process.env.ALPI_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.scrapy) {
|
||||||
|
const response = await got(url, got_options);
|
||||||
|
return cheerio.load(response.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.parse) {
|
||||||
|
got_options.responseType = 'json';
|
||||||
|
const response = await got(url, got_options);
|
||||||
|
return response.body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const response = await got.get(url, got_options);
|
||||||
|
return response;
|
||||||
|
};
|
@ -0,0 +1,22 @@
|
|||||||
|
import { requestGot } from './requestCall';
|
||||||
|
|
||||||
|
export const transformUrlServer = async (urlReal: any) => {
|
||||||
|
for (const data of urlReal) {
|
||||||
|
if (data.server === 'amus' || data.server === 'natsuki') {
|
||||||
|
let res = await requestGot(data.code.replace('embed', 'check'), {
|
||||||
|
parse: true,
|
||||||
|
scrapy: false,
|
||||||
|
});
|
||||||
|
data.code = res.file || null;
|
||||||
|
data.direct = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return urlReal.map((item: any) => {
|
||||||
|
return {
|
||||||
|
id: item.title.toLowerCase(),
|
||||||
|
url: item.code,
|
||||||
|
direct: item.direct || false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,23 @@
|
|||||||
|
export default {
|
||||||
|
BASE_ARUPPI: 'https://aruppi.jeluchu.xyz/',
|
||||||
|
BASE_ANIMEFLV: 'https://www3.animeflv.net/',
|
||||||
|
BASE_MONOSCHINOS: 'https://monoschinos2.com/',
|
||||||
|
BASE_TIOANIME: 'https://tioanime.com/',
|
||||||
|
BASE_JKANIME: 'https://jkanime.net/',
|
||||||
|
BASE_ANIMEFLV_JELU: 'https://aruppi.jeluchu.xyz/apis/animeflv/v1/',
|
||||||
|
BASE_YOUTUBE: 'https://aruppi.jeluchu.xyz/api/Youtube/?channelId=',
|
||||||
|
BASE_YOUTUBE_PLAYLIST: 'https://aruppi.jeluchu.xyz/api/Youtube/playlist/?playlistId=',
|
||||||
|
BASE_JIKAN: 'https://aruppi.jeluchu.xyz/apis/jikan/v4/',
|
||||||
|
BASE_IVOOX: 'https://www.ivoox.com/podcast-anitakume_fg_f1660716_filtro_1.xml',
|
||||||
|
BASE_KUDASAI: 'https://somoskudasai.com/feed/',
|
||||||
|
BASE_RAMENPARADOS: 'https://ramenparados.com/category/noticias/anime/feed/',
|
||||||
|
BASE_CRUNCHYROLL: 'https://www.crunchyroll.com/newsrss?lang=esES',
|
||||||
|
JKANIME_SEARCH: 'https://jkanime.net/buscar/',
|
||||||
|
ANIMEFLV_SEARCH: 'https://animeflv.net/browse?',
|
||||||
|
SEARCH_DIRECTORY: 'https://animeflv.net/browse?order=title&page=',
|
||||||
|
BASE_EPISODE_IMG_URL: 'https://cdn.animeflv.net/screenshots/',
|
||||||
|
BASE_QWANT: 'https://api.qwant.com/v3/search/images?',
|
||||||
|
REDDIT_ANIMETHEMES: 'https://reddit.com/r/AnimeThemes/wiki/',
|
||||||
|
BASE_THEMEMOE: 'https://themes.moe/api/',
|
||||||
|
BASE_ARUPPI_MONOSCHINOS: 'https://aruppi.jeluchu.xyz/apis/monoschinos/',
|
||||||
|
};
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||||
|
|
||||||
|
/* Basic Options */
|
||||||
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
|
"target": "ES2017" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
|
||||||
|
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
|
||||||
|
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||||
|
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||||
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
|
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||||
|
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||||
|
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||||
|
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||||
|
// "outFile": "./" /* Concatenate and emit output to single file. */,
|
||||||
|
"outDir": "./dist" /* Redirect output structure to the directory. */,
|
||||||
|
"rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
|
||||||
|
// "composite": true, /* Enable project compilation */
|
||||||
|
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||||
|
// "removeComments": true, /* Do not emit comments to output. */
|
||||||
|
// "noEmit": true, /* Do not emit outputs. */
|
||||||
|
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||||
|
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||||
|
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||||
|
|
||||||
|
/* Strict Type-Checking Options */
|
||||||
|
"strict": true /* Enable all strict type-checking options. */,
|
||||||
|
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||||
|
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||||
|
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||||
|
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||||
|
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||||
|
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||||
|
|
||||||
|
/* Additional Checks */
|
||||||
|
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||||
|
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||||
|
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||||
|
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||||
|
|
||||||
|
/* Module Resolution Options */
|
||||||
|
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
|
||||||
|
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||||
|
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||||
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||||
|
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||||
|
// "types": [], /* Type declaration files to be included in compilation. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||||
|
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
|
||||||
|
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
|
||||||
|
/* Source Map Options */
|
||||||
|
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||||
|
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||||
|
|
||||||
|
/* Experimental Options */
|
||||||
|
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||||
|
|
||||||
|
/* Advanced Options */
|
||||||
|
"skipLibCheck": true /* Skip type checking of declaration files. */,
|
||||||
|
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue