├── .dockerignore ├── .github └── workflows │ ├── codeql-analysis.yml │ └── docker-image.yml ├── .gitignore ├── Dockerfile ├── Dockerfile.arm32v7 ├── Dockerfile.buildx ├── LICENSE ├── README.md ├── SECURITY.md ├── build.gradle ├── docker-compose.yml ├── docker ├── Dockerfile ├── Dockerfile.dev ├── Dockerfile.pi ├── kill.sh ├── run.sh ├── run_pi.sh ├── torrssen2-0.9.49.jar └── transmission │ ├── Dockerfile │ ├── done.sh │ └── settings.json ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lombok.config ├── nuxt ├── .eslintrc.js ├── .gitignore ├── README.md ├── assets │ ├── README.md │ └── style │ │ └── app.styl ├── components │ ├── DownloadDialog.vue │ ├── DownloadListDialog.vue │ ├── DownloadPathDialog.vue │ ├── DownloadStatusDialog.vue │ ├── Feed.vue │ ├── NuxtLogo.vue │ ├── Progress.vue │ ├── README.md │ ├── RssListDialog.vue │ ├── SeenListDialog.vue │ ├── SettingDialog.vue │ ├── Toolbar.vue │ ├── VuetifyLogo.vue │ └── WatchListDialog.vue ├── layouts │ ├── README.md │ └── default.vue ├── middleware │ └── README.md ├── nuxt.config.js ├── package.json ├── pages │ ├── README.md │ ├── _.vue │ └── index.vue ├── plugins │ ├── README.md │ ├── axios.js │ ├── stomp.js │ ├── vue-infinite-scroll.js │ └── vuetify.js ├── static │ ├── README.md │ ├── favicon.ico │ └── v.png ├── store │ ├── download.js │ ├── index.js │ ├── setting.js │ ├── snackbar.js │ └── toolbar.js └── yarn.lock ├── settings.gradle └── src ├── main ├── java │ └── com │ │ └── tarpha │ │ └── torrssen2 │ │ ├── Torrssen2Application.java │ │ ├── auth │ │ └── MyUserPrincipal.java │ │ ├── config │ │ ├── AsyncConfig.java │ │ ├── SchedulerConfig.java │ │ ├── SecurityConfig.java │ │ ├── SwaggerConfig.java │ │ └── WebSocketConfig.java │ │ ├── controller │ │ ├── DownloadController.java │ │ ├── RssController.java │ │ ├── SettingController.java │ │ ├── TransmissionController.java │ │ ├── UserController.java │ │ └── WebSocketController.java │ │ ├── domain │ │ ├── DownloadList.java │ │ ├── DownloadPath.java │ │ ├── RssFeed.java │ │ ├── RssList.java │ │ ├── SeenList.java │ │ ├── Setting.java │ │ ├── User.java │ │ └── WatchList.java │ │ ├── repository │ │ ├── DownloadListRepository.java │ │ ├── DownloadPathRepository.java │ │ ├── RssFeedRepository.java │ │ ├── RssListRepository.java │ │ ├── SeenListRepository.java │ │ ├── SettingRepository.java │ │ ├── UserRepository.java │ │ └── WatchListRepository.java │ │ ├── service │ │ ├── CryptoService.java │ │ ├── DaumMovieTvService.java │ │ ├── DownloadService.java │ │ ├── DownloadStationService.java │ │ ├── FileStationService.java │ │ ├── HttpDownloadService.java │ │ ├── MyUserDetailsService.java │ │ ├── RssLoadService.java │ │ ├── RssMakeService.java │ │ ├── SchedulerService.java │ │ ├── SettingService.java │ │ ├── TelegramService.java │ │ └── TransmissionService.java │ │ ├── util │ │ ├── CommonUtils.java │ │ └── SynologyApiUtils.java │ │ └── vo │ │ ├── TextValueVO.java │ │ └── WatchListVO.java └── resources │ ├── application.yml │ ├── banner.txt │ ├── data.sql │ └── logback-spring.xml └── test └── java └── com └── tarpha └── torrssen2 ├── Torrssen2ApplicationTests.java └── service └── RssMakeServiceTest.java /.dockerignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .git 3 | .gradle 4 | .DS_Store 5 | /build/* 6 | /bin/* 7 | logs/* 8 | /docker/* 9 | **/node_modules 10 | nuxt/dist/* 11 | gradle/wrapper/* 12 | 13 | ### VS Code ### 14 | .vscode/* -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '25 16 * * 1' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'java', 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v2 18 | 19 | - name: Docker Setup QEMU 20 | uses: docker/setup-qemu-action@v1.2.0 21 | 22 | - name: Set up Docker Buildx 23 | uses: docker/setup-buildx-action@v1.6.0 24 | 25 | - name: Login to DockerHub 26 | uses: docker/login-action@v1 27 | with: 28 | username: ${{ secrets.DOCKERHUB_USERNAME }} 29 | password: ${{ secrets.DOCKERHUB_TOKEN }} 30 | 31 | - name: Build Image 32 | run: docker buildx build --platform linux/amd64,linux/arm64 -t tarpha/torrssen2:latest --push . 33 | 34 | # - name: Build and push 35 | # id: docker_build 36 | # uses: docker/build-push-action@v2 37 | # with: 38 | # push: true 39 | # tags: tarpha/torrssen2:latest 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | .DS_Store 4 | /build/ 5 | /bin/ 6 | logs/ 7 | src/main/resources/static/* 8 | !gradle/wrapper/gradle-wrapper.jar 9 | 10 | ### STS ### 11 | .apt_generated 12 | .classpath 13 | .factorypath 14 | .project 15 | .settings 16 | .springBeans 17 | .sts4-cache 18 | 19 | ### IntelliJ IDEA ### 20 | .idea 21 | *.iws 22 | *.iml 23 | *.ipr 24 | /out/ 25 | 26 | ### NetBeans ### 27 | /nbproject/private/ 28 | /nbbuild/ 29 | /dist/ 30 | /nbdist/ 31 | /.nb-gradle/ 32 | 33 | ### VS Code ### 34 | .vscode/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:current-alpine 2 | ENV NODE_OPTIONS --openssl-legacy-provider 3 | RUN mkdir /torrssen2 4 | COPY . /torrssen2 5 | WORKDIR /torrssen2/nuxt 6 | #RUN apt install -y yarn 7 | RUN yarn && yarn build --spa 8 | #RUN npm install && npm run build -- --spa 9 | RUN mkdir -p ../src/main/resources/static 10 | RUN cp -R dist/* ../src/main/resources/static 11 | 12 | FROM gradle:jdk8 13 | RUN mkdir /torrssen2 14 | WORKDIR /torrssen2 15 | COPY --from=0 /torrssen2 . 16 | RUN gradle bootjar 17 | 18 | #FROM adoptopenjdk/openjdk8-openj9:alpine-slim 19 | #FROM adoptopenjdk:8-openj9-focal 20 | FROM openjdk:8-jre 21 | COPY --from=1 /torrssen2/build/libs/torrssen2-*.jar torrssen2.jar 22 | VOLUME [ "/root/data" ] 23 | EXPOSE 8080 24 | #CMD [ "java", "-Xshareclasses", "-Xquickstart", "-jar", "torrssen2.jar"] 25 | CMD [ "java", "-jar", "torrssen2.jar"] 26 | -------------------------------------------------------------------------------- /Dockerfile.arm32v7: -------------------------------------------------------------------------------- 1 | FROM arm32v7/node 2 | RUN mkdir /torrssen2 3 | COPY . /torrssen2 4 | WORKDIR /torrssen2/nuxt 5 | RUN npm install && npm run build -- --spa 6 | RUN mkdir -p ../src/main/resources/static 7 | RUN cp -R dist/* ../src/main/resources/static 8 | 9 | FROM arm32v7/gradle 10 | RUN mkdir /torrssen2 11 | WORKDIR /torrssen2 12 | COPY --from=0 /torrssen2 . 13 | RUN gradle bootjar 14 | 15 | FROM arm32v7/openjdk:8-alpine 16 | COPY --from=1 /torrssen2/build/libs/torrssen2-*.jar torrssen2.jar 17 | VOLUME [ "/root/data" ] 18 | EXPOSE 8080 19 | CMD [ "java", "-jar", "torrssen2.jar"] -------------------------------------------------------------------------------- /Dockerfile.buildx: -------------------------------------------------------------------------------- 1 | FROM node:current-alpine 2 | RUN mkdir /torrssen2 3 | COPY . /torrssen2 4 | WORKDIR /torrssen2/nuxt 5 | RUN npm install && npm run build -- --spa 6 | RUN mkdir -p ../src/main/resources/static 7 | RUN cp -R dist/* ../src/main/resources/static 8 | 9 | FROM gradle:jdk8 10 | RUN mkdir /torrssen2 11 | WORKDIR /torrssen2 12 | COPY --from=0 /torrssen2 . 13 | RUN gradle bootjar 14 | 15 | FROM openjdk:18-jdk 16 | COPY --from=1 /torrssen2/build/libs/torrssen2-*.jar torrssen2.jar 17 | VOLUME [ "/root/data" ] 18 | EXPOSE 8080 19 | CMD [ "java", "-jar", "torrssen2.jar"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 JW.Lee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # torrssen2 2 | 3 | Torrent RSS Site를 등록/관리하고, 다운로드를 요청/관리하고, 자동 다운로드를 수행하는 Webapp (Spring Boot + Nuxt.js) 4 | 5 | ## 기능 6 | 7 | - 복수의 RSS을 주기적으로 읽어와서 게시판 리스트 생성 (주기 설정 가능) 8 | - 리스트의 항목의 다운로드 요청 및 진행율 표시, 취소 가능 (트랜스미션/다운로드스테이션 지원) 9 | - 제목 검색 기능 10 | - 자동 다운로드 설정 기능 (제목, 에피소드, 시즌으로 관리하여 한번 자동 다운로드 된 내역은 다시 받지 않습니다.) 11 | - 다운로드 경로 설정 기능 (기본, 사용자 지정, 경로에 타이틀 추가, 경로에 시즌 추가) 12 | - 다운로드 클라이언트의 현재 다운로드 리스트 관리 기능 13 | - 이력 관리 기능 14 | - 텔레그렘 메시지 발송 기능 15 | - 다크테마 16 | - 모바일 환경 지원 17 | - 로그인 기능 18 | 19 | ## Docker 20 | 21 | docker run -d --name torrssen2 -p 8080:8080 -v {data path}:/root/data tarpha/torrssen2 22 | 23 | ## 수정이력 24 | 25 | - 0.9.54: minor update 26 | - 0.9.53: minor update 27 | - 0.9.52: minor update 28 | - 0.9.51: 내장 RSS 한 곳 추가 29 | - 0.9.50: minor update 30 | - 0.9.49: 내장 RSS 한 곳 추가 31 | - 0.9.48: 포스터 정상 표시 32 | - 0.9.47: F1RST 릴 표시 추가 33 | - 0.9.46: 내장 RSS 정리 34 | - 0.9.45: 내장 RSS 한 곳 추가 35 | - 0.9.44: 자동 다운로드 추가 시 title에 빈 값이 들어가는 문제 수정 36 | - 0.9.43: 자동 다운로드 중복 오류 수정. 내장 RSS 사이트 변경 37 | - 0.9.42: tvSeries 여부에 따른 Feed Title rule 변경 38 | - 0.9.41: 내장 RSS 한 곳 제거 39 | - 0.9.40: Scheduler hang 현상 조치(시도) 40 | - 0.9.39: 내장 RSS SSL 오류 수정 41 | - 0.9.38: 복수 화질 자동 다운로드 기능 추가. 자동 다운로드 이력 정리 기능 추가 42 | - 0.9.37: 마이너 오류 수정. 테마 보정 43 | - 0.9.36: 시스템 다크모드 적용 옵션 추가 44 | - 0.9.35: Spring boot version up (2.2.2), 리스트 내 자동다운로드 상위화질 체크 버그 수정 45 | - 0.9.34: Proxy 서버 지원. 내장 RSS 모듈 추가. 46 | - 0.9.33: FileStation API 변경(?)에 따른 파일명 변경 로직 수정 47 | - 0.9.32: 자동 다운로드 시 RSS 사이트 선택 기능 추가 (선택 안할 시 전체 사이트) 48 | - 0.9.31: 자동 다운로드 관리 개별 건에 대한 사용자 실행 기능 추가 49 | - 0.9.30: RSS Feed desc 컬럼 길이 수정 (1024) 50 | - 0.9.29: RSS Site 관리에서 이름 수정 시 기존 Feed를 못불러 오는 오류 수정 51 | - 0.9.28: RSS Load 오류 수정 52 | - 0.9.27: RSS Feed description에 http link가 있을 경우 표시 53 | - 0.9.26: RSS List 저장 오류 수정 54 | - 0.9.25: 다운로드 스테이션 외부 요청 건 필터링 로직 보안 55 | - 0.9.24: 자동 다운로드 매칭 시 단건 조건 추가 56 | - 0.9.23: DOWNLOAD_LIST URI Column 길이 2048로 변경 57 | - 0.9.22: 자동다운로드 시 자막 다운로드 오류 수정 58 | - 0.9.21: 자동다운로드 이력 파일명 변경 상태 추가 59 | - 0.9.20: 트랜스미션 다운로드 시 .torrent 파일 로직 수정 60 | - 0.9.19: 자동다운로드관리 일괄 수정 항목별로 가능하게 변경 61 | - 0.9.18: 자동다운로드관리 일괄 수정 변경. RSS 일괄 다운로드 추가 62 | - 0.9.17: 자동다운로드관리 검색/일괄 수정 추가 63 | - 0.9.16: 자동다운로드 시 Quality Number Casting 오류 수정 64 | - 0.9.15: 자동다운로드 시 시리즈 여부 추가. 각 리스트 동작 아이콘 위치 변경 65 | - 0.9.14: 다운로드 완료 시 즉시 완료(v) 표시 66 | - 0.9.13: 다운로드 스테이션 텔레그램 발송 오류 수정 67 | - 0.9.11: 자동 다운로드 시 자막 다운로드 기능 추가 68 | - 0.9.10: 트랜스미션/다운로드 스테이션 접속 테스트 후 다운로드 실패 오류 수정 69 | - 0.9.9 : 접속 테스트 추가 (텔레그램/트랜스미션/다운로드 스테이션) 70 | - 0.9.8 : infinite scroll, 사용자 아이디 변경 가능 71 | - 0.9.7 : 다운로드 스테이션 폴더 내 복수 파일 이동 시 삭제 오류 수정 72 | - 0.9.6 : 다운로드 스테이션 폴더 삭제 오류 수정 (이동 시 taskid를 발급하는데 이를 확인해서 완료를 체크하고...) 73 | - 0.9.5 : 화면 수정 (더보기 버튼 크기, 자동다운로드/다운로드완료 여부, RSS 메뉴 스크롤 등), 마이너 버그 수정 74 | - 0.9.4 : 트랜스 미션 사용 시 일반 파일(토렌트가 아닌) 다운로드 로직 추가. 포스터 오류 수정 75 | - 0.9.3 : 다운로드 스테이션 파일명 변경 로직 추가 76 | - 0.9.2 : 다운로드 스테이션 폴더 제거 로직 추가 77 | - 0.9.1 : 내장 다운로드 모듈 비활성화(안정성 하락), 폴더 제거 로직 트랜스미션에서 가능하도록 수정 78 | - 0.9.0 : 마이너 버그 수정. 메모리 관리 향상(openj9) 79 | - 0.8.8 : 자막 다운로드 오류 수정 80 | - 0.8.7 : 폴더 삭제 로직 향상 81 | - 0.8.6 : 마이너 버그 수정. 피드에 자막 정보 추가 82 | - 0.8.5 : 스케줄링 자동 업데이트 83 | - 0.8.2 : 자동다운로드 경로 저장 시 한글 문제 해결. 자동다운로드 체크 시 제목 수정 가능. 합본 제목 정제 수정. 84 | - 0.8.0 : 동시 다운로드 수 설정 (EMBEDDED만) 85 | - 0.7.8 : 마이너 버그 수정 86 | - 0.7.7 : 컨테이너 재시작 시 자동 업데이트 87 | - 0.7.6 : 메뉴에 버전 정보 표시, 내장 다운로더 추가(embedded), 파일명 변경 패턴에 일자 추가. 88 | - 0.6.9 : 트랜스미션 콜백 옵션 추가, FEED link 컬럼 길이 변경(2048), RSS 리스트 제목 파싱 옵션 추가 89 | - 0.6.7 : 로그인 오류 수정, recovery 암호 파일 자동 생성 (/root/data/symmetricKey) 90 | - 0.6.5 : 로그인 기능 추가 91 | - 0.6.2 : 목록 내 경과시간 버그 수정, 트랜스미션 사용 시 별도 콜백 없이도 텔레그램 발송 기능 추가 92 | - 0.6.0 : 마이너 버그 수정 93 | - 0.5.9 : 포스터 매칭 타이틀 로직 수정 94 | - 0.5.8 : 화면에서 리스트 삭제 시 목록 바로 반영. RSS 갱신 버튼 RSS 아이콘에 추가. RSS 갱신 시 리스트에 바로 반영 95 | - 0.5.7 : 다운로드 스테이션 대상 폴더가 없을 시 생성 로직 추가. 리스트 타이틀 정제 로직 변경. 96 | - 0.5.5 : 다운로드 스테이션 오류 몇개 수정, 리스트에 날짜 추가, 리스트 삭제 기능 추가, 리스트 건 수 관리 기능 추가, tomcat -> undertow, 로그 레벨 변경(에러만), 리스트 로드 시 시간 역순으로 저장 97 | - 0.5.0 : Release 98 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 0.9.x | :white_check_mark: | 8 | | < 0.5 | :x: | 9 | 10 | ## Reporting a Vulnerability 11 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '2.3.3.RELEASE' 3 | id "io.freefair.lombok" version "5.1.1" 4 | id 'java' 5 | } 6 | 7 | apply plugin: 'io.spring.dependency-management' 8 | 9 | group = 'com.tarpha' 10 | version = '0.9.54' 11 | sourceCompatibility = '1.8' 12 | 13 | repositories { 14 | mavenCentral() 15 | jcenter() 16 | } 17 | 18 | ext { 19 | swaggerVersion = '2.9.2' 20 | romeVersion = '1.12.0' 21 | httpclientVersion = '4.5.8' 22 | commonsLangVersion = '3.9' 23 | commonsCollectionVersion = '4.3' 24 | commonsIoVersion = '2.6' 25 | commonsCodecVersion = '1.12' 26 | jsonVersion = '20180813' 27 | jsonSmartVersion = '2.3' 28 | asyncHttpClientVersion = '2.10.1' 29 | btVersion = '1.8' 30 | jsoupVersion = '1.13.1' 31 | // htmlunitVersion = '2.55.0' 32 | } 33 | 34 | configurations { 35 | developmentOnly 36 | runtimeClasspath { 37 | extendsFrom developmentOnly 38 | } 39 | implementation { 40 | exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat' 41 | } 42 | } 43 | 44 | springBoot { 45 | buildInfo () 46 | } 47 | 48 | dependencies { 49 | implementation 'org.springframework.boot:spring-boot-starter-actuator' 50 | implementation 'org.springframework.boot:spring-boot-starter-cache' 51 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 52 | implementation 'org.springframework.boot:spring-boot-starter-security' 53 | implementation 'org.springframework.boot:spring-boot-starter-undertow' 54 | implementation 'org.springframework.boot:spring-boot-starter-web' 55 | implementation 'org.springframework.boot:spring-boot-starter-websocket' 56 | 57 | implementation "io.springfox:springfox-swagger2:${swaggerVersion}" 58 | implementation "io.springfox:springfox-swagger-ui:${swaggerVersion}" 59 | 60 | implementation "com.rometools:rome:${romeVersion}" 61 | 62 | implementation "org.apache.httpcomponents:httpclient:${httpclientVersion}" 63 | 64 | implementation "org.apache.commons:commons-lang3:${commonsLangVersion}" 65 | implementation "org.apache.commons:commons-collections4:${commonsCollectionVersion}" 66 | 67 | implementation "org.json:json:${jsonVersion}" 68 | 69 | implementation "commons-io:commons-io:${commonsIoVersion}" 70 | implementation "commons-codec:commons-codec:${commonsCodecVersion}" 71 | 72 | implementation "net.minidev:json-smart:${jsonSmartVersion}" 73 | 74 | implementation "org.asynchttpclient:async-http-client:${asyncHttpClientVersion}" 75 | 76 | // implementation "com.github.atomashpolskiy:bt-core:${btVersion}" 77 | // implementation "com.github.atomashpolskiy:bt-http-tracker-client:${btVersion}" 78 | // implementation "com.github.atomashpolskiy:bt-dht:${btVersion}" 79 | 80 | implementation "org.jsoup:jsoup:${jsoupVersion}" 81 | 82 | // implementation "net.sourceforge.htmlunit:htmlunit:${htmlunitVersion}" 83 | 84 | developmentOnly("org.springframework.boot:spring-boot-devtools") 85 | 86 | runtimeOnly 'com.h2database:h2' 87 | 88 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 89 | testImplementation 'org.springframework.security:spring-security-test' 90 | } 91 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.3" 2 | services: 3 | torrssen2: 4 | image: tarpha/torrssen2:latest 5 | restart: unless-stopped 6 | ports: 7 | - "8080:8080" 8 | volumes: 9 | - /docker/torrssen:/root/data 10 | links: 11 | - php 12 | - tunnel 13 | php: 14 | image: php:7.2-apache 15 | restart: unless-stopped 16 | volumes: 17 | - /docker/php:/var/www/html 18 | tunnel: 19 | image: sadeghhayeri/green-tunnel:latest 20 | restart: unless-stopped -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | #FROM openjdk:8-alpine 2 | FROM adoptopenjdk/openjdk8-openj9:alpine-slim 3 | #COPY torrssen2-0.8.3.jar torrssen2.jar 4 | COPY run.sh /run.sh 5 | COPY kill.sh /kill.sh 6 | RUN apk --no-cache add git 7 | RUN git clone https://github.com/tarpha/torrssen2.git 8 | VOLUME [ "/root/data" ] 9 | EXPOSE 8080 10 | #ENV BASE_URL http://localhost:8080 11 | #CMD [ "java", "-jar", "torrssen2.jar"] 12 | ENTRYPOINT ["/run.sh"] -------------------------------------------------------------------------------- /docker/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | #FROM openjdk:8-alpine 2 | FROM adoptopenjdk/openjdk8-openj9:alpine-slim 3 | COPY torrssen2-*.jar torrssen2.jar 4 | #COPY run.sh /run.sh 5 | #COPY kill.sh /kill.sh 6 | #RUN apk --no-cache add git 7 | #RUN git clone https://github.com/tarpha/torrssen2.git 8 | VOLUME [ "/root/data" ] 9 | EXPOSE 8080 10 | #ENV BASE_URL http://localhost:8080 11 | CMD [ "java", "-jar", "torrssen2.jar", "--spring.profiles.active=dev"] 12 | #ENTRYPOINT ["/run.sh"] -------------------------------------------------------------------------------- /docker/Dockerfile.pi: -------------------------------------------------------------------------------- 1 | FROM tarpha/ubuntu:openjdk-8-pi 2 | ENV LC_ALL=C.UTF-8 3 | COPY run_pi.sh /run.sh 4 | COPY kill.sh /kill.sh 5 | RUN apt-get install -y git 6 | RUN git clone https://github.com/tarpha/torrssen2.git 7 | VOLUME [ "/root/data" ] 8 | EXPOSE 8080 9 | ENTRYPOINT ["/run.sh"] -------------------------------------------------------------------------------- /docker/kill.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ps - ef | grep torrssen2.jar | awk '{print $1}' | xargs kill 4 | -------------------------------------------------------------------------------- /docker/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | while true 4 | do 5 | cd /torrssen2 && git pull && cd / 6 | 7 | cp /torrssen2/docker/torrssen2-*.jar torrssen2.jar 8 | 9 | #java -jar torrssen2.jar 10 | java $JAVA_OPTS -Xshareclasses -Xquickstart -jar torrssen2.jar 11 | done 12 | -------------------------------------------------------------------------------- /docker/run_pi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | while true 4 | do 5 | cd /torrssen2 && git pull && cd / 6 | 7 | cp /torrssen2/docker/torrssen2-*.jar torrssen2.jar 8 | 9 | java -jar torrssen2.jar 10 | #java $JAVA_OPTS -Xshareclasses -Xquickstart -jar torrssen2.jar 11 | done 12 | -------------------------------------------------------------------------------- /docker/torrssen2-0.9.49.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarpha/torrssen2/131ca5b55e986f2fba3ad43b23d1cf8747e4cfdf/docker/torrssen2-0.9.49.jar -------------------------------------------------------------------------------- /docker/transmission/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | 3 | ARG DOCKER_UID 4 | 5 | # Create a user to run the application 6 | RUN adduser -D -u ${DOCKER_UID} transmission 7 | COPY ./settings.json /home/transmission/.config/settings.json 8 | RUN chown -R transmission:transmission /home/transmission 9 | WORKDIR /home/transmission 10 | 11 | # Data and config volumes 12 | VOLUME /home/transmission/.config 13 | VOLUME /home/transmission/Downloads 14 | VOLUME /home/transmission/incomplete 15 | VOLUME /home/transmission/watch 16 | 17 | # Install Transmission 18 | RUN apk update && apk add --no-cache transmission-daemon 19 | 20 | EXPOSE 9091 21 | 22 | USER transmission 23 | ENTRYPOINT ["transmission-daemon", "--foreground", "--log-info"] -------------------------------------------------------------------------------- /docker/transmission/done.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | FILE_NAME=${TR_TORRENT_NAME} 3 | 4 | # DELETE DIRECTORY FILE TO ONLY FILE 5 | # if [ -d "${TR_TORRENT_DIR}/${TR_TORRENT_NAME}" ] 6 | # then 7 | # if [ -f "${TR_TORRENT_DIR}/${TR_TORRENT_NAME}"/*.mp4 ] 8 | # then 9 | # FILE_NAME=$(ls "${TR_TORRENT_DIR}/${TR_TORRENT_NAME}"/*.mp4 | awk '{print $1}' | head -1 | xargs -n 1 basename) 10 | # mv "${TR_TORRENT_DIR}/${TR_TORRENT_NAME}"/*.mp4 "${TR_TORRENT_DIR}" 11 | # rm -rf "${TR_TORRENT_DIR}/${TR_TORRENT_NAME}" 12 | # elif [ -f "${TR_TORRENT_DIR}/${TR_TORRENT_NAME}"/*.mkv ] 13 | # then 14 | # FILE_NAME=$(ls "${TR_TORRENT_DIR}/${TR_TORRENT_NAME}"/*.mp4 | awk '{print $1}' | head -1 | xargs -n 1 basename) 15 | # mv "${TR_TORRENT_DIR}/${TR_TORRENT_NAME}"/*.mkv "${TR_TORRENT_DIR}" 16 | # rm -rf "${TR_TORRENT_DIR}/${TR_TORRENT_NAME}" 17 | # fi 18 | # fi 19 | 20 | #CALL SERVICE 21 | generate_post_data() 22 | { 23 | cat < \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS='"-Xmx64m"' 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS="-Xmx64m" 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /lombok.config: -------------------------------------------------------------------------------- 1 | # This file is generated by the 'io.freefair.lombok' Gradle plugin 2 | config.stopBubbling = true 3 | -------------------------------------------------------------------------------- /nuxt/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | env: { 5 | browser: true, 6 | node: true 7 | }, 8 | extends: 'standard', 9 | // required to lint *.vue files 10 | plugins: [ 11 | 'html' 12 | ], 13 | // add your custom rules here 14 | rules: {}, 15 | globals: {} 16 | } 17 | -------------------------------------------------------------------------------- /nuxt/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # logs 5 | npm-debug.log 6 | 7 | # Nuxt build 8 | .nuxt 9 | 10 | # Nuxt generate 11 | dist 12 | -------------------------------------------------------------------------------- /nuxt/README.md: -------------------------------------------------------------------------------- 1 | # torrssen2-nuxt 2 | 3 | > Nuxt.js + Vuetify.js project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | $ npm install # Or yarn install 10 | 11 | # serve with hot reload at localhost:3000 12 | $ npm run dev 13 | 14 | # build for production and launch server 15 | $ npm run build 16 | $ npm start 17 | 18 | # generate static project 19 | $ npm run generate 20 | ``` 21 | 22 | For detailed explanation on how things work, check out the [Nuxt.js](https://github.com/nuxt/nuxt.js) and [Vuetify.js](https://vuetifyjs.com/) documentation. 23 | -------------------------------------------------------------------------------- /nuxt/assets/README.md: -------------------------------------------------------------------------------- 1 | # ASSETS 2 | 3 | This directory contains your un-compiled assets such as LESS, SASS, or JavaScript. 4 | 5 | More information about the usage of this directory in the documentation: 6 | https://nuxtjs.org/guide/assets#webpacked 7 | 8 | **This directory is not required, you can delete it if you don't want to use it.** 9 | -------------------------------------------------------------------------------- /nuxt/assets/style/app.styl: -------------------------------------------------------------------------------- 1 | // Import and define Vuetify color theme 2 | // https://vuetifyjs.com/en/style/colors 3 | @require '~vuetify/src/stylus/settings/_colors' 4 | $theme := { 5 | primary: $blue.darken-2 6 | accent: $blue.accent-2 7 | secondary: $grey.lighten-1 8 | info: $blue.lighten-1 9 | warning: $amber.darken-2 10 | error: $red.accent-4 11 | success: $green.lighten-2 12 | } 13 | 14 | // Import Vuetify styling 15 | @require '~vuetify/src/stylus/main' 16 | 17 | .page 18 | @extend .fade-transition 19 | -------------------------------------------------------------------------------- /nuxt/components/DownloadListDialog.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 125 | -------------------------------------------------------------------------------- /nuxt/components/DownloadPathDialog.vue: -------------------------------------------------------------------------------- 1 | 129 | 130 | 202 | -------------------------------------------------------------------------------- /nuxt/components/DownloadStatusDialog.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 148 | -------------------------------------------------------------------------------- /nuxt/components/Feed.vue: -------------------------------------------------------------------------------- 1 | 62 | 63 | -------------------------------------------------------------------------------- /nuxt/components/NuxtLogo.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 80 | -------------------------------------------------------------------------------- /nuxt/components/Progress.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 117 | 118 | -------------------------------------------------------------------------------- /nuxt/components/README.md: -------------------------------------------------------------------------------- 1 | # COMPONENTS 2 | 3 | The components directory contains your Vue.js Components. 4 | Nuxt.js doesn't supercharge these components. 5 | 6 | **This directory is not required, you can delete it if you don't want to use it.** 7 | -------------------------------------------------------------------------------- /nuxt/components/RssListDialog.vue: -------------------------------------------------------------------------------- 1 | 131 | 132 | 235 | -------------------------------------------------------------------------------- /nuxt/components/SeenListDialog.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 149 | 150 | -------------------------------------------------------------------------------- /nuxt/components/Toolbar.vue: -------------------------------------------------------------------------------- 1 | 99 | 100 | 266 | -------------------------------------------------------------------------------- /nuxt/components/VuetifyLogo.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 17 | -------------------------------------------------------------------------------- /nuxt/layouts/README.md: -------------------------------------------------------------------------------- 1 | # LAYOUTS 2 | 3 | This directory contains your Application Layouts. 4 | 5 | More information about the usage of this directory in the documentation: 6 | https://nuxtjs.org/guide/views#layouts 7 | 8 | **This directory is not required, you can delete it if you don't want to use it.** 9 | -------------------------------------------------------------------------------- /nuxt/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 20 | -------------------------------------------------------------------------------- /nuxt/middleware/README.md: -------------------------------------------------------------------------------- 1 | # MIDDLEWARE 2 | 3 | This directory contains your Application Middleware. 4 | The middleware lets you define custom function to be ran before rendering a page or a group of pages (layouts). 5 | 6 | More information about the usage of this directory in the documentation: 7 | https://nuxtjs.org/guide/routing#middleware 8 | 9 | **This directory is not required, you can delete it if you don't want to use it.** 10 | -------------------------------------------------------------------------------- /nuxt/nuxt.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /* 3 | ** Headers of the page 4 | */ 5 | head: { 6 | title: 'torrssen2-nuxt', 7 | meta: [ 8 | { charset: 'utf-8' }, 9 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 10 | { hid: 'description', name: 'description', content: 'Nuxt.js + Vuetify.js project' } 11 | ], 12 | link: [ 13 | { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }, 14 | { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' } 15 | ] 16 | }, 17 | env: { 18 | baseUrl: process.env.BASE_URL || 'http://localhost:8080' 19 | }, 20 | plugins: [ 21 | '~/plugins/vuetify.js', 22 | { src: '~plugins/vue-infinite-scroll.js', ssr: false } 23 | ], 24 | css: ['~/assets/style/app.styl'], 25 | /* 26 | ** Customize the progress bar color 27 | */ 28 | loading: { color: '#3B8070' }, 29 | /* 30 | ** Build configuration 31 | */ 32 | build: { 33 | extractCSS: true, 34 | extend (config, ctx) { 35 | // Run ESLint on save 36 | if (ctx.isDev && ctx.isClient) { 37 | config.module.rules.push({ 38 | enforce: 'pre', 39 | test: /\.(js|vue)$/, 40 | loader: 'eslint-loader', 41 | exclude: /(node_modules)/ 42 | }) 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /nuxt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "torrssen2-nuxt", 3 | "version": "1.0.0", 4 | "description": "Nuxt.js + Vuetify.js project", 5 | "author": "Lee Jeongwook ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "nuxt", 9 | "build": "nuxt build", 10 | "start": "nuxt start", 11 | "generate": "nuxt generate" 12 | }, 13 | "dependencies": { 14 | "@stomp/stompjs": "^5.4.2", 15 | "axios": "^0.21.2", 16 | "nuxt": "^2.8.1", 17 | "sockjs-client": "^1.3.0", 18 | "vue-infinite-scroll": "^2.0.2", 19 | "vuetify": "^1.5.16", 20 | "webstomp-client": "^1.2.6" 21 | }, 22 | "devDependencies": { 23 | "babel-eslint": "^10.0.1", 24 | "eslint": "^5.7.0", 25 | "eslint-config-standard": "^12.0.0", 26 | "eslint-loader": "^2.1.1", 27 | "eslint-plugin-html": "^4.0.6", 28 | "eslint-plugin-import": "^2.14.0", 29 | "eslint-plugin-node": "^7.0.1", 30 | "eslint-plugin-promise": "^4.0.1", 31 | "eslint-plugin-standard": "^4.0.0", 32 | "stylus": "^0.54.5", 33 | "stylus-loader": "^3.0.2" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /nuxt/pages/README.md: -------------------------------------------------------------------------------- 1 | # PAGES 2 | 3 | This directory contains your Application Views and Routes. 4 | The framework reads all the .vue files inside this directory and create the router of your application. 5 | 6 | More information about the usage of this directory in the documentation: 7 | https://nuxtjs.org/guide/routing 8 | -------------------------------------------------------------------------------- /nuxt/pages/_.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 264 | -------------------------------------------------------------------------------- /nuxt/pages/index.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 264 | -------------------------------------------------------------------------------- /nuxt/plugins/README.md: -------------------------------------------------------------------------------- 1 | # PLUGINS 2 | 3 | This directory contains your Javascript plugins that you want to run before instantiating the root vue.js application. 4 | 5 | More information about the usage of this directory in the documentation: 6 | https://nuxtjs.org/guide/plugins 7 | 8 | **This directory is not required, you can delete it if you don't want to use it.** 9 | -------------------------------------------------------------------------------- /nuxt/plugins/axios.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | let options = {} 4 | 5 | if (process.env.NODE_ENV !== 'production') { 6 | options = { baseURL: process.env.baseUrl } 7 | } 8 | 9 | export default axios.create(options) 10 | -------------------------------------------------------------------------------- /nuxt/plugins/stomp.js: -------------------------------------------------------------------------------- 1 | import SockJS from 'sockjs-client' 2 | import { Client } from '@stomp/stompjs' 3 | 4 | const client = new Client({ 5 | webSocketFactory: () => new SockJS((process.env.NODE_ENV !== 'production' ? process.env.baseUrl : '') + '/torrssen'), 6 | debug: function (str) { 7 | console.log(str) 8 | }, 9 | reconnectDelay: 5000, 10 | heartbeatIncoming: 4000, 11 | heartbeatOutgoing: 4000 12 | }) 13 | 14 | client.onConnect = function (frame) { 15 | // Do something, all subscribes must be done is this callback 16 | // This is needed because this will be executed after a (re)connect 17 | console.log('Stomp (re)connected: ' + frame.headers['message']) 18 | } 19 | 20 | client.onStompError = function (frame) { 21 | // Will be invoked in case of error encountered at Broker 22 | // Bad login/passcode typically will cause an error 23 | // Complaint brokers will set `message` header with a brief message. Body may contain details. 24 | // Compliant brokers will terminate the connection after any error 25 | console.error('Broker reported error: ' + frame.headers['message']) 26 | console.error('Additional details: ' + frame.body) 27 | } 28 | 29 | client.activate() 30 | 31 | export default { 32 | subscribe: (destination, callback) => client.subscribe(destination, callback), 33 | publish: (destination, body) => client.publish({ destination: destination, body: body }), 34 | activate: () => client.activate(), 35 | connected: () => client.connected 36 | } 37 | -------------------------------------------------------------------------------- /nuxt/plugins/vue-infinite-scroll.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import infiniteScroll from 'vue-infinite-scroll' 3 | 4 | Vue.use(infiniteScroll) 5 | -------------------------------------------------------------------------------- /nuxt/plugins/vuetify.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuetify from 'vuetify' 3 | 4 | Vue.use(Vuetify) 5 | -------------------------------------------------------------------------------- /nuxt/static/README.md: -------------------------------------------------------------------------------- 1 | # STATIC 2 | 3 | This directory contains your static files. 4 | Each file inside this directory is mapped to /. 5 | 6 | Example: /static/robots.txt is mapped as /robots.txt. 7 | 8 | More information about the usage of this directory in the documentation: 9 | https://nuxtjs.org/guide/assets#static 10 | 11 | **This directory is not required, you can delete it if you don't want to use it.** 12 | -------------------------------------------------------------------------------- /nuxt/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarpha/torrssen2/131ca5b55e986f2fba3ad43b23d1cf8747e4cfdf/nuxt/static/favicon.ico -------------------------------------------------------------------------------- /nuxt/static/v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarpha/torrssen2/131ca5b55e986f2fba3ad43b23d1cf8747e4cfdf/nuxt/static/v.png -------------------------------------------------------------------------------- /nuxt/store/download.js: -------------------------------------------------------------------------------- 1 | export const state = () => ({ 2 | show: false, 3 | result: false, 4 | auto: false, 5 | text: '', 6 | path: [], 7 | data: {}, 8 | index: -1, 9 | toggle: false, 10 | download: { 11 | active: false, 12 | done: false, 13 | stop: false, 14 | vueIndex: -1, 15 | id: 0 16 | } 17 | }) 18 | 19 | export const mutations = { 20 | show (state, obj) { 21 | state.show = true 22 | state.result = false 23 | state.auto = false 24 | state.text = '' 25 | state.data = obj.data 26 | state.path = obj.path 27 | state.index = obj.index 28 | }, 29 | setResult (state, value) { 30 | state.result = value 31 | }, 32 | setAuto (state, value) { 33 | state.auto = value 34 | }, 35 | setText (state, value) { 36 | state.text = value 37 | }, 38 | setShow (state, value) { 39 | state.show = value 40 | }, 41 | toggle (state, obj) { 42 | state.toggle = !state.toggle 43 | state.download.active = obj.active 44 | state.download.stop = obj.stop 45 | state.download.vueIndex = obj.vueIndex 46 | state.download.id = obj.id 47 | state.download.done = obj.done 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /nuxt/store/index.js: -------------------------------------------------------------------------------- 1 | export const state = () => ({ 2 | input: '', 3 | modalWidth: 650, 4 | version: '', 5 | dark: false 6 | }) 7 | 8 | export const mutations = { 9 | setInput: function (state, input) { 10 | state.input = input 11 | }, 12 | setDark: function (state, val) { 13 | state.dark = val 14 | }, 15 | setVesrion: function (state, val) { 16 | state.version = val 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /nuxt/store/setting.js: -------------------------------------------------------------------------------- 1 | export const state = () => ({ 2 | showSetting: false, 3 | showRssList: false, 4 | // showRssFeed: false, 5 | showDownloadPath: false, 6 | showWatchList: false, 7 | showSeenList: false, 8 | showDownloadList: false, 9 | showDownloadStatus: false, 10 | downloadStatus: [], 11 | rssList: [] 12 | }) 13 | 14 | export const mutations = { 15 | setShowSetting (state, value) { 16 | state.showSetting = value 17 | }, 18 | setShowRssList (state, value) { 19 | state.showRssList = value 20 | }, 21 | // setShowRssFeed (state, value) { 22 | // state.showRssFeed = value 23 | // }, 24 | setShowDownloadPath (state, value) { 25 | state.showDownloadPath = value 26 | }, 27 | setShowWatchList (state, value) { 28 | state.showWatchList = value 29 | }, 30 | setShowSeenList (state, value) { 31 | state.showSeenList = value 32 | }, 33 | setShowDownloadList (state, value) { 34 | state.showDownloadList = value 35 | }, 36 | setShowDownloadStatus (state, value) { 37 | state.showDownloadStatus = value 38 | }, 39 | setDownloadStatus (state, array) { 40 | state.downloadStatus = array 41 | }, 42 | setRssList (state, array) { 43 | state.rssList = array 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /nuxt/store/snackbar.js: -------------------------------------------------------------------------------- 1 | export const state = () => ({ 2 | show: false, 3 | text: '' 4 | }) 5 | 6 | export const mutations = { 7 | show (state, text) { 8 | state.show = true 9 | state.text = text 10 | }, 11 | setShow (state, value) { 12 | state.show = value 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /nuxt/store/toolbar.js: -------------------------------------------------------------------------------- 1 | export const state = () => ({ 2 | searchText: '', 3 | toggle: false 4 | }) 5 | 6 | export const mutations = { 7 | setSearchText (state, value) { 8 | state.searchText = value 9 | }, 10 | toggle (state) { 11 | state.toggle = !state.toggle 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | rootProject.name = 'torrssen2' 7 | -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/Torrssen2Application.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.scheduling.annotation.EnableScheduling; 6 | 7 | @SpringBootApplication 8 | @EnableScheduling 9 | public class Torrssen2Application { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(Torrssen2Application.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/auth/MyUserPrincipal.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.auth; 2 | 3 | import java.util.Collection; 4 | 5 | import com.tarpha.torrssen2.domain.User; 6 | 7 | import org.springframework.security.core.GrantedAuthority; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | 10 | public class MyUserPrincipal implements UserDetails { 11 | private static final long serialVersionUID = 1L; 12 | private User user; 13 | 14 | public MyUserPrincipal(User user) { 15 | this.user = user; 16 | } 17 | 18 | @Override 19 | public Collection getAuthorities() { 20 | return null; 21 | } 22 | 23 | @Override 24 | public String getPassword() { 25 | return user.getPassword(); 26 | } 27 | 28 | @Override 29 | public String getUsername() { 30 | return user.getUsername(); 31 | } 32 | 33 | @Override 34 | public boolean isAccountNonExpired() { 35 | return true; 36 | } 37 | 38 | @Override 39 | public boolean isAccountNonLocked() { 40 | return true; 41 | } 42 | 43 | @Override 44 | public boolean isCredentialsNonExpired() { 45 | return true; 46 | } 47 | 48 | @Override 49 | public boolean isEnabled() { 50 | return true; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/config/AsyncConfig.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.config; 2 | 3 | import java.util.concurrent.Executor; 4 | 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.scheduling.annotation.AsyncConfigurerSupport; 7 | import org.springframework.scheduling.annotation.EnableAsync; 8 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 9 | 10 | @Configuration 11 | @EnableAsync 12 | public class AsyncConfig extends AsyncConfigurerSupport { 13 | 14 | @Override 15 | public Executor getAsyncExecutor() { 16 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 17 | executor.setCorePoolSize(2); 18 | executor.setMaxPoolSize(10); 19 | executor.setQueueCapacity(500); 20 | executor.setThreadNamePrefix("thread-async-"); 21 | executor.initialize(); 22 | return executor; 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/config/SchedulerConfig.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.config; 2 | 3 | import java.util.Calendar; 4 | import java.util.Date; 5 | import java.util.GregorianCalendar; 6 | import java.util.Optional; 7 | 8 | import com.tarpha.torrssen2.domain.Setting; 9 | import com.tarpha.torrssen2.repository.SettingRepository; 10 | import com.tarpha.torrssen2.service.RssLoadService; 11 | import com.tarpha.torrssen2.service.SchedulerService; 12 | 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.context.annotation.Configuration; 15 | import org.springframework.scheduling.annotation.SchedulingConfigurer; 16 | import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; 17 | import org.springframework.scheduling.config.ScheduledTaskRegistrar; 18 | import org.springframework.scheduling.support.CronTrigger; 19 | 20 | @Configuration 21 | public class SchedulerConfig implements SchedulingConfigurer { 22 | @Autowired 23 | RssLoadService rssLoadService; 24 | 25 | @Autowired 26 | SchedulerService schedulerService; 27 | 28 | @Autowired 29 | SettingRepository settingRepository; 30 | 31 | @Override 32 | public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { 33 | ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); 34 | 35 | threadPoolTaskScheduler.setPoolSize(10); 36 | threadPoolTaskScheduler.setThreadNamePrefix("scheduled-task-pool-"); 37 | threadPoolTaskScheduler.initialize(); 38 | scheduledTaskRegistrar.setTaskScheduler(threadPoolTaskScheduler); 39 | 40 | scheduledTaskRegistrar.addTriggerTask(() -> rssLoadService.asyncLoadRss(), t -> { 41 | Calendar nextExecutionTime = new GregorianCalendar(); 42 | Date lastActualExecutionTime = t.lastActualExecutionTime(); 43 | nextExecutionTime.setTime(lastActualExecutionTime != null ? lastActualExecutionTime : new Date()); 44 | Optional optionalSetting = settingRepository.findByKey("RSS_LOAD_INTERVAL"); 45 | if (optionalSetting.isPresent()) { 46 | nextExecutionTime.add(Calendar.MINUTE, Integer.parseInt(optionalSetting.get().getValue())); 47 | } 48 | return nextExecutionTime.getTime(); 49 | }); 50 | 51 | scheduledTaskRegistrar.addTriggerTask(() -> schedulerService.runTask(), t -> { 52 | Calendar nextExecutionTime = new GregorianCalendar(); 53 | Date lastActualExecutionTime = t.lastActualExecutionTime(); 54 | nextExecutionTime.setTime(lastActualExecutionTime != null ? lastActualExecutionTime : new Date()); 55 | Optional optionalSetting = settingRepository.findByKey("DOWNLOAD_CHECK_INTERVAL"); 56 | if (optionalSetting.isPresent()) { 57 | nextExecutionTime.add(Calendar.MINUTE, Integer.parseInt(optionalSetting.get().getValue())); 58 | } 59 | return nextExecutionTime.getTime(); 60 | }); 61 | 62 | scheduledTaskRegistrar.addTriggerTask(() -> schedulerService.killTask(), t -> { 63 | String cronExp = "0/5 * * * * ?"; 64 | Optional optionalSetting = settingRepository.findByKey("CRON_EXR"); 65 | if (optionalSetting.isPresent()) { 66 | cronExp = optionalSetting.get().getValue(); 67 | } 68 | return new CronTrigger(cronExp).nextExecutionTime(t); 69 | }); 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.config; 2 | 3 | import java.io.File; 4 | import java.security.NoSuchAlgorithmException; 5 | import java.util.Base64; 6 | import java.util.Optional; 7 | 8 | import javax.crypto.KeyGenerator; 9 | import javax.crypto.SecretKey; 10 | 11 | import com.tarpha.torrssen2.domain.Setting; 12 | import com.tarpha.torrssen2.domain.User; 13 | import com.tarpha.torrssen2.repository.SettingRepository; 14 | import com.tarpha.torrssen2.repository.UserRepository; 15 | import com.tarpha.torrssen2.service.MyUserDetailsService; 16 | 17 | import org.apache.commons.io.FileUtils; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.context.annotation.Bean; 20 | import org.springframework.context.annotation.Configuration; 21 | import org.springframework.security.authentication.dao.DaoAuthenticationProvider; 22 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 23 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 24 | import org.springframework.security.config.annotation.web.builders.WebSecurity; 25 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 26 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 27 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 28 | import org.springframework.security.crypto.password.PasswordEncoder; 29 | 30 | @Configuration 31 | @EnableWebSecurity 32 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 33 | 34 | @Autowired 35 | SettingRepository settingRepository; 36 | 37 | @Autowired 38 | UserRepository userRepository; 39 | 40 | @Autowired 41 | MyUserDetailsService userDetailsService; 42 | 43 | @Override 44 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 45 | auth.authenticationProvider(authenticationProvider()); 46 | } 47 | 48 | @Bean 49 | public DaoAuthenticationProvider authenticationProvider() { 50 | DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); 51 | authProvider.setUserDetailsService(userDetailsService); 52 | authProvider.setPasswordEncoder(encoder()); 53 | return authProvider; 54 | } 55 | 56 | @Bean 57 | public PasswordEncoder encoder() { 58 | return new BCryptPasswordEncoder(11); 59 | } 60 | 61 | @Override 62 | public void configure(WebSecurity web) throws Exception 63 | { 64 | web.ignoring().antMatchers("/css/**", "/script/**", "image/**", "/fonts/**", "lib/**"); 65 | } 66 | 67 | @Override 68 | protected void configure(HttpSecurity http) throws Exception { 69 | 70 | String symmetricKey = symmetricKey(); 71 | String path = System.getProperty("user.home") + File.separator + "data" + File.separator + "symmetricKey"; 72 | FileUtils.writeStringToFile(new File(path), symmetricKey, "UTF-8", false); 73 | 74 | User recovery = userRepository.findByUsername("recovery"); 75 | if(recovery == null) { 76 | User user = new User(); 77 | user.setUsername("recovery"); 78 | user.setPassword(encoder().encode(symmetricKey)); 79 | userRepository.save(user); 80 | } else { 81 | recovery.setPassword(encoder().encode(symmetricKey)); 82 | userRepository.save(recovery); 83 | } 84 | 85 | // User torrssen = userRepository.findByUsername("torrssen"); 86 | // if(torrssen == null) { 87 | // User user = new User(); 88 | // user.setUsername("torrssen"); 89 | // user.setPassword(""); 90 | // userRepository.save(user); 91 | // } 92 | 93 | Optional optionalSetting = settingRepository.findByKey("USE_LOGIN"); 94 | if(optionalSetting.isPresent()) { 95 | if(!Boolean.parseBoolean(optionalSetting.get().getValue())) { 96 | http.authorizeRequests() 97 | .anyRequest().permitAll() 98 | .and().headers().frameOptions().sameOrigin() 99 | .and().csrf().disable(); 100 | 101 | } else { 102 | http.authorizeRequests() 103 | .antMatchers("/login").permitAll() 104 | .antMatchers("/h2-console/**").permitAll() 105 | .anyRequest().authenticated() 106 | .and().formLogin().permitAll().defaultSuccessUrl("/", true) 107 | .and().headers().frameOptions().sameOrigin() 108 | .and().csrf().disable(); 109 | } 110 | } 111 | } 112 | 113 | private String symmetricKey() { 114 | try { 115 | KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); 116 | keyGenerator.init(128); 117 | SecretKey key = keyGenerator.generateKey(); 118 | return Base64.getEncoder().encodeToString(key.getEncoded()); 119 | } catch (NoSuchAlgorithmException e) { 120 | throw new RuntimeException(e); 121 | } 122 | } 123 | 124 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | import springfox.documentation.builders.PathSelectors; 7 | import springfox.documentation.builders.RequestHandlerSelectors; 8 | import springfox.documentation.spi.DocumentationType; 9 | import springfox.documentation.spring.web.plugins.Docket; 10 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 11 | 12 | @Configuration 13 | @EnableSwagger2 14 | public class SwaggerConfig { 15 | 16 | @Bean 17 | public Docket api() { 18 | return new Docket(DocumentationType.SWAGGER_2) 19 | .select() 20 | .apis(RequestHandlerSelectors.any()) 21 | .paths(PathSelectors.any()) 22 | .build(); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/config/WebSocketConfig.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.messaging.simp.config.MessageBrokerRegistry; 5 | import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; 6 | import org.springframework.web.socket.config.annotation.StompEndpointRegistry; 7 | import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; 8 | 9 | @Configuration 10 | @EnableWebSocketMessageBroker 11 | public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { 12 | 13 | @Override 14 | public void configureMessageBroker(MessageBrokerRegistry config) { 15 | config.enableSimpleBroker("/topic"); 16 | config.setApplicationDestinationPrefixes("/app"); 17 | } 18 | 19 | @Override 20 | public void registerStompEndpoints(StompEndpointRegistry registry) { 21 | registry.addEndpoint("/torrssen").setAllowedOrigins("*").withSockJS(); 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/controller/DownloadController.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.controller; 2 | 3 | import java.util.Optional; 4 | 5 | import com.tarpha.torrssen2.domain.DownloadList; 6 | import com.tarpha.torrssen2.repository.DownloadListRepository; 7 | import com.tarpha.torrssen2.service.DownloadService; 8 | import com.tarpha.torrssen2.service.SettingService; 9 | 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.web.bind.annotation.CrossOrigin; 12 | import org.springframework.web.bind.annotation.GetMapping; 13 | import org.springframework.web.bind.annotation.PathVariable; 14 | import org.springframework.web.bind.annotation.PostMapping; 15 | import org.springframework.web.bind.annotation.RequestBody; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.RequestParam; 18 | import org.springframework.web.bind.annotation.RestController; 19 | 20 | import io.swagger.annotations.Api; 21 | 22 | @RestController 23 | @RequestMapping(value = "/api/download/") 24 | // @CrossOrigin(origins = "http://localhost:3000") 25 | @CrossOrigin(origins = "*") 26 | @Api 27 | public class DownloadController { 28 | @Autowired 29 | private DownloadListRepository downloadListRepository; 30 | 31 | @Autowired 32 | private SettingService settingService; 33 | 34 | @Autowired 35 | private DownloadService downloadService; 36 | 37 | @GetMapping(value = "/app") 38 | public String getApp() { 39 | return settingService.getDownloadApp(); 40 | } 41 | 42 | @GetMapping(value = "/id/{id}") 43 | public DownloadList getDownload(@PathVariable("id") long id) { 44 | return downloadService.getInfo(id); 45 | } 46 | 47 | @PostMapping(value = "/create") 48 | public long create(@RequestBody DownloadList download) { 49 | return downloadService.create(download); 50 | } 51 | 52 | @PostMapping(value = "/remove") 53 | public int remove(@RequestBody DownloadList download) { 54 | return downloadService.remove(download); 55 | } 56 | 57 | @GetMapping(value = "/magnet") 58 | public DownloadList downloadStatus(@RequestParam("magnet") String magnet) { 59 | Optional download = downloadListRepository.findFirstByUriAndDoneOrderByCreateDtDesc(magnet, false); 60 | if(download.isPresent()) { 61 | if(getDownload(download.get().getId()) != null) { 62 | return download.get(); 63 | } 64 | } 65 | return null; 66 | } 67 | 68 | @GetMapping(value = "/conn-test") 69 | public boolean connTest(@RequestParam("app") String app, @RequestParam("host") String host, 70 | @RequestParam("port") String port, @RequestParam("id") String id, @RequestParam("pwd") String pwd) { 71 | return downloadService.connTest(app, host, port, id, pwd); 72 | } 73 | 74 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/controller/RssController.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.controller; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Optional; 6 | 7 | import com.tarpha.torrssen2.domain.DownloadList; 8 | import com.tarpha.torrssen2.domain.RssFeed; 9 | import com.tarpha.torrssen2.domain.RssList; 10 | import com.tarpha.torrssen2.domain.WatchList; 11 | import com.tarpha.torrssen2.repository.DownloadListRepository; 12 | import com.tarpha.torrssen2.repository.RssFeedRepository; 13 | import com.tarpha.torrssen2.repository.RssListRepository; 14 | import com.tarpha.torrssen2.repository.WatchListRepository; 15 | import com.tarpha.torrssen2.service.DownloadService; 16 | import com.tarpha.torrssen2.service.RssLoadService; 17 | 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.data.domain.Page; 20 | import org.springframework.data.domain.Pageable; 21 | import org.springframework.web.bind.annotation.CrossOrigin; 22 | import org.springframework.web.bind.annotation.GetMapping; 23 | import org.springframework.web.bind.annotation.PostMapping; 24 | import org.springframework.web.bind.annotation.RequestBody; 25 | import org.springframework.web.bind.annotation.RequestMapping; 26 | import org.springframework.web.bind.annotation.RequestParam; 27 | import org.springframework.web.bind.annotation.RestController; 28 | 29 | import io.swagger.annotations.Api; 30 | 31 | @RestController 32 | @RequestMapping(value = "/api/rss/") 33 | // @CrossOrigin(origins = "http://localhost:3000") 34 | @CrossOrigin(origins = "*") 35 | @Api 36 | public class RssController { 37 | @Autowired 38 | private RssFeedRepository rssFeedRepository; 39 | 40 | @Autowired 41 | private RssListRepository rssListRepository; 42 | 43 | @Autowired 44 | private DownloadListRepository downloadListRepository; 45 | 46 | @Autowired 47 | private WatchListRepository watchListRepository; 48 | 49 | @Autowired 50 | private RssLoadService rssLoadService; 51 | 52 | @Autowired 53 | private DownloadService downloadService; 54 | 55 | private void setInfo(RssFeed feed) { 56 | Optional download = downloadListRepository.findFirstByUriAndDoneOrderByCreateDtDesc(feed.getLink(), false); 57 | if(download.isPresent()) { 58 | if(downloadService.getInfo(download.get().getId()) != null) { 59 | feed.setDownloadId(download.get().getId()); 60 | feed.setDownloading(true); 61 | } 62 | } 63 | 64 | Optional downloaded = downloadListRepository.findFirstByUriAndDoneOrderByCreateDtDesc(feed.getLink(), true); 65 | if(downloaded.isPresent()) { 66 | feed.setDownloaded(downloaded.get().getDone()); 67 | } 68 | 69 | Optional optionalWatchList = watchListRepository.findByTitleRegex(feed.getTitle(), feed.getRssQuality()); 70 | if(optionalWatchList.isPresent()) { 71 | feed.setWatch(rssLoadService.checkWatchListQuality(feed, optionalWatchList.get())); 72 | } 73 | } 74 | 75 | private List rssSite() { 76 | List ret = new ArrayList<>(); 77 | List list = rssListRepository.findByShow(true); 78 | for(RssList rss: list) { 79 | if(rss.getShow()) { 80 | ret.add(rss.getName()); 81 | } 82 | } 83 | return ret; 84 | } 85 | 86 | @GetMapping(value = "/feed/list") 87 | public Page feedList(Pageable pageable) { 88 | Page feedList = rssFeedRepository.findByRssSiteIn(rssSite(), pageable); 89 | for(RssFeed feed: feedList) { 90 | setInfo(feed); 91 | } 92 | return feedList; 93 | } 94 | 95 | @GetMapping(value = "/feed/search") 96 | public Page searchList(@RequestParam("title") String title, Pageable pageable) { 97 | Page feedList = rssFeedRepository.findByTitleContainingAndRssSiteIn(title, rssSite(), pageable); 98 | for(RssFeed feed: feedList) { 99 | setInfo(feed); 100 | } 101 | return feedList; 102 | } 103 | 104 | @PostMapping(value = "/feed/delete") 105 | public void deleteFeed() { 106 | rssFeedRepository.deleteAll(); 107 | } 108 | 109 | @PostMapping(value = "/feed/delete/rss-site") 110 | public void deleteFeedByRssSite(@RequestBody RssList rssList) { 111 | rssFeedRepository.deleteByRssSite(rssList.getName()); 112 | } 113 | 114 | @PostMapping(value = "/feed/delete/rss-site/list") 115 | public void deleteFeedByRssSiteList(@RequestBody List rssSiteList) { 116 | for(String rssSite: rssSiteList) { 117 | rssFeedRepository.deleteByRssSite(rssSite); 118 | } 119 | } 120 | 121 | @PostMapping(value = "/reload") 122 | public String reLoad() { 123 | rssLoadService.asyncLoadRss(); 124 | return "success"; 125 | } 126 | 127 | @GetMapping(value = "/feed/regex/test") 128 | public List regexTest(@RequestParam("title") String title) { 129 | return rssFeedRepository.testRegexTitle(title); 130 | } 131 | 132 | @GetMapping(value = "/rss-site/distinct") 133 | public List distinctRssSite() { 134 | return rssFeedRepository.distinctRssSite(); 135 | } 136 | 137 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/controller/TransmissionController.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.controller; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Optional; 6 | 7 | import com.tarpha.torrssen2.domain.DownloadList; 8 | import com.tarpha.torrssen2.domain.SeenList; 9 | import com.tarpha.torrssen2.domain.Setting; 10 | import com.tarpha.torrssen2.repository.DownloadListRepository; 11 | import com.tarpha.torrssen2.repository.SeenListRepository; 12 | import com.tarpha.torrssen2.repository.SettingRepository; 13 | import com.tarpha.torrssen2.service.TelegramService; 14 | import com.tarpha.torrssen2.service.TransmissionService; 15 | import com.tarpha.torrssen2.util.CommonUtils; 16 | 17 | import org.apache.commons.lang3.StringUtils; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.web.bind.annotation.CrossOrigin; 20 | import org.springframework.web.bind.annotation.PostMapping; 21 | import org.springframework.web.bind.annotation.RequestBody; 22 | import org.springframework.web.bind.annotation.RequestMapping; 23 | import org.springframework.web.bind.annotation.RestController; 24 | 25 | import io.swagger.annotations.Api; 26 | import lombok.extern.slf4j.Slf4j; 27 | 28 | @RestController 29 | @Slf4j 30 | @RequestMapping(value = "/api/transmission/") 31 | // @CrossOrigin(origins = "http://localhost:3000") 32 | @CrossOrigin(origins = "*") 33 | @Api 34 | public class TransmissionController { 35 | @Autowired 36 | private SettingRepository settingRepository; 37 | 38 | @Autowired 39 | private SeenListRepository seenListRepository; 40 | 41 | @Autowired 42 | private DownloadListRepository downloadListRepository; 43 | 44 | @Autowired 45 | private TransmissionService transmissionService; 46 | 47 | @Autowired 48 | private TelegramService telegramService; 49 | 50 | @PostMapping(value = "/download-done") 51 | public int downloadDone(@RequestBody DownloadList downloadList) throws Exception { 52 | int ret = 0; 53 | log.debug("download-done"); 54 | log.debug(downloadList.toString()); 55 | 56 | // 다운로드 정보를 가져온다. 57 | Optional optionalInfo = downloadListRepository.findById(downloadList.getId()); 58 | if (optionalInfo.isPresent()) { 59 | DownloadList info = optionalInfo.get(); 60 | 61 | // 파일명을 변경한다. 62 | if (!StringUtils.isBlank(info.getRename())) { 63 | if(!CommonUtils.renameFile( 64 | downloadList.getDownloadPath(), 65 | downloadList.getFileName(), 66 | info.getRename())) { 67 | ret = -1; 68 | } 69 | } 70 | 71 | // Download List 완료 처리 72 | info.setDone(true); 73 | info.setFileName(downloadList.getFileName()); 74 | info.setDownloadPath(downloadList.getDownloadPath()); 75 | info.setIsFake(downloadList.getIsFake()); 76 | info.setPercentDone(100); 77 | 78 | //Telegram Message를 발송한다. 79 | Optional optionalSetting = settingRepository.findByKey("SEND_TELEGRAM"); 80 | if (optionalSetting.isPresent()) { 81 | if (Boolean.parseBoolean(optionalSetting.get().getValue())) { 82 | log.info("Send Telegram: " + info.getName()); 83 | if(telegramService.sendMessage("" + info.getName() + "의 다운로드가 완료되었습니다.")) { 84 | info.setIsSentAlert(true); 85 | } 86 | 87 | } 88 | } 89 | 90 | downloadListRepository.save(info); 91 | } 92 | 93 | // 완료 시 삭제 여부 94 | Optional optionalSetting = settingRepository.findByKey("DONE_DELETE"); 95 | if (optionalSetting.isPresent()) { 96 | if (Boolean.parseBoolean(optionalSetting.get().getValue())) { 97 | Thread.sleep(10000); 98 | log.info("Remove Torrent: " + downloadList.getFileName()); 99 | 100 | List ids = new ArrayList(); 101 | ids.add(downloadList.getId()); 102 | transmissionService.torrentRemove(ids); 103 | } 104 | } 105 | 106 | log.debug("removeDirectory"); 107 | List inners = CommonUtils.removeDirectory(downloadList.getDownloadPath(), downloadList.getFileName(), settingRepository); 108 | 109 | if (!StringUtils.isBlank(downloadList.getRename())) { 110 | log.debug("getRename: " + downloadList.getRename()); 111 | if(inners == null) { 112 | boolean renameStatus = CommonUtils.renameFile(downloadList.getDownloadPath(), downloadList.getFileName(), downloadList.getRename()); 113 | setSeenList(downloadList.getUri(), String.valueOf(renameStatus)); 114 | } else { 115 | for(String name: inners) { 116 | if(StringUtils.contains(downloadList.getFileName(), name)) { 117 | boolean renameStatus = CommonUtils.renameFile(downloadList.getDownloadPath(), name, downloadList.getRename()); 118 | setSeenList(downloadList.getUri(), String.valueOf(renameStatus)); 119 | } 120 | } 121 | } 122 | } 123 | 124 | return ret; 125 | } 126 | 127 | private void setSeenList(String link, String renameStatus) { 128 | Optional optionalSeen = seenListRepository.findFirstByLink(link); 129 | if(optionalSeen.isPresent()) { 130 | SeenList seen = optionalSeen.get(); 131 | seen.setRenameStatus(renameStatus); 132 | seenListRepository.save(seen); 133 | } 134 | 135 | } 136 | 137 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.controller; 2 | 3 | import com.tarpha.torrssen2.domain.User; 4 | import com.tarpha.torrssen2.repository.UserRepository; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.security.crypto.password.PasswordEncoder; 8 | import org.springframework.web.bind.annotation.CrossOrigin; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RestController; 14 | 15 | import io.swagger.annotations.Api; 16 | 17 | @RestController 18 | @RequestMapping(value = "/api/user/") 19 | // @CrossOrigin(origins = "http://localhost:3000") 20 | @CrossOrigin(origins = "*") 21 | @Api 22 | public class UserController { 23 | @Autowired 24 | private UserRepository userRepository; 25 | 26 | @Autowired 27 | private PasswordEncoder passwordEncoder; 28 | 29 | @GetMapping(value = "/admin") 30 | public User getAdmin() { 31 | return userRepository.findFirstByUsernameNot("recovery"); 32 | } 33 | 34 | @PostMapping(value = "/admin") 35 | public void setAdmin(@RequestBody User user) { 36 | user.setPassword(passwordEncoder.encode(user.getPassword())); 37 | userRepository.save(user); 38 | } 39 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/controller/WebSocketController.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.controller; 2 | 3 | import java.util.List; 4 | 5 | import com.tarpha.torrssen2.domain.DownloadList; 6 | import com.tarpha.torrssen2.service.DownloadService; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.messaging.handler.annotation.DestinationVariable; 10 | import org.springframework.messaging.handler.annotation.MessageMapping; 11 | import org.springframework.stereotype.Controller; 12 | 13 | @Controller 14 | public class WebSocketController { 15 | @Autowired 16 | private DownloadService downloadService; 17 | 18 | @MessageMapping("/rate/{sid}") 19 | public DownloadList downloadRate(@DestinationVariable String sid) throws Exception { 20 | return downloadService.getInfo(Long.valueOf(sid)); 21 | } 22 | 23 | @MessageMapping("/rate/list") 24 | public List downloadRateList() { 25 | return downloadService.list(); 26 | } 27 | 28 | @MessageMapping("/remove") 29 | public DownloadList remove(DownloadList download) { 30 | if (downloadService.remove(download) >= 0) { 31 | return download; 32 | } else { 33 | return null; 34 | } 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/domain/DownloadList.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.domain; 2 | 3 | import java.util.Date; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Entity; 7 | import javax.persistence.Id; 8 | import javax.persistence.Temporal; 9 | import javax.persistence.TemporalType; 10 | 11 | import lombok.Data; 12 | 13 | @Entity 14 | @Data 15 | public class DownloadList { 16 | 17 | @Id 18 | private Long id; 19 | 20 | private String dbid; 21 | 22 | private String name; 23 | 24 | @Column(length = 1024) 25 | private String fileName; 26 | 27 | private String downloadPath; 28 | 29 | private String rename; 30 | 31 | @Column(length = 2048) 32 | private String uri; 33 | 34 | private String rssTitle; 35 | 36 | private String rssReleaseGroup; 37 | 38 | private Integer percentDone = 0; 39 | 40 | private Integer status = 3; 41 | 42 | private Integer vueItemIndex; 43 | 44 | private Boolean auto = false; 45 | 46 | private Boolean done = false; 47 | 48 | private Boolean cancel = false; 49 | 50 | private Boolean isFake = false; 51 | 52 | private Boolean isSentAlert = false; 53 | 54 | private Boolean task = false; 55 | 56 | private String taskId; 57 | 58 | private String deletePath; 59 | 60 | @Temporal(TemporalType.TIMESTAMP) 61 | private Date createDt = new Date(); 62 | 63 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/domain/DownloadPath.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.domain; 2 | 3 | import java.util.Date; 4 | 5 | import javax.persistence.Entity; 6 | import javax.persistence.Id; 7 | import javax.persistence.Temporal; 8 | import javax.persistence.TemporalType; 9 | 10 | import lombok.Data; 11 | 12 | @Entity 13 | @Data 14 | public class DownloadPath { 15 | 16 | @Id 17 | private String name; 18 | 19 | private String path; 20 | 21 | private Boolean useTitle = false; 22 | 23 | private Boolean useSeason = false; 24 | 25 | @Temporal(TemporalType.TIMESTAMP) 26 | private Date createDt = new Date(); 27 | 28 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/domain/RssFeed.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.domain; 2 | 3 | import java.util.Date; 4 | import java.util.regex.Matcher; 5 | import java.util.regex.Pattern; 6 | 7 | import javax.persistence.Column; 8 | import javax.persistence.Entity; 9 | import javax.persistence.Id; 10 | import javax.persistence.Temporal; 11 | import javax.persistence.TemporalType; 12 | 13 | import com.rometools.rome.feed.synd.SyndEntry; 14 | 15 | import org.apache.commons.lang3.RegExUtils; 16 | import org.apache.commons.lang3.StringUtils; 17 | 18 | import lombok.Data; 19 | 20 | @Entity 21 | @Data 22 | public class RssFeed { 23 | 24 | @Id 25 | @Column(length = 2048) 26 | private String link; 27 | 28 | private String title; 29 | 30 | @Column(length = 1024) 31 | private String desc; 32 | 33 | private String rssEpisode; 34 | 35 | private String rssSeason = "01"; 36 | 37 | private String rssTitle; 38 | 39 | private String rssQuality; 40 | 41 | private String rssReleaseGroup; 42 | 43 | private String rssSite; 44 | 45 | private String rssPoster; 46 | 47 | private String rssDate; 48 | 49 | private String watchTitle; 50 | 51 | private Long downloadId; 52 | 53 | private Boolean downloading = false; 54 | 55 | private Boolean tvSeries = true; 56 | 57 | private Boolean downloaded = false; 58 | 59 | private Boolean watch = false; 60 | 61 | @Temporal(TemporalType.TIMESTAMP) 62 | private Date createDt = new Date(); 63 | 64 | public void setRssEpisodeByTitle(String title) { 65 | Pattern pattern = Pattern.compile(".*e(\\d{2,}).*", Pattern.CASE_INSENSITIVE); 66 | Matcher matcher = pattern.matcher(title); 67 | 68 | String episode = "01"; 69 | 70 | if (matcher.matches()) { 71 | episode = matcher.group(1); 72 | } else { 73 | pattern = Pattern.compile(".*-\\s*(\\d{2,}).*", Pattern.CASE_INSENSITIVE); 74 | matcher = pattern.matcher(title); 75 | 76 | if (matcher.matches()) { 77 | episode = matcher.group(1); 78 | } 79 | } 80 | 81 | this.rssEpisode = episode; 82 | } 83 | 84 | public void setRssSeasonByTitle(String title) { 85 | Pattern pattern = Pattern.compile(".*s(\\d{1,}).*", Pattern.CASE_INSENSITIVE); 86 | Matcher matcher = pattern.matcher(title); 87 | 88 | String season = "01"; 89 | 90 | if (matcher.matches()) { 91 | season = matcher.group(1); 92 | } else { 93 | pattern = Pattern.compile(".*시즌(\\d{1,}).*", Pattern.CASE_INSENSITIVE); 94 | matcher = pattern.matcher(title); 95 | 96 | if (matcher.matches()) { 97 | season = matcher.group(1); 98 | } 99 | } 100 | 101 | this.rssSeason = season; 102 | } 103 | 104 | public void setRssTitleByTitle(String title) { 105 | Pattern pattern = Pattern.compile(".*(e\\d{2,}).*", Pattern.CASE_INSENSITIVE); 106 | Matcher matcher = pattern.matcher(title); 107 | 108 | String rssTitle = title; 109 | 110 | if (matcher.matches()) { 111 | rssTitle = rssTitle.substring(0, matcher.start(1)).replaceAll("\\.", ""); 112 | } 113 | 114 | // pattern = Pattern.compile("\\d{1,}-\\d{1,}회 합본"); 115 | pattern = Pattern.compile("E{0,1}\\d{1,}.{0,1}E{0,1}\\d{1,}회.{0,1}합본"); 116 | rssTitle = RegExUtils.removeAll(rssTitle, pattern); 117 | 118 | pattern = Pattern.compile("\\[[^\\]]{1,}\\]"); 119 | rssTitle = RegExUtils.removeAll(rssTitle, pattern); 120 | 121 | this.rssTitle = StringUtils.trim(rssTitle); 122 | } 123 | 124 | public void setRssQualityBytitle(String title) { 125 | Pattern pattern = Pattern.compile(".*[^0-9](\\d{3,4}p).*", Pattern.CASE_INSENSITIVE); 126 | Matcher matcher = pattern.matcher(title); 127 | 128 | String quality = ""; 129 | 130 | if (matcher.matches()) { 131 | quality = matcher.group(1); 132 | } 133 | 134 | this.rssQuality = quality; 135 | } 136 | 137 | public void setRssReleaseGroupByTitle(String title) { 138 | String rssReleaseGroup = "OTHERS"; 139 | 140 | if (StringUtils.containsIgnoreCase(title, "next")) { 141 | rssReleaseGroup = "NEXT"; 142 | } else if (StringUtils.containsIgnoreCase(title, "once")) { 143 | rssReleaseGroup = "ONCE"; 144 | } else if (StringUtils.containsIgnoreCase(title, "Chaos")) { 145 | rssReleaseGroup = "Chaos"; 146 | } else if (StringUtils.containsIgnoreCase(title, "Hel")) { 147 | rssReleaseGroup = "Hel"; 148 | } else if (StringUtils.containsIgnoreCase(title, "DWBH")) { 149 | rssReleaseGroup = "DWBH"; 150 | } else if (StringUtils.containsIgnoreCase(title, "BluRay")) { 151 | rssReleaseGroup = "BluRay"; 152 | } else if (StringUtils.containsIgnoreCase(title, "WEBRip")) { 153 | rssReleaseGroup = "WEBRip"; 154 | } else if (StringUtils.containsIgnoreCase(title, "Deresisi")) { 155 | rssReleaseGroup = "Deresisi"; 156 | } else if (StringUtils.containsIgnoreCase(title, "F1RST")) { 157 | rssReleaseGroup = "F1RST"; 158 | } 159 | 160 | this.rssReleaseGroup = rssReleaseGroup; 161 | } 162 | 163 | public void setRssDateBytitle(String title) { 164 | Pattern pattern = Pattern.compile(".*(\\d{6,8}).*", Pattern.CASE_INSENSITIVE); 165 | Matcher matcher = pattern.matcher(title); 166 | 167 | String rssDate = ""; 168 | 169 | if (matcher.matches()) { 170 | rssDate = matcher.group(1); 171 | } 172 | 173 | this.rssDate = rssDate; 174 | } 175 | 176 | public void setLinkByKey(String key, SyndEntry syndEntry) { 177 | if (StringUtils.isEmpty(key) || StringUtils.equals(key, "link")) { 178 | this.link = syndEntry.getLink(); 179 | } 180 | } 181 | 182 | public String getLinkByKey(String key, SyndEntry syndEntry) { 183 | if (StringUtils.isEmpty(key) || StringUtils.equals(key, "link")) { 184 | return syndEntry.getLink(); 185 | } 186 | 187 | return null; 188 | } 189 | 190 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/domain/RssList.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.domain; 2 | 3 | import java.util.Date; 4 | 5 | import javax.persistence.Entity; 6 | import javax.persistence.Id; 7 | import javax.persistence.Temporal; 8 | import javax.persistence.TemporalType; 9 | 10 | import lombok.Data; 11 | 12 | @Entity 13 | @Data 14 | public class RssList { 15 | 16 | @Id 17 | private String name; 18 | 19 | private String url; 20 | 21 | private Boolean useDb = true; 22 | 23 | private Boolean show = true; 24 | 25 | private Boolean tvSeries = true; 26 | 27 | private String linkKey = "link"; 28 | 29 | private Boolean downloadAll = false; 30 | 31 | private String downloadPath; 32 | 33 | private Boolean internal = false; 34 | 35 | @Temporal(TemporalType.TIMESTAMP) 36 | private Date createDt = new Date(); 37 | 38 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/domain/SeenList.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.domain; 2 | 3 | import java.util.Date; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Entity; 7 | import javax.persistence.Id; 8 | import javax.persistence.Temporal; 9 | import javax.persistence.TemporalType; 10 | 11 | import lombok.Data; 12 | 13 | @Entity 14 | @Data 15 | public class SeenList { 16 | 17 | @Id 18 | @Column(length = 2048) 19 | private String link; 20 | 21 | private String title; 22 | 23 | private String season; 24 | 25 | private String episode; 26 | 27 | private String fileName; 28 | 29 | private String downloadPath; 30 | 31 | private String rename; 32 | 33 | private Boolean subtitle = false; 34 | 35 | private String renameStatus; 36 | 37 | private String quality; 38 | 39 | @Temporal(TemporalType.TIMESTAMP) 40 | private Date createDt = new Date(); 41 | 42 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/domain/Setting.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.domain; 2 | 3 | import java.util.Date; 4 | 5 | import javax.persistence.Entity; 6 | import javax.persistence.Id; 7 | import javax.persistence.Temporal; 8 | import javax.persistence.TemporalType; 9 | 10 | import lombok.Data; 11 | 12 | @Entity 13 | @Data 14 | public class Setting { 15 | 16 | @Id 17 | private String key; 18 | 19 | private String value; 20 | 21 | private String type; 22 | 23 | private String label; 24 | 25 | private String groupLabel; 26 | 27 | private Boolean required; 28 | 29 | private Byte orderId; 30 | 31 | @Temporal(TemporalType.TIMESTAMP) 32 | private Date createDt = new Date(); 33 | 34 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/domain/User.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.domain; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.Id; 7 | 8 | import lombok.Data; 9 | 10 | @Entity 11 | @Data 12 | public class User { 13 | @Id 14 | @GeneratedValue 15 | private Long id; 16 | 17 | @Column(nullable = false, unique = true) 18 | private String username; 19 | 20 | private String password; 21 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/domain/WatchList.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.domain; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Date; 5 | 6 | import javax.persistence.Entity; 7 | import javax.persistence.Id; 8 | import javax.persistence.Temporal; 9 | import javax.persistence.TemporalType; 10 | 11 | import lombok.Data; 12 | 13 | @Entity 14 | @Data 15 | public class WatchList { 16 | 17 | @Id 18 | private String title; 19 | 20 | private Boolean useRegex = false; 21 | 22 | private String startSeason = "01"; 23 | 24 | private String startEpisode = "01"; 25 | 26 | private String endSeason = "999"; 27 | 28 | private String endEpisode = "999"; 29 | 30 | private String quality = "720p+"; 31 | 32 | private String releaseGroup; 33 | 34 | private String downloadPath; 35 | 36 | private String rename; 37 | 38 | private Boolean use = true; 39 | 40 | private Boolean subtitle = false; 41 | 42 | private Boolean series = true; 43 | 44 | private ArrayList rssList = new ArrayList<>(); 45 | 46 | @Temporal(TemporalType.TIMESTAMP) 47 | private Date createDt = new Date(); 48 | 49 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/repository/DownloadListRepository.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.repository; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import com.tarpha.torrssen2.domain.DownloadList; 7 | 8 | import org.springframework.data.jpa.repository.JpaRepository; 9 | import org.springframework.data.jpa.repository.Modifying; 10 | import org.springframework.data.jpa.repository.Query; 11 | import org.springframework.transaction.annotation.Transactional; 12 | 13 | public interface DownloadListRepository extends JpaRepository { 14 | 15 | public List findAllById(Long id); 16 | 17 | public List findByTask(Boolean task); 18 | 19 | public Optional findByDbid(String dbid); 20 | 21 | public Optional findFirstByUriAndDoneOrderByCreateDtDesc(String uri, Boolean done); 22 | 23 | public Optional findTopByOrderByIdDesc(); 24 | 25 | @Transactional 26 | @Modifying 27 | @Query( 28 | value = "DELETE FROM DOWNLOAD_LIST " + 29 | "WHERE create_dt < (SELECT create_dt " + 30 | " FROM DOWNLOAD_LIST " + 31 | " ORDER BY create_dt " + 32 | " LIMIT ?1, 1 )" 33 | , nativeQuery = true) 34 | public void deleteByLimitCount(int limitCount); 35 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/repository/DownloadPathRepository.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.repository; 2 | 3 | import java.util.List; 4 | 5 | import com.tarpha.torrssen2.domain.DownloadPath; 6 | 7 | import org.springframework.data.jpa.repository.JpaRepository; 8 | import org.springframework.data.jpa.repository.Query; 9 | 10 | public interface DownloadPathRepository extends JpaRepository { 11 | 12 | @Query( 13 | value = "SELECT name " + 14 | " , use_title " + 15 | " , use_season " + 16 | " , create_dt " + 17 | " , CONCAT(path " + 18 | " , CASE use_title WHEN true THEN CONCAT('/', ?1) ELSE '' END " + 19 | " , CASE use_season WHEN true THEN CONCAT('/' " + 20 | " , (SELECT value FROM SETTING WHERE key = 'SEASON_PREFIX') " + 21 | " , ?2) ELSE '' END) path " + 22 | "FROM DOWNLOAD_PATH " 23 | , nativeQuery = true) 24 | public List findByParams(String title, String season); 25 | 26 | @Query( 27 | value = "SELECT CONCAT(path " + 28 | " , CASE use_title WHEN true THEN CONCAT('/', ?2) ELSE '' END " + 29 | " , CASE use_season WHEN true THEN CONCAT('/' " + 30 | " , (SELECT value FROM SETTING WHERE key = 'SEASON_PREFIX') " + 31 | " , ?3) ELSE '' END) path " + 32 | "FROM DOWNLOAD_PATH " + 33 | "WHERE name = ?1" 34 | , nativeQuery = true) 35 | public String computedPath(String name, String title, String season); 36 | 37 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/repository/RssFeedRepository.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.repository; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import javax.transaction.Transactional; 7 | 8 | import com.tarpha.torrssen2.domain.RssFeed; 9 | 10 | import org.springframework.data.domain.Page; 11 | import org.springframework.data.domain.Pageable; 12 | import org.springframework.data.jpa.repository.JpaRepository; 13 | import org.springframework.data.jpa.repository.Modifying; 14 | import org.springframework.data.jpa.repository.Query; 15 | 16 | public interface RssFeedRepository extends JpaRepository { 17 | 18 | public Page findAll(Pageable pageable); 19 | 20 | public Page findByTitleContaining(String title, Pageable pageable); 21 | 22 | public Page findByRssSiteIn(List rssSite, Pageable pageable); 23 | 24 | public Page findByTitleContainingAndRssSiteIn(String title, List rssSite, Pageable pageable); 25 | 26 | public Optional findByLink(String link); 27 | 28 | @Transactional 29 | public void deleteByRssSite(String rssSite); 30 | 31 | @Query( 32 | value = "SELECT * " + 33 | "FROM RSS_FEED " + 34 | "WHERE REGEXP_LIKE(title, ?1, 'i') " + 35 | "LIMIT 5" 36 | , nativeQuery = true) 37 | public List testRegexTitle(String regex); 38 | 39 | @Query( 40 | value = "SELECT DISTINCT rss_site " + 41 | "FROM RSS_FEED " 42 | , nativeQuery = true) 43 | public List distinctRssSite(); 44 | 45 | @Transactional 46 | @Modifying 47 | @Query( 48 | value = "DELETE FROM RSS_FEED " + 49 | "WHERE create_dt < (SELECT create_dt " + 50 | " FROM RSS_FEED " + 51 | " ORDER BY create_dt " + 52 | " LIMIT ?1, 1 )" 53 | , nativeQuery = true) 54 | public void deleteByLimitCount(int limitCount); 55 | 56 | @Transactional 57 | @Modifying 58 | @Query( 59 | value = "UPDATE RSS_FEED " + 60 | "SET RSS_SITE = ?2 " + 61 | "WHERE RSS_SITE = ?1 " 62 | , nativeQuery = true) 63 | public void updateByRssSite(String oriRssSite, String trgRssSite); 64 | 65 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/repository/RssListRepository.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.repository; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import com.tarpha.torrssen2.domain.RssList; 7 | 8 | import org.springframework.data.jpa.repository.JpaRepository; 9 | 10 | public interface RssListRepository extends JpaRepository { 11 | 12 | public Optional findByName(String name); 13 | 14 | public List findByUseDb(boolean useDb); 15 | 16 | public List findByUseDbAndInternal(boolean useDb, boolean internal); 17 | 18 | public List findByShow(boolean show); 19 | 20 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/repository/SeenListRepository.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.repository; 2 | 3 | import java.util.Optional; 4 | 5 | import com.tarpha.torrssen2.domain.SeenList; 6 | 7 | import org.springframework.data.jpa.repository.JpaRepository; 8 | import org.springframework.data.jpa.repository.Modifying; 9 | import org.springframework.data.jpa.repository.Query; 10 | import org.springframework.transaction.annotation.Transactional; 11 | 12 | public interface SeenListRepository extends JpaRepository { 13 | 14 | @Query( 15 | value = "SELECT count(1) cnt " + 16 | "FROM SEEN_LIST " + 17 | "WHERE link = ?1 " + 18 | "AND subtitle = ?5 " + 19 | "OR (title = ?2 AND " + 20 | " season = ?3 AND " + 21 | " episode = ?4) " 22 | , nativeQuery = true) 23 | public int countByParams(String link, String title, String season, String episode, boolean subtitle); 24 | 25 | @Query( 26 | value = "SELECT count(1) cnt " + 27 | "FROM SEEN_LIST " + 28 | "WHERE link = ?1 " + 29 | "AND subtitle = ?5 " + 30 | "OR (title = ?2 AND " + 31 | " season = ?3 AND " + 32 | " episode = ?4 AND" + 33 | " quality = ?6)" 34 | , nativeQuery = true) 35 | public int countByParams(String link, String title, String season, String episode, boolean subtitle, String quality); 36 | 37 | public Optional findFirstByLinkAndSubtitle(String link, boolean subtitle); 38 | 39 | public Optional findFirstByLink(String link); 40 | 41 | public int deleteByTitle(String title); 42 | 43 | @Transactional 44 | @Modifying 45 | @Query( 46 | value = "DELETE FROM SEEN_LIST A " + 47 | "WHERE NOT EXISTS (SELECT 1 " + 48 | " FROM WATCH_LIST B " + 49 | " WHERE A.title = B.title) " 50 | , nativeQuery = true) 51 | public void adjustList(); 52 | 53 | // public Optional findFirstByTitleAndSeasonAndEpisodeAndQuality(String title, String season, String episode, String quality); 54 | 55 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/repository/SettingRepository.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.repository; 2 | 3 | import java.util.Optional; 4 | 5 | import com.tarpha.torrssen2.domain.Setting; 6 | 7 | import org.springframework.data.jpa.repository.JpaRepository; 8 | 9 | public interface SettingRepository extends JpaRepository { 10 | 11 | public Optional findByKey(String key); 12 | 13 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.repository; 2 | 3 | import com.tarpha.torrssen2.domain.User; 4 | 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | public interface UserRepository extends JpaRepository { 8 | public User findByUsername(String username); 9 | public User findFirstByUsernameNot (String username); 10 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/repository/WatchListRepository.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.repository; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import com.tarpha.torrssen2.domain.WatchList; 7 | 8 | import org.springframework.data.domain.Sort; 9 | import org.springframework.data.jpa.repository.JpaRepository; 10 | import org.springframework.data.jpa.repository.Query; 11 | 12 | public interface WatchListRepository extends JpaRepository { 13 | 14 | public List findByUse(boolean use); 15 | 16 | @Query(value = "SELECT w.* FROM WATCH_LIST w " + "WHERE w.use = true " + "AND ((w.use_regex = false AND " 17 | + " REPLACE(UPPER(?1), ' ', '') LIKE " 18 | + " REPLACE(UPPER(CONCAT('%', w.title, '%', IFNULL(w.release_group, ''), '%')), ' ', '')) OR " 19 | + " (w.use_regex = true AND REGEXP_LIKE(?1, w.title, 'i')) AND REPLACE(UPPER(?1), ' ', '') LIKE " 20 | + " REPLACE(UPPER(CONCAT('%', IFNULL(w.release_group, ''), '%')), ' ', '')) " 21 | + " LIMIT 1" 22 | // + "AND ((IFNULL(w.quality, '100P+') LIKE '%+' AND " 23 | // + " CAST(REPLACE(UPPER(IFNULL(?2, '100')), 'P', '') AS INTEGER) " 24 | // + " >= CAST(REPLACE(UPPER(IFNULL(w.quality, '100P+')), 'P+', '' ) AS 25 | // INTEGER)) OR " 26 | // + " (UPPER(w.quality) LIKE '%P' AND " 27 | // + " UPPER(IFNULL(?2, '')) = UPPER(w.quality))) " 28 | , nativeQuery = true) 29 | public Optional findByTitleRegex(String title, String quality); 30 | 31 | public List findByTitleContaining(String title, Sort sort); 32 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/service/CryptoService.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.service; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.security.GeneralSecurityException; 5 | import java.security.Key; 6 | import java.security.NoSuchAlgorithmException; 7 | 8 | import javax.annotation.PostConstruct; 9 | import javax.crypto.Cipher; 10 | import javax.crypto.spec.IvParameterSpec; 11 | import javax.crypto.spec.SecretKeySpec; 12 | 13 | import org.apache.commons.codec.binary.Base64; 14 | import org.springframework.beans.factory.annotation.Value; 15 | import org.springframework.stereotype.Service; 16 | 17 | /** 18 | * https://happy-hs.tistory.com/15 19 | */ 20 | @Service 21 | public class CryptoService { 22 | 23 | private String iv; 24 | private Key keySpec; 25 | 26 | /** 27 | * 16자리의 키값을 입력하여 객체를 생성한다. 28 | * 29 | * @param key 30 | * 암/복호화를 위한 키값 31 | * @throws UnsupportedEncodingException 32 | * 키값의 길이가 16이하일 경우 발생 33 | */ 34 | @Value("${crypto.key}") 35 | private String key; 36 | 37 | @PostConstruct 38 | public void initialize() throws UnsupportedEncodingException { 39 | this.iv = key.substring(0, 16); 40 | byte[] keyBytes = new byte[16]; 41 | byte[] b = key.getBytes("UTF-8"); 42 | int len = b.length; 43 | if (len > keyBytes.length) { 44 | len = keyBytes.length; 45 | } 46 | System.arraycopy(b, 0, keyBytes, 0, len); 47 | SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); 48 | 49 | this.keySpec = keySpec; 50 | } 51 | 52 | /** 53 | * AES256 으로 암호화 한다. 54 | * 55 | * @param str 56 | * 암호화할 문자열 57 | * @return 58 | * @throws NoSuchAlgorithmException 59 | * @throws GeneralSecurityException 60 | * @throws UnsupportedEncodingException 61 | */ 62 | public String encrypt(String str) throws NoSuchAlgorithmException, 63 | GeneralSecurityException, UnsupportedEncodingException { 64 | Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding"); 65 | c.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(iv.getBytes())); 66 | byte[] encrypted = c.doFinal(str.getBytes("UTF-8")); 67 | String enStr = new String(Base64.encodeBase64(encrypted)); 68 | return enStr; 69 | } 70 | 71 | /** 72 | * AES256으로 암호화된 txt 를 복호화한다. 73 | * 74 | * @param str 75 | * 복호화할 문자열 76 | * @return 77 | * @throws NoSuchAlgorithmException 78 | * @throws GeneralSecurityException 79 | * @throws UnsupportedEncodingException 80 | */ 81 | public String decrypt(String str) throws NoSuchAlgorithmException, 82 | GeneralSecurityException, UnsupportedEncodingException { 83 | Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding"); 84 | c.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv.getBytes())); 85 | byte[] byteStr = Base64.decodeBase64(str.getBytes()); 86 | return new String(c.doFinal(byteStr), "UTF-8"); 87 | } 88 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/service/DaumMovieTvService.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.service; 2 | 3 | import java.io.IOException; 4 | // import java.net.URISyntaxException; 5 | import java.net.URLEncoder; 6 | 7 | // import org.apache.commons.lang3.StringUtils; 8 | // import org.apache.http.ParseException; 9 | // import org.apache.http.client.methods.CloseableHttpResponse; 10 | // import org.apache.http.client.methods.HttpGet; 11 | // import org.apache.http.client.utils.HttpClientUtils; 12 | // import org.apache.http.client.utils.URIBuilder; 13 | // import org.apache.http.impl.client.CloseableHttpClient; 14 | // import org.apache.http.impl.client.HttpClientBuilder; 15 | // import org.apache.http.util.EntityUtils; 16 | // import org.json.JSONArray; 17 | // import org.json.JSONException; 18 | // import org.json.JSONObject; 19 | import org.jsoup.Jsoup; 20 | import org.jsoup.nodes.Document; 21 | import org.jsoup.select.Elements; 22 | import org.springframework.beans.factory.annotation.Value; 23 | import org.springframework.stereotype.Service; 24 | 25 | import lombok.extern.slf4j.Slf4j; 26 | 27 | @Service 28 | @Slf4j 29 | public class DaumMovieTvService { 30 | // https://search.daum.net/search?w=tot&rtmaxcoll=TVP&q= 31 | @Value("${daum-movie-tv.search-url}") 32 | private String baseUrl; 33 | 34 | // @Value("${daum-movie-tv.limit}") 35 | // private int limit; 36 | 37 | // private CloseableHttpClient httpClient = HttpClientBuilder.create().build(); 38 | 39 | public String getPoster(String query) { 40 | 41 | try { 42 | log.debug("getPoster Start / {}", String.format(baseUrl, URLEncoder.encode(query, "UTF-8"))); 43 | 44 | Document doc = Jsoup.connect(String.format(baseUrl, URLEncoder.encode(query, "UTF-8"))).execute().parse(); 45 | 46 | //tvp = html.xpath('//div[@id="tvpColl"]')[0] 47 | //poster_url = urllib.unquote(Regex('fname=(.*)').search(html.xpath('//div[@class="info_cont"]/div[@class="wrap_thumb"]/a/img/@src')[0]).group(1)) 48 | Elements els = doc.select("div.info_cont div.wrap_thumb a img"); 49 | 50 | log.debug("els / {}", els.first().absUrl("src")); 51 | 52 | return els.first().absUrl("src"); 53 | } catch (IOException | NullPointerException e) { 54 | log.error("getPoster Error : {}", e.toString()); 55 | } 56 | 57 | // log.debug("Get Poster: " + query); 58 | // CloseableHttpResponse response = null; 59 | 60 | // try { 61 | // URIBuilder builder = new URIBuilder(this.baseUrl); 62 | // builder.setParameter("q", query); 63 | 64 | // HttpGet httpGet = new HttpGet(builder.build()); 65 | // response = httpClient.execute(httpGet); 66 | 67 | // JSONObject json = new JSONObject(EntityUtils.toString(response.getEntity())); 68 | // // log.debug(json.toString()); 69 | 70 | // if(json.has("items")) { 71 | // JSONArray jarr = json.getJSONArray("items"); 72 | // for(int i = 0; i < jarr.length(); i++) { 73 | // String[] arr = StringUtils.split(jarr.getString(i), "|"); 74 | 75 | // if(arr.length > 2) { 76 | // if(StringUtils.containsIgnoreCase(arr[0], query)) { 77 | // return arr[2]; 78 | // } 79 | // } 80 | // } 81 | // } 82 | // } catch (URISyntaxException | IOException | ParseException | JSONException e) { 83 | // log.error(e.getMessage()); 84 | // } finally { 85 | // HttpClientUtils.closeQuietly(response); 86 | // } 87 | 88 | return null; 89 | } 90 | 91 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/service/DownloadService.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.service; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Optional; 6 | 7 | import com.tarpha.torrssen2.domain.DownloadList; 8 | import com.tarpha.torrssen2.domain.WatchList; 9 | import com.tarpha.torrssen2.repository.DownloadListRepository; 10 | import com.tarpha.torrssen2.repository.WatchListRepository; 11 | 12 | import org.apache.commons.io.FilenameUtils; 13 | import org.apache.commons.lang3.StringUtils; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.stereotype.Service; 16 | 17 | import lombok.extern.slf4j.Slf4j; 18 | 19 | @Service 20 | @Slf4j 21 | public class DownloadService { 22 | @Autowired 23 | private DownloadListRepository downloadListRepository; 24 | 25 | @Autowired 26 | private WatchListRepository watchListRepository; 27 | 28 | @Autowired 29 | private SettingService settingService; 30 | 31 | @Autowired 32 | private DownloadStationService downloadStationService; 33 | 34 | @Autowired 35 | private FileStationService fileStationService; 36 | 37 | @Autowired 38 | private TransmissionService transmissionService; 39 | 40 | @Autowired 41 | private HttpDownloadService httpDownloadService; 42 | 43 | public DownloadList getInfo(long id) { 44 | String app = settingService.getDownloadApp(); 45 | if(StringUtils.equals(app, "DOWNLOAD_STATION")) { 46 | Optional down = downloadListRepository.findById(id); 47 | if(down.isPresent()) { 48 | return downloadStationService.getInfo(down.get().getDbid()); 49 | } 50 | } else if(StringUtils.equals(app, "TRANSMISSION")) { 51 | List ids = new ArrayList(); 52 | ids.add(id); 53 | List list = transmissionService.torrentGet(ids); 54 | if(list.size() > 0) { 55 | return list.get(0); 56 | } else { 57 | return httpDownloadService.getInfo(id); 58 | } 59 | } 60 | 61 | return null; 62 | } 63 | 64 | public List list() { 65 | List ret = null; 66 | log.debug("downloadList"); 67 | 68 | String app = settingService.getDownloadApp(); 69 | if (StringUtils.equals(app, "DOWNLOAD_STATION")) { 70 | ret = downloadStationService.list(); 71 | } else if (StringUtils.equals(app, "TRANSMISSION")) { 72 | ret = transmissionService.torrentGet(null); 73 | } 74 | 75 | return ret; 76 | } 77 | 78 | public long create(DownloadList download) { 79 | long ret = 0L; 80 | 81 | String app = settingService.getDownloadApp(); 82 | if(StringUtils.equals(app, "DOWNLOAD_STATION")) { 83 | String[] paths = StringUtils.split(download.getDownloadPath(), "/"); 84 | 85 | if(paths.length > 1) { 86 | StringBuffer path = new StringBuffer(); 87 | String name = null; 88 | for(int i = 0; i < paths.length; i++) { 89 | if(i < paths.length -1) { 90 | path.append("/" + paths[i]); 91 | } else { 92 | name = paths[i]; 93 | } 94 | } 95 | fileStationService.createFolder(path.toString(), name); 96 | } 97 | if(downloadStationService.create(download.getUri(), download.getDownloadPath())) { 98 | for(DownloadList down: downloadStationService.list()) { 99 | if(StringUtils.equals(download.getUri(), down.getUri())) { 100 | ret = down.getId(); 101 | download.setDbid(down.getDbid()); 102 | } 103 | } 104 | } 105 | } else if(StringUtils.equals(app, "TRANSMISSION")) { 106 | if (StringUtils.startsWith(download.getUri(), "magnet") 107 | || StringUtils.equalsIgnoreCase(FilenameUtils.getExtension(download.getUri()), "torrent")) { 108 | ret = (long)transmissionService.torrentAdd(download.getUri(), download.getDownloadPath()); 109 | } else { 110 | Optional optionalSeq = downloadListRepository.findTopByOrderByIdDesc(); 111 | if (optionalSeq.isPresent()) { 112 | Long id = optionalSeq.get().getId() + 100L; 113 | log.debug("id: " + id); 114 | ret = id; 115 | } else { 116 | ret = 100L; 117 | } 118 | download.setId(ret); 119 | httpDownloadService.createTransmission(download); 120 | } 121 | 122 | } 123 | 124 | if(ret > 0L) { 125 | download.setId(ret); 126 | downloadListRepository.save(download); 127 | } 128 | 129 | if(download.getAuto()) { 130 | if(StringUtils.isBlank(download.getRssTitle())) { 131 | return -999L; 132 | } 133 | WatchList watchList = new WatchList(); 134 | watchList.setTitle(download.getRssTitle()); 135 | watchList.setDownloadPath(download.getDownloadPath()); 136 | if(!StringUtils.equals(download.getRssReleaseGroup(), "OTHERS")) { 137 | watchList.setReleaseGroup(download.getRssReleaseGroup()); 138 | } 139 | 140 | watchListRepository.save(watchList); 141 | } 142 | 143 | return ret; 144 | } 145 | 146 | public int remove(DownloadList download) { 147 | int ret = -1; 148 | boolean res = false; 149 | 150 | String app = settingService.getDownloadApp(); 151 | if(StringUtils.equals(app, "DOWNLOAD_STATION")) { 152 | List ids = new ArrayList<>(); 153 | ids.add(downloadStationService.getDbId(download.getId())); 154 | res = downloadStationService.delete(ids); 155 | } else if(StringUtils.equals(app, "TRANSMISSION")) { 156 | List ids = new ArrayList<>(); 157 | ids.add(download.getId()); 158 | res = transmissionService.torrentRemove(ids); 159 | } 160 | 161 | if(res) { 162 | Optional down = downloadListRepository.findById(download.getId()); 163 | if(down.isPresent()) { 164 | try { 165 | DownloadList temp = down.get(); 166 | temp.setCancel(true); 167 | downloadListRepository.save(temp); 168 | ret = temp.getVueItemIndex(); 169 | } catch (NullPointerException e) { 170 | log.error(e.getMessage()); 171 | } 172 | } else { 173 | ret = -2; 174 | } 175 | } 176 | 177 | return ret; 178 | } 179 | 180 | public boolean connTest(String app, String host, String port, String id, String pwd) { 181 | if(StringUtils.equals(app, "dsTest")) { 182 | return downloadStationService.test(host, port, id, pwd); 183 | } else if(StringUtils.equals(app, "transmissionTest")) { 184 | return transmissionService.test(host, port, id, pwd); 185 | } 186 | return false; 187 | } 188 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/service/DownloadStationService.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.service; 2 | 3 | import java.net.URISyntaxException; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.Optional; 7 | 8 | import com.tarpha.torrssen2.domain.DownloadList; 9 | import com.tarpha.torrssen2.repository.DownloadListRepository; 10 | import com.tarpha.torrssen2.util.SynologyApiUtils; 11 | 12 | import org.apache.commons.lang3.StringUtils; 13 | import org.apache.http.NameValuePair; 14 | import org.apache.http.ParseException; 15 | import org.apache.http.client.utils.URIBuilder; 16 | import org.apache.http.message.BasicNameValuePair; 17 | import org.json.JSONArray; 18 | import org.json.JSONException; 19 | import org.json.JSONObject; 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.stereotype.Service; 22 | 23 | import lombok.extern.slf4j.Slf4j; 24 | 25 | @Service 26 | @Slf4j 27 | public class DownloadStationService extends SynologyApiUtils { 28 | 29 | @Autowired 30 | private DownloadListRepository downloadListRepository; 31 | 32 | private DownloadList setInfo(JSONObject json) throws JSONException { 33 | DownloadList down = new DownloadList(); 34 | 35 | if(json.has("id")) { 36 | boolean done = StringUtils.equalsAny(json.getString("status"), "finished", "seeding"); 37 | 38 | Long id = Long.parseLong(StringUtils.remove(json.getString("id"), "dbid_")); 39 | 40 | down.setId(id); 41 | down.setDbid(json.getString("id")); 42 | down.setDone(done); 43 | down.setName(json.getString("title")); 44 | down.setFileName(json.getString("title")); 45 | down.setUri(json.getJSONObject("additional").getJSONObject("detail").getString("uri")); 46 | down.setDownloadPath(json.getJSONObject("additional").getJSONObject("detail").getString("destination")); 47 | 48 | Optional info = downloadListRepository.findById(id); 49 | if(info.isPresent()) { 50 | down.setVueItemIndex(info.get().getVueItemIndex()); 51 | } 52 | 53 | try { 54 | Long sizeDownloaded = json.getJSONObject("additional").getJSONObject("transfer").getLong("size_downloaded"); 55 | Long fileSize = json.getLong("size"); 56 | log.debug("percent-done: " + sizeDownloaded.toString() + " / " + fileSize.toString() + " = " + String.valueOf(((double)sizeDownloaded / (double)fileSize) * 100)); 57 | if(fileSize > 0L) { 58 | down.setPercentDone((int)(((double)sizeDownloaded / (double)fileSize) * 100)); 59 | } 60 | } catch (ArithmeticException ae) { 61 | log.error(ae.getMessage()); 62 | } 63 | } 64 | 65 | return down; 66 | } 67 | 68 | public List list() { 69 | log.debug("Download Station list"); 70 | List ret = new ArrayList(); 71 | 72 | try { 73 | URIBuilder builder = new URIBuilder(baseUrl + "/DownloadStation/task.cgi"); 74 | builder.setParameter("api", "SYNO.DownloadStation.Task").setParameter("version", "3") 75 | .setParameter("method", "list").setParameter("additional", "detail,transfer") 76 | .setParameter("_sid", this.sid); 77 | 78 | JSONObject resJson = executeGet(builder); 79 | 80 | if(resJson != null) { 81 | if(resJson.has("success")) { 82 | if(Boolean.parseBoolean(resJson.get("success").toString())) { 83 | if(resJson.has("data")) { 84 | if(resJson.getJSONObject("data").has("tasks")) { 85 | JSONArray jsonArray = resJson.getJSONObject("data").getJSONArray("tasks"); 86 | 87 | for(int i = 0; i < jsonArray.length(); i ++) { 88 | DownloadList down = setInfo(jsonArray.getJSONObject(i)); 89 | ret.add(down); 90 | } 91 | } 92 | } 93 | } 94 | } 95 | } 96 | 97 | } catch (URISyntaxException | ParseException | JSONException e) { 98 | log.error(e.getMessage()); 99 | } 100 | 101 | return ret; 102 | } 103 | 104 | public DownloadList getInfo(String id) { 105 | log.debug("Download Station getInfo"); 106 | DownloadList ret = null; 107 | 108 | try { 109 | URIBuilder builder = new URIBuilder(baseUrl + "/DownloadStation/task.cgi"); 110 | builder.setParameter("api", "SYNO.DownloadStation.Task").setParameter("version", "3") 111 | .setParameter("method", "getinfo").setParameter("additional", "detail,transfer") 112 | .setParameter("id", id).setParameter("_sid", this.sid); 113 | 114 | JSONObject resJson = executeGet(builder); 115 | 116 | if(resJson != null) { 117 | if(resJson.has("success")) { 118 | if(Boolean.parseBoolean(resJson.get("success").toString())) { 119 | JSONArray jsonArray = resJson.getJSONObject("data").getJSONArray("tasks"); 120 | 121 | ret = setInfo(jsonArray.getJSONObject(0)); 122 | } 123 | } 124 | } 125 | 126 | } catch (URISyntaxException | ParseException | JSONException e) { 127 | log.error(e.getMessage()); 128 | } 129 | 130 | return ret; 131 | } 132 | 133 | public List getInfo(List ids) { 134 | log.debug ("Download Station getInfo"); 135 | List ret = new ArrayList(); 136 | 137 | try { 138 | URIBuilder builder = new URIBuilder(baseUrl + "/DownloadStation/task.cgi"); 139 | builder.setParameter("api", "SYNO.DownloadStation.Task").setParameter("version", "3") 140 | .setParameter("method", "getinfo").setParameter("additional", "detail,transfer") 141 | .setParameter("id", StringUtils.join(ids, ",")).setParameter("_sid", this.sid); 142 | 143 | JSONObject resJson = executeGet(builder); 144 | 145 | if(resJson != null) { 146 | if(Boolean.parseBoolean(resJson.get("success").toString())) { 147 | JSONArray jsonArray = resJson.getJSONObject("data").getJSONArray("tasks"); 148 | 149 | for(int i = 0; i < jsonArray.length(); i++) { 150 | ret.add(setInfo(jsonArray.getJSONObject(i))); 151 | } 152 | } 153 | } 154 | 155 | } catch (URISyntaxException | ParseException | JSONException e) { 156 | log.error(e.getMessage()); 157 | } 158 | 159 | return ret; 160 | } 161 | 162 | public boolean create(String uri, String downloadDir) { 163 | log.debug("Download Station create"); 164 | boolean ret = false; 165 | 166 | List form = new ArrayList(); 167 | form.add(new BasicNameValuePair("api", "SYNO.DownloadStation.Task")); 168 | form.add(new BasicNameValuePair("version", "3")); 169 | form.add(new BasicNameValuePair("method", "create")); 170 | form.add(new BasicNameValuePair("uri", uri)); 171 | if(!StringUtils.isBlank(downloadDir)) { 172 | if(StringUtils.startsWith(downloadDir, "/")) { 173 | form.add(new BasicNameValuePair("destination", StringUtils.substring(downloadDir, 1))); 174 | } else { 175 | form.add(new BasicNameValuePair("destination", downloadDir)); 176 | } 177 | } 178 | 179 | JSONObject resJson = executePost(form); 180 | 181 | if(resJson != null) { 182 | try { 183 | if(Boolean.parseBoolean(resJson.get("success").toString())) { 184 | ret = true; 185 | } 186 | } catch (JSONException e) { 187 | log.error(e.getMessage()); 188 | } 189 | } 190 | 191 | return ret; 192 | } 193 | 194 | public boolean delete(List ids) { 195 | log.debug("Download Station delete"); 196 | boolean ret = true; 197 | 198 | try { 199 | URIBuilder builder = new URIBuilder(baseUrl + "/DownloadStation/task.cgi"); 200 | builder.setParameter("api", "SYNO.DownloadStation.Task").setParameter("version", "3") 201 | .setParameter("method", "delete").setParameter("force_complete", "false") 202 | .setParameter("id", StringUtils.join(ids, ",")).setParameter("_sid", this.sid); 203 | 204 | JSONObject resJson = executeGet(builder); 205 | 206 | if(resJson != null) { 207 | if(resJson.has("data")) { 208 | JSONArray jarray = resJson.getJSONArray("data"); 209 | for(int i = 0; i 0) { 212 | ret = false; 213 | break; 214 | } 215 | } 216 | } 217 | } 218 | } 219 | 220 | } catch (URISyntaxException | ParseException | JSONException e) { 221 | log.error(e.getMessage()); 222 | } 223 | 224 | return ret; 225 | } 226 | 227 | public String getDbId(Long id) { 228 | String ret = null; 229 | 230 | Optional info = downloadListRepository.findById(id); 231 | if(info.isPresent()) { 232 | ret = info.get().getDbid(); 233 | } 234 | 235 | return ret; 236 | } 237 | 238 | } 239 | -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/service/HttpDownloadService.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.service; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.BufferedOutputStream; 5 | import java.io.File; 6 | import java.io.FileOutputStream; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | import com.tarpha.torrssen2.domain.DownloadList; 11 | import com.tarpha.torrssen2.repository.DownloadListRepository; 12 | 13 | import org.apache.commons.io.FileUtils; 14 | import org.apache.commons.lang3.StringUtils; 15 | import org.apache.http.Header; 16 | import org.apache.http.client.methods.CloseableHttpResponse; 17 | import org.apache.http.client.methods.HttpGet; 18 | import org.apache.http.client.utils.URIBuilder; 19 | import org.apache.http.impl.client.CloseableHttpClient; 20 | import org.apache.http.impl.client.HttpClientBuilder; 21 | import org.springframework.beans.factory.annotation.Autowired; 22 | import org.springframework.messaging.simp.SimpMessagingTemplate; 23 | import org.springframework.scheduling.annotation.Async; 24 | import org.springframework.stereotype.Service; 25 | 26 | import lombok.Data; 27 | import lombok.extern.slf4j.Slf4j; 28 | 29 | @Service 30 | @Slf4j 31 | public class HttpDownloadService { 32 | @Autowired 33 | private TransmissionService transmissionService; 34 | 35 | @Autowired 36 | private DownloadListRepository downloadListRepository; 37 | 38 | @Autowired 39 | private SimpMessagingTemplate simpMessagingTemplate; 40 | 41 | private Map jobs = new HashMap<>(); 42 | 43 | @Data 44 | private class HttpVo { 45 | private Long id; 46 | private String name; 47 | private String filename; 48 | private String path; 49 | private int percentDone = 0; 50 | private Boolean done = false; 51 | } 52 | 53 | @Async 54 | public void createTransmission(DownloadList download) { 55 | String link = download.getUri(); 56 | String path = download.getDownloadPath(); 57 | 58 | long currentId = download.getId(); 59 | 60 | try { 61 | CloseableHttpClient httpClient = HttpClientBuilder.create().build(); 62 | URIBuilder builder = new URIBuilder(link); 63 | HttpGet httpGet = new HttpGet(builder.build()); 64 | CloseableHttpResponse response = httpClient.execute(httpGet); 65 | 66 | Header[] header = response.getHeaders("Content-Disposition"); 67 | String content = header[0].getValue(); 68 | for(String str: StringUtils.split(content, ";")) { 69 | if(StringUtils.containsIgnoreCase(str, "filename=")) { 70 | log.debug(str); 71 | String[] attachment = StringUtils.split(str, "="); 72 | File directory = new File(path); 73 | 74 | if(!directory.isDirectory()) { 75 | FileUtils.forceMkdir(directory); 76 | } 77 | 78 | String filename = StringUtils.remove(attachment[1], "\""); 79 | 80 | HttpVo vo = new HttpVo(); 81 | vo.setId(currentId); 82 | vo.setName(download.getName()); 83 | vo.setFilename(filename); 84 | vo.setPath(download.getDownloadPath()); 85 | 86 | jobs.put(currentId, vo); 87 | 88 | download.setFileName(filename); 89 | download.setDbid("http_" + vo.getId()); 90 | downloadListRepository.save(download); 91 | 92 | BufferedInputStream bis = new BufferedInputStream(response.getEntity().getContent()); 93 | BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(path, filename))); 94 | 95 | int inByte; 96 | while((inByte = bis.read()) != -1) bos.write(inByte); 97 | bis.close(); 98 | bos.close(); 99 | 100 | vo.setDone(true); 101 | vo.setPercentDone(100); 102 | 103 | jobs.put(currentId, vo); 104 | 105 | downloadListRepository.save(download); 106 | 107 | // if(StringUtils.equalsIgnoreCase(FilenameUtils.getExtension(attachment[1]), "torrent")) { 108 | if(StringUtils.containsIgnoreCase(filename, ".torrent")) { 109 | long ret = transmissionService.torrentAdd(path + File.separator + filename, path); 110 | if(ret > 0L) { 111 | download.setId(ret); 112 | downloadListRepository.save(download); 113 | simpMessagingTemplate.convertAndSend("/topic/feed/download", download); 114 | } 115 | } 116 | } 117 | } 118 | response.close(); 119 | httpClient.close(); 120 | } catch (Exception e) { 121 | log.error(e.getMessage()); 122 | } 123 | } 124 | 125 | public DownloadList getInfo(Long id) { 126 | if(jobs.containsKey(id)) { 127 | DownloadList download = new DownloadList(); 128 | HttpVo vo = jobs.get(id); 129 | download.setId(vo.getId()); 130 | download.setDbid("http_" + vo.getId()); 131 | download.setPercentDone(vo.getPercentDone()); 132 | download.setDone(vo.getDone()); 133 | download.setDownloadPath(vo.getPath()); 134 | download.setFileName(vo.getFilename()); 135 | download.setName(vo.getName()); 136 | 137 | if(vo.getDone()) { 138 | jobs.remove(vo.getId()); 139 | } 140 | 141 | return download; 142 | } 143 | 144 | return null; 145 | } 146 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/service/MyUserDetailsService.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.service; 2 | 3 | import com.tarpha.torrssen2.auth.MyUserPrincipal; 4 | import com.tarpha.torrssen2.domain.User; 5 | import com.tarpha.torrssen2.repository.UserRepository; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | import org.springframework.security.core.userdetails.UserDetailsService; 10 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 11 | import org.springframework.stereotype.Service; 12 | 13 | @Service 14 | public class MyUserDetailsService implements UserDetailsService { 15 | @Autowired 16 | private UserRepository userRepository; 17 | 18 | @Override 19 | public UserDetails loadUserByUsername(String username) { 20 | User user = userRepository.findByUsername(username); 21 | if (user == null) { 22 | throw new UsernameNotFoundException(username); 23 | } 24 | return new MyUserPrincipal(user); 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/service/SettingService.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.service; 2 | 3 | import java.util.Optional; 4 | 5 | import com.tarpha.torrssen2.domain.Setting; 6 | import com.tarpha.torrssen2.repository.SettingRepository; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | 11 | @Service 12 | public class SettingService { 13 | 14 | @Autowired 15 | private SettingRepository settingRepository; 16 | 17 | public String getDownloadApp() { 18 | Optional optionalSetting = settingRepository.findByKey("DOWNLOAD_APP"); 19 | if (optionalSetting.isPresent()) { 20 | return optionalSetting.get().getValue(); 21 | } 22 | 23 | return null; 24 | } 25 | 26 | public String getSettingValue(String key) { 27 | Optional setting = settingRepository.findByKey(key); 28 | if(setting.isPresent()) { 29 | return setting.get().getValue(); 30 | } 31 | return null; 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/service/TelegramService.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.service; 2 | 3 | import java.io.IOException; 4 | import java.nio.charset.StandardCharsets; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | import org.apache.commons.lang3.StringUtils; 9 | import org.apache.http.Header; 10 | import org.apache.http.HttpHeaders; 11 | import org.apache.http.client.methods.CloseableHttpResponse; 12 | import org.apache.http.client.methods.HttpPost; 13 | import org.apache.http.client.utils.HttpClientUtils; 14 | import org.apache.http.entity.StringEntity; 15 | import org.apache.http.impl.client.CloseableHttpClient; 16 | import org.apache.http.impl.client.HttpClientBuilder; 17 | import org.apache.http.message.BasicHeader; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.stereotype.Service; 20 | 21 | import lombok.extern.slf4j.Slf4j; 22 | import net.minidev.json.JSONObject; 23 | 24 | @Service 25 | @Slf4j 26 | public class TelegramService { 27 | @Autowired 28 | private SettingService settingService; 29 | 30 | // @Value("${telegram.token}") 31 | private String token; 32 | 33 | // @Value("${telegram.chat-id}") 34 | private String[] chatIds; 35 | 36 | private String baseUrl; 37 | 38 | private CloseableHttpClient httpClient = null; 39 | 40 | private void initialize() { 41 | token = settingService.getSettingValue("TELEGRAM_TOKEN"); 42 | chatIds = StringUtils.split(settingService.getSettingValue("TELEGRAM_CHAT_ID"), ","); 43 | baseUrl = "https://api.telegram.org/bot" + token + "/sendMessage"; 44 | 45 | List
headers = new ArrayList
(); 46 | headers.add(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")); 47 | httpClient = HttpClientBuilder.create().setDefaultHeaders(headers).build(); 48 | } 49 | 50 | public boolean sendMessage(String message) { 51 | boolean ret = false; 52 | 53 | if(httpClient == null) { 54 | initialize(); 55 | } 56 | 57 | HttpPost httpPost = new HttpPost(baseUrl); 58 | CloseableHttpResponse response = null; 59 | 60 | try { 61 | for(String chatId: chatIds) { 62 | JSONObject params = new JSONObject(); 63 | params.put("chat_id", chatId); 64 | params.put("text", message); 65 | params.put("parse_mode", "HTML"); 66 | httpPost.setEntity(new StringEntity(params.toString(), StandardCharsets.UTF_8)); 67 | 68 | response = httpClient.execute(httpPost); 69 | 70 | log.debug("telegram-send-message-response-code: " + response.getStatusLine().getStatusCode()); 71 | if (response.getStatusLine().getStatusCode() == 200) { 72 | ret = true; 73 | } 74 | } 75 | } catch (IOException e) { 76 | log.error(e.getMessage()); 77 | } finally { 78 | HttpClientUtils.closeQuietly(response); 79 | } 80 | 81 | return ret; 82 | } 83 | 84 | public boolean sendMessage(String inToken, String chatId, String message) { 85 | boolean ret = false; 86 | 87 | baseUrl = "https://api.telegram.org/bot" + inToken + "/sendMessage"; 88 | 89 | List
headers = new ArrayList
(); 90 | headers.add(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")); 91 | httpClient = HttpClientBuilder.create().setDefaultHeaders(headers).build(); 92 | 93 | HttpPost httpPost = new HttpPost(baseUrl); 94 | CloseableHttpResponse response = null; 95 | 96 | log.debug("token:" + inToken); 97 | log.debug("chatId: " + chatId); 98 | 99 | try { 100 | JSONObject params = new JSONObject(); 101 | params.put("chat_id", chatId); 102 | params.put("text", message); 103 | params.put("parse_mode", "HTML"); 104 | httpPost.setEntity(new StringEntity(params.toString(), StandardCharsets.UTF_8)); 105 | 106 | response = httpClient.execute(httpPost); 107 | 108 | log.debug("telegram-send-message-response-code: " + response.getStatusLine().getStatusCode()); 109 | if (response.getStatusLine().getStatusCode() == 200) { 110 | ret = true; 111 | } 112 | } catch (IOException e) { 113 | log.error(e.getMessage()); 114 | } finally { 115 | HttpClientUtils.closeQuietly(response); 116 | } 117 | 118 | return ret; 119 | } 120 | 121 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/util/CommonUtils.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.util; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.ArrayList; 6 | import java.util.Collection; 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | import com.tarpha.torrssen2.domain.Setting; 11 | import com.tarpha.torrssen2.repository.SettingRepository; 12 | 13 | import org.apache.commons.io.FileUtils; 14 | import org.apache.commons.io.FilenameUtils; 15 | import org.apache.commons.io.filefilter.NotFileFilter; 16 | import org.apache.commons.io.filefilter.SuffixFileFilter; 17 | import org.apache.commons.io.filefilter.TrueFileFilter; 18 | import org.apache.commons.lang3.StringUtils; 19 | 20 | import lombok.extern.slf4j.Slf4j; 21 | 22 | @Slf4j 23 | public class CommonUtils { 24 | public static String getRename(String rename, String title, String season, String episode, String quality, 25 | String releaseGroup, String rssDate) { 26 | return StringUtils.replaceEach(rename, 27 | new String[] { "${TITLE}", "${SEASON}", "${EPISODE}", "${QUALITY}", "${RELEASE_GROUP}", "${DATE}" }, 28 | new String[] { title, season, episode, quality, releaseGroup, rssDate }); 29 | } 30 | 31 | public static boolean renameFile(String path, String from, String to) { 32 | log.info("Rename File To: " + to); 33 | File srcFile = new File(path + File.separator, from); 34 | File destFile = new File(path, to + "." + FilenameUtils.getExtension(from)); 35 | 36 | return srcFile.renameTo(destFile); 37 | } 38 | 39 | public static boolean removeDirectory(String path, String outer, String inner, SettingRepository settingRepository) { 40 | Optional delDirSetting = settingRepository.findByKey("DEL_DIR"); 41 | 42 | if (delDirSetting.isPresent()) { 43 | if(Boolean.parseBoolean(delDirSetting.get().getValue())) { 44 | boolean ret = true; 45 | 46 | File file = new File(path, outer); 47 | if (file.isDirectory()) { 48 | File src = new File(path + File.separator + outer, inner); 49 | File trg = new File(path); 50 | try { 51 | File remove = new File(path, inner); 52 | if (remove.isFile()) { 53 | FileUtils.forceDelete(remove); 54 | } 55 | FileUtils.moveFileToDirectory(src, trg, true); 56 | FileUtils.forceDelete(new File(path, outer)); 57 | } catch (IOException e) { 58 | log.error(e.getMessage()); 59 | ret = false; 60 | } 61 | } else { 62 | ret = false; 63 | } 64 | 65 | return ret; 66 | } 67 | } 68 | 69 | return false; 70 | } 71 | 72 | public static boolean removeDirectory(String path, String outer, List innerList, SettingRepository settingRepository) { 73 | String[] exts = {}; 74 | Optional exceptExtSetting = settingRepository.findByKey("EXCEPT_EXT"); 75 | if(exceptExtSetting.isPresent()) { 76 | exts = StringUtils.split(StringUtils.lowerCase(exceptExtSetting.get().getValue()), ","); 77 | } 78 | 79 | Optional delDirSetting = settingRepository.findByKey("DEL_DIR"); 80 | 81 | log.debug("delete List"); 82 | 83 | if (delDirSetting.isPresent()) { 84 | log.debug(delDirSetting.get().getValue()); 85 | if(Boolean.parseBoolean(delDirSetting.get().getValue())) { 86 | boolean ret = true; 87 | 88 | File file = new File(path, outer); 89 | if (file.isDirectory()) { 90 | try { 91 | for (String inner : innerList) { 92 | if(!StringUtils.containsAny(StringUtils.lowerCase(FilenameUtils.getExtension(inner)), exts)) { 93 | File src = new File(path + File.separator + outer, inner); 94 | File trg = new File(path); 95 | File remove = new File(path, inner); 96 | if (remove.isFile()) { 97 | FileUtils.forceDelete(remove); 98 | } 99 | FileUtils.moveFileToDirectory(src, trg, true); 100 | } 101 | } 102 | FileUtils.forceDelete(new File(path, outer)); 103 | } catch (IOException e) { 104 | log.error(e.getMessage()); 105 | ret = false; 106 | } 107 | } else { 108 | ret = false; 109 | } 110 | 111 | return ret; 112 | } 113 | } 114 | return false; 115 | } 116 | 117 | public static List removeDirectory(String path, String outer, SettingRepository settingRepository) { 118 | String[] exts = {}; 119 | Optional exceptExtSetting = settingRepository.findByKey("EXCEPT_EXT"); 120 | if(exceptExtSetting.isPresent()) { 121 | exts = StringUtils.split(StringUtils.lowerCase(exceptExtSetting.get().getValue()), ","); 122 | } 123 | 124 | Optional delDirSetting = settingRepository.findByKey("DEL_DIR"); 125 | 126 | if (delDirSetting.isPresent()) { 127 | if(Boolean.parseBoolean(delDirSetting.get().getValue())) { 128 | File file = new File(path, outer); 129 | if (file.isDirectory()) { 130 | Collection subFiles; 131 | if(exts != null) { 132 | NotFileFilter fileFilter = new NotFileFilter(new SuffixFileFilter(exts)); 133 | subFiles = FileUtils.listFiles(new File(path, outer), fileFilter, TrueFileFilter.INSTANCE); 134 | } else { 135 | subFiles = FileUtils.listFiles(new File(path, outer), TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE); 136 | } 137 | 138 | List ret = new ArrayList<>(); 139 | 140 | try { 141 | for(File subFile: subFiles) { 142 | log.debug(subFile.getPath() + ":" + subFile.getName()); 143 | File remove = new File(path, subFile.getName()); 144 | if (remove.isFile()) { 145 | FileUtils.forceDelete(remove); 146 | } 147 | FileUtils.moveFileToDirectory(subFile, new File(path), true); 148 | ret.add(subFile.getName()); 149 | } 150 | FileUtils.forceDelete(new File(path, outer)); 151 | } catch (IOException e) { 152 | log.error(e.getMessage()); 153 | } 154 | 155 | return ret; 156 | } 157 | } 158 | } 159 | 160 | return null; 161 | } 162 | 163 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/vo/TextValueVO.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.vo; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class TextValueVO { 7 | private String text; 8 | private String value; 9 | } -------------------------------------------------------------------------------- /src/main/java/com/tarpha/torrssen2/vo/WatchListVO.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.vo; 2 | 3 | import java.util.List; 4 | 5 | import com.tarpha.torrssen2.domain.WatchList; 6 | 7 | import lombok.Data; 8 | 9 | @Data 10 | public class WatchListVO { 11 | private WatchList watchList; 12 | private List selectList; 13 | } -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: default 4 | h2: 5 | console: 6 | enabled: true 7 | jpa: 8 | database-platform: org.hibernate.dialect.H2Dialect 9 | open-in-view: false 10 | hibernate: 11 | ddl-auto: update 12 | 13 | datasource: 14 | driver-class-name: org.h2.Driver 15 | url: jdbc:h2:~/data/h2;DB_CLOSE_ON_EXIT=FALSE;AUTO_RECONNECT=TRUE 16 | username: torrssen 17 | password: torrssen 18 | # logging: 19 | # level: 20 | # root: warn 21 | # com.tarpha: debug 22 | 23 | crypto: 24 | key: 3t6w9z$C&F)J@NcRfUjWnZr4u7x!A%D* 25 | 26 | daum-movie-tv: 27 | search-url: https://search.daum.net/search?w=tot&rtmaxcoll=TVP&q=%s 28 | # limit: 10 29 | 30 | internal-rss1: 31 | base-url: https:// 32 | page-query: page 33 | max-page: 1 34 | board-query: bo_table 35 | tv-boards: > 36 | 1, 37 | 2, 38 | 3 39 | other-boards: > 40 | 1, 41 | 2 42 | 43 | internal-rss6: 44 | base_url: https:// 45 | page-html: p 46 | max-page: 1 47 | tv-boards: > 48 | 1, 49 | 2 50 | 51 | internal-rss7: 52 | base_url: https:// 53 | page-html: page 54 | max-page: 1 55 | tv-boards: > 56 | 1, 57 | 2 58 | 59 | internal-rss8: 60 | base_url: https:// 61 | page-html: page 62 | max-page: 1 63 | tv-boards: > 64 | 1, 65 | 2, 66 | 3 67 | -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | __ ___ 2 | / /_____ _______________________ ____ |__ \ 3 | / __/ __ \/ ___/ ___/ ___/ ___/ _ \/ __ \__/ / 4 | / /_/ /_/ / / / / (__ |__ ) __/ / / / __/ 5 | \__/\____/_/ /_/ /____/____/\___/_/ /_/____/ 6 | :: Spring Boot :: ${spring-boot.formatted-version} -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | %black(%d{ISO8601}) %highlight(%-5level) [%blue(%t)] %yellow(%C{1.}): %msg%n%throwable 11 | 12 | 13 | 14 | 15 | 17 | ${LOGS}/spring-boot-logger.log 18 | 20 | %d %p %C{1.} [%t] %m%n 21 | 22 | 23 | 25 | 26 | ${LOGS}/archived/spring-boot-logger-%d{yyyy-MM-dd}.%i.log 27 | 28 | 30 | 10MB 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/test/java/com/tarpha/torrssen2/Torrssen2ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class Torrssen2ApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/com/tarpha/torrssen2/service/RssMakeServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.tarpha.torrssen2.service; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | @Slf4j 9 | public class RssMakeServiceTest { 10 | 11 | @Test 12 | public void rssMake() { 13 | RssMakeService svc = new RssMakeService(); 14 | 15 | try { 16 | svc.makeRss(); 17 | } catch (Exception e) { 18 | log.error(e.toString()); 19 | Assert.fail(); 20 | } 21 | Assert.assertTrue(true); 22 | } 23 | 24 | } --------------------------------------------------------------------------------