From 9854acd343d5dba17d6f8623d6344d9109333c62 Mon Sep 17 00:00:00 2001 From: marios Date: Fri, 3 Apr 2020 14:29:51 +0300 Subject: [PATCH] added anime scripts and new animebyter --- Animebyter/Animebyter.py | 37 ++++ Animebyter/Dockerfile | 12 +- Animebyter/Downloader.py | 115 +++++++++++++ Animebyter/Notifications.py | 49 ++++++ Animebyter/README.md | 34 ++++ Animebyter/animebyter.py | 252 ---------------------------- Animebyter/animebyter.service | 21 --- Animebyter/docker-compose.yml | 16 +- Animebyter/main.py | 95 +++++++++++ Animebyter/requirements.txt | 4 + Animebyter/static/css/style.css | 38 +++++ Animebyter/templates/error.html | 17 ++ Animebyter/templates/index.html | 71 ++++++++ Caddy_Logger/caddy_logger.py | 58 ++++--- anime_scripts/cmpl.sh | 25 +++ anime_scripts/jellyfin-namer.sh | 27 +++ anime_scripts/subtitle-converter.sh | 25 +++ 17 files changed, 589 insertions(+), 307 deletions(-) create mode 100644 Animebyter/Animebyter.py create mode 100644 Animebyter/Downloader.py create mode 100644 Animebyter/Notifications.py create mode 100644 Animebyter/README.md delete mode 100644 Animebyter/animebyter.py delete mode 100644 Animebyter/animebyter.service create mode 100644 Animebyter/main.py create mode 100644 Animebyter/requirements.txt create mode 100644 Animebyter/static/css/style.css create mode 100644 Animebyter/templates/error.html create mode 100644 Animebyter/templates/index.html create mode 100755 anime_scripts/cmpl.sh create mode 100755 anime_scripts/jellyfin-namer.sh create mode 100755 anime_scripts/subtitle-converter.sh diff --git a/Animebyter/Animebyter.py b/Animebyter/Animebyter.py new file mode 100644 index 0000000..3f03b18 --- /dev/null +++ b/Animebyter/Animebyter.py @@ -0,0 +1,37 @@ +from aiohttp import ClientSession +from feedparser import parse +from os import getenv +from json import dumps +import logging +import hashlib + +web = ClientSession() + +class Anime: + def __eq__(self,other): + return self.title == other.title + def __hash__(self): + return hash(self.title) + + def __init__(self,name,le,tl,res): + self.title = name.replace("/","-") + self.last_episode = le + self.torrent_link = tl + self.resolution = res.strip() + self.id = str(int(hashlib.sha256(self.title.encode('utf-8')).hexdigest(), 16) % 10**8) + +async def get_airing(): + r = [] + async with web.get("https://animebytes.tv/feed/rss_torrents_airing_anime/{}".format(getenv("ab_key"))) as res: + if res.status==200: + txt = await res.text() + rss = parse(txt) + for i in rss['entries']: + try: + title = i['ab_grouptitle'] + ep = int((''.join(x for x in i['ab_torrentproperty'].split("|")[6] if x.isdigit())).strip()) + link = i['link'] + r.append(Anime(title,ep,link,i['ab_torrentproperty'].split("|")[3])) + except Exception as e: + logging.error(str(e)) + return r \ No newline at end of file diff --git a/Animebyter/Dockerfile b/Animebyter/Dockerfile index a9a4e58..fe53cda 100644 --- a/Animebyter/Dockerfile +++ b/Animebyter/Dockerfile @@ -5,10 +5,14 @@ RUN apk add libffi-dev RUN apk add openssl-dev RUN apk add python3-dev +COPY requirements.txt /app/requirements.txt +RUN pip install -r /app/requirements.txt + +ENV PYTHONUNBUFFERED=0 +ENV interval 300 +EXPOSE 5000 + COPY . /app WORKDIR /app -ENV interval 300 -RUN pip install -r requirements.txt - -CMD python3 animebyter.py \ No newline at end of file +CMD python3 main.py diff --git a/Animebyter/Downloader.py b/Animebyter/Downloader.py new file mode 100644 index 0000000..06ee7c5 --- /dev/null +++ b/Animebyter/Downloader.py @@ -0,0 +1,115 @@ +from Animebyter import get_airing +from asyncio import sleep, Queue, get_event_loop +from aiohttp import ClientSession +from pickledb import PickleDB +import logging +import os + +INTERVAL = int(os.getenv("interval","5")) +web = ClientSession() +QB_URL = os.getenv("qbit_url") +store = PickleDB(os.getenv("database"), True, False) +dl_queue = Queue() +loop = get_event_loop() + +if not store.exists("watching"): + store.lcreate("watching") +if not store.exists("qbUser"): + store.set("qbUser", "") +if not store.exists("qbPass"): + store.set("qbPass", "") + +class qbLoginException(Exception): + pass + +class NotLoggedInException(Exception): + pass + +class DownloadableItem: + def __init__(self,anime): + self.anime = anime + + def complete(self): + watching = store.get("watching") + for v in watching: + if v['id'] == self.anime.id: + store.lremvalue("watching",v) + v["last_episode"] = self.anime.last_episode + store.ladd("watching",v) + return + + +async def login_qb(username=store.get("qbUser"),password=store.get("qbPass"), client=web): + async with client.post(QB_URL+'/login',data={'username':username,'password':password}) as res: + if res.status!=200: + raise qbLoginException(await res.text()) + else: + logging.info("Logged into qBittorrent") + +async def add_anime_torrent(anime): + logging.info("Adding episode {} of {}".format(anime.last_episode,anime.title)) + path = os.path.join(store.get('downloadPath'),anime.title) + async with web.post(QB_URL+'/command/download',data={'urls':anime.torrent_link,'savepath':path,'category':store.get("downloadLabel")}) as res: + if res.status==200: + return 1 + elif res.status==403: + raise NotLoggedInException() + else: + raise Exception(await res.text()) + +async def get_last_added(): + async with web.get(QB_URL+"/query/torrents",params={"category":"Anime","sort":"added_on","reverse":"true"}) as res: + if res.status==200: + res = await res.json() + return res[0] + +from Notifications import downloading +async def add_to_download_list(anime): + last = await get_last_added() + if last: + downloading[last["hash"]] = anime + +async def downloader(): + logging.info("Starting downloader") + while True: + item = await dl_queue.get() + while True: + try: + await add_anime_torrent(item.anime) + await add_to_download_list(item.anime) + item.complete() + logging.info("Added episode {} of {}".format(item.anime.last_episode,item.anime.title)) + break + except NotLoggedInException: + while True: + try: + await login_qb() + break + except Exception as e: + logging.warn("Could not log into qBittorrent ({})".format(str(e))) + await sleep(5) + continue + continue + except Exception as e: + logging.error(str(e)) + await sleep(3) + +async def checker(): + logging.info("Starting new episode checker") + while True: + try: + logging.debug("Checking for new episodes") + airing = await get_airing() + watching = store.get("watching") + for air in airing: + for watch in watching: + if air.id == watch['id'] and air.resolution == watch["resolution"]: + if air.last_episode > watch['last_episode']: + logging.debug("Attempting to add episode {} of {}".format(air.last_episode,air.title)) + item = DownloadableItem(air) + await dl_queue.put(item) + except Exception as e: + logging.error(str(e)) + continue + finally: + await sleep(INTERVAL) diff --git a/Animebyter/Notifications.py b/Animebyter/Notifications.py new file mode 100644 index 0000000..a0c9b3d --- /dev/null +++ b/Animebyter/Notifications.py @@ -0,0 +1,49 @@ +from aiohttp import ClientSession +from os import getenv +from Downloader import QB_URL, login_qb, web +from asyncio import sleep +import logging + +notif_web = ClientSession() +URL = getenv("gotify_url") +downloading = {} + +async def _send_notification(title,message): + if not URL: + logging.debug("Ignoring notification push because gotify url not set") + return + res = await notif_web.post(URL, data={ + "title": title, + "message": message + }) + if res.status == 200: + return 1 + else: + logging.warn("Could not push notification ({}: {})".format(res.status, await res.text())) + +async def send_anime_notification(anime): + logging.info("Finished downloading episode {} of {}".format(anime.last_episode, anime.title)) + title = anime.title + message = "Episode {} has finished downloading".format(anime.last_episode) + return await _send_notification(title, message) + +async def dl_watchdog(): + await login_qb(client=web) + logging.info("Starting download watchdog") + while True: + try: + res = await web.get(QB_URL+"/query/torrents",params={'filter':'downloading', 'category':'Anime'}) + if res.status==200: + res = await res.json() + hashes = [i['hash'] for i in res] + for i in downloading: + if i not in hashes: + anime = downloading.pop(i,None) + await send_anime_notification(anime) + else: + logging.warn("Something went wrong with fetching downloads ({}: {})".format(res.status,await res.text())) + except Exception as e: + logging.error(str(e)) + continue + finally: + await sleep(5) \ No newline at end of file diff --git a/Animebyter/README.md b/Animebyter/README.md new file mode 100644 index 0000000..00f4795 --- /dev/null +++ b/Animebyter/README.md @@ -0,0 +1,34 @@ +# Animebyter + +##### A daemon and web app that keeps track of the shows you watch and automatically downloads new episode off Animebytes.tv +---- + +### How to set-up +0) Set-up docker. This guide assumes that you use qBittorrent as a docker container and that you have some sort of web server with proxying capabilities, also dockerized. +1) Edit docker-compose: + - Edit the `qbit-network` external name to the one that matches your qbittorrent network. + - Edit the `qbit_url` enironment variable to the hostname of your qbittorrent server. This will typically be your container name (so make sure to set one). + - Edit the `ab_key` environment variable with your animebytes passkey. You can find this under Settings > Account > Passkey. + - Edit the `base_url` environment variable with the path that you've set on your reverse proxy. It should be `/` if you run it on a subdomain. + - Edit the `gotify_url` to a valid Gotify server URL if you want to receive notifications. +2) Run `docker-compose up -d`. If you've set-up your docker-compose correctly it should run without problems. +3) Configure your reverse proxy. The docker-compose.yml provided, exposes a network called `animebyter-network` and names the container `animebyter`. You should edit the deployment of your web-server, making sure it is connected to the `animebyter-network`. The app should then be accessible on `animebyter:5000`. Here's a sample configuration for the caddy webserver: +``` +proxy /animebyter animebyter:5000 { + transparent + without /animebyter +} +redir /animebyter /animebyter/ + +``` + +### How to use +The page shows two tables: Airing and Watching. You can add any show by clicking the (+) and remove it by clicking (-). Make sure to set a download path and preferably a label also. Also make sure to log into qBittorrent. + +### Development + +Want to contribute? Sure + +License +---- +MIT diff --git a/Animebyter/animebyter.py b/Animebyter/animebyter.py deleted file mode 100644 index dad7a8b..0000000 --- a/Animebyter/animebyter.py +++ /dev/null @@ -1,252 +0,0 @@ -from feedparser import parse -from aiohttp import ClientSession -from discord.ext.commands import Bot -from tinydb import TinyDB, Query -from os.path import join -from os import getenv -from asyncio import sleep -from traceback import print_exc - -client = Bot('ab!') -QB_URL = getenv("qbit_url") -INTERVAL = int(getenv("INTERVAL")) if getenv("INTERVAL") else 300 -web = ClientSession() -db = TinyDB(getenv("database_path","animebyter.json")) -path = getenv("download_path") - -def get_channel_id(): - res = db.search(Query().type == 'channel') - if len(res) > 0: - chn = res[0]['id'] - else: - db.insert({'type':'channel','id':''}) - chn = None - return chn - -def get_notif_id(): - res = db.search(Query().type == 'notifications') - if len(res) > 0: - id = res[0]['id'] - else: - db.insert({'type':'notifications','id':''}) - id = None - return id - -get_channel_id() -get_notif_id() - -downloading = [] - -class Anime: - def __init__(self,name,le,tl,res): - self.title = name.replace("/","-") - self.last_episode = le - self.torrent_link = tl - self.resolution = res.strip() - - def __eq__(self,other): - return self.title == other.title - - def __hash__(self): - return hash(self.title) - -async def login_qb(): - async with web.post(QB_URL+'/login',data={'username':getenv("qbit_user"),'password':getenv("qbit_pass")}) as res: - if res.status!=200: - print("Could not authenticate with qBittorrent. Exiting...") - exit(1) - else: - print("Logged in to qBittorrent") - -async def get_airing(): - r = [] - res = await web.get("https://animebytes.tv/feed/rss_torrents_airing_anime/{}".format(getenv("ab_key"))) - if res.status==200: - txt = await res.text() - rss = parse(txt) - for i in rss['entries']: - try: - title = i['ab_grouptitle'] - ep = int((''.join(x for x in i['ab_torrentproperty'].split("|")[6] if x.isdigit())).strip()) - link = i['link'] - r.append(Anime(title,ep,link,i['ab_torrentproperty'].split("|")[3])) - except Exception: - continue - return r - -async def add_anime(anime): - print("Adding episode {} of {}".format(anime.last_episode,anime.title)) - try: - await add_torrent(anime.torrent_link, join(path,anime.title), 'Anime') - except Exception as e: - print("Failed to add episode {} of {} ({})".format(anime.last_episode,anime.title,e)) - else: - msg = "Added episode {} of {}".format(anime.last_episode,anime.title) - print(msg) - return msg - - -async def add_torrent(url,path,category): - async with web.post(QB_URL+'/command/download',data={'urls':url,'savepath':path,'category':category}) as res: - if res.status==200: - return 1 - else: - raise Exception(await res.text()) - -async def main(): - print("Starting new episode checker") - while True: - try: - print("Checking for new episodes") - airing = await get_airing() - for i in airing: - res = db.search(Query().title==i.title) - if res: - le = res[0]['last_episode'] - if le {} has finished downloading.".format(get_notif_id(),i)) - else: - print("Something went wrong with fetching downloads ({}: {})".format(res.status,await res.text())) - except Exception as e: - print(str(e)) - print_exc() - continue - finally: - await sleep(10) - -def chunks(s, n=1999): - for start in range(0, len(s), n): - yield s[start:start+n] - -@client.command(pass_context=True) -async def add(ctx): - airing = list(set(await get_airing())) - txt = "" - for i,v in enumerate(airing): - txt+="{}) {}\n".format(i,v.title) - msgs = [] - for i in chunks(txt): - msgs.append(await ctx.send(i)) - msg = await client.wait_for('message',check=lambda m: m.author==ctx.author and m.channel==ctx.channel) - await ctx.channel.delete_messages(msgs) - if msg: - try: - msg = int(msg.content) - except Exception as e: - return await ctx.send(e) - if msg>=len(airing): - return await ctx.send("Invalid number") - an = airing[msg] - db.insert({'title':an.title,'last_episode':an.last_episode-1}) - return await ctx.send("Added {}".format(an.title)) - - -@client.command(pass_context=True) -async def remove(ctx): - watching = [i for i in db.all() if 'title' in i] - txt = "" - for i,v in enumerate(watching): - txt+="{}) {}\n".format(i,v['title']) - msgs = [] - for i in chunks(txt): - msgs.append(await ctx.send(i)) - msg = await client.wait_for('message',check=lambda m: m.author==ctx.author and m.channel==ctx.channel) - if len(msgs)==1: - await msgs[0].delete() - else: - await ctx.channel.delete_messages(msgs) - if msg: - try: - msg = int(msg.content) - except Exception as e: - return await ctx.send(e) - if msg>=len(watching): - return await ctx.send("Invalid number") - an = watching[msg] - db.remove(Query().title==an['title']) - return await ctx.send("Removed {}".format(an['title'])) - -@client.command(pass_context=True) -async def down(ctx): - airing = await get_airing() - txt = "" - for i,v in enumerate(airing): - txt+="{}) {} (Episode: {})[{}]\n".format(i,v.title,v.last_episode,v.resolution) - msgs = [] - for i in chunks(txt): - msgs.append(await ctx.send(i)) - msg = await client.wait_for('message',check=lambda m: m.author==ctx.author and m.channel==ctx.channel) - print(msg) - await ctx.channel.delete_messages(msgs) - if msg: - try: - msg = int(msg.content) - except Exception as e: - return await ctx.send(e) - if msg>=len(airing): - return await ctx.send("Invalid number") - await ctx.send(await add_anime(airing[msg])) - -@client.command(pass_context=True) -async def torrent(ctx,url,path=None,category=None): - if not path: - if 'jpopsuki' in url: - path = '/Music/' - else: - path = '/Downloads/' - if not category: - if 'jpopsuki' in url: - category = 'Music' - try: - await ctx.send(await add_torrent(url,path,category)) - except Exception as e: - await ctx.send("Failed to add torrent: {}".format(e)) - - -@client.command(pass_context=True) -async def setchannel(ctx): - db.update({'id':ctx.channel.id},Query().type == 'channel') - await ctx.send("I will now send notifications to this channel!") - -@client.command(pass_context=True) -async def setnotif(ctx): - db.update({'id':ctx.author.id}, Query().type == 'notifications') - await ctx.send("I will now tag you for notifications!") - -@client.event -async def on_ready(): - print("Starting animebyter") - await login_qb() - client.loop.create_task(main()) - client.loop.create_task(dl_watchdog()) - -client.run(getenv("discord_token")) diff --git a/Animebyter/animebyter.service b/Animebyter/animebyter.service deleted file mode 100644 index b2f4833..0000000 --- a/Animebyter/animebyter.service +++ /dev/null @@ -1,21 +0,0 @@ -[Unit] -Description=Adds new episodes from animebytes on qbittorrent -After=network.target qbittorrent.service - -[Service] - -WorkingDirectory=/home/marios/Animebyter #The location of the animebyter script -ExecStart=/usr/bin/python3.7 animebyter.py -User=marios -Restart=always - -Environment=qbit_url=http://127.0.0.1:8080 #URL of your qBittorrent WebUI -Environment=qbit_user=admin #Username for qBittorrent WebUI -Environment=qbit_pass=adminadmin #Password for qBittorrent WebUI -Environment=ab_key=YOUR_ANIMEBYTES_TOKEN #Your animebytes token. You can find this by querying any of the RSS feeds -Environment=channel=THE_CHANNEL_YOU_WANT_TO_SEND_NOTIFICATIONS_IN #The ID of the channel you want to send notifications in on discord -Environment=discord_token=YOUR_DISCORD_TOKEN #The token of your discord bot -Environment=INTERVAL=300 #It will look for new episodes every x seconds - -[Install] -WantedBy=multi-user.target diff --git a/Animebyter/docker-compose.yml b/Animebyter/docker-compose.yml index 23ce208..4430f3f 100644 --- a/Animebyter/docker-compose.yml +++ b/Animebyter/docker-compose.yml @@ -5,18 +5,22 @@ services: container_name: animebyter environment: - qbit_url=http://qbittorrent:8080 - - qbit_user=admin - - qbit_pass=adminadmin - - ab_key=YOUR_ANIMEBYTES_KEY - - discord_token=YOUR_DISCORD_TOKEN - - download_path=/Anime + - ab_key=YOUR_ANIMEBYTES_TOKEN + - database=/db/animebyter.json + - base_url=/animebyter + - gotify_url=Gotify_URL + - LOGLEVEL=INFO volumes: - ./db:/db networks: - qbit-network + - animebyter-network restart: unless-stopped networks: + animebyter-network: + driver: bridge + qbit-network: external: - name: qbittorrent_qbit-network \ No newline at end of file + name: qbittorrent_qbit-network diff --git a/Animebyter/main.py b/Animebyter/main.py new file mode 100644 index 0000000..e8c7fd9 --- /dev/null +++ b/Animebyter/main.py @@ -0,0 +1,95 @@ +from quart import Quart, request, make_response, render_template, redirect +from Animebyter import get_airing +from Downloader import downloader, store, login_qb, qbLoginException, checker +from Notifications import dl_watchdog +from asyncio import get_event_loop,gather +import os +from sys import stdout +import logging + +logging.basicConfig(level=logging.DEBUG) +app = Quart(__name__,"/static") +base_url = os.getenv("base_url") + +class LastAiring: + airing = [] + def get(self): + return self.airing + def sett(self,a): + self.airing = a +last_airing = LastAiring() + +class FakeObj: + def __init__(self,dc): + for i in dc.keys(): + setattr(self,i,dc[i]) + +@app.route("/") +async def home(): + airing = await get_airing() + last_airing.sett(airing) + watching = store.get("watching") + dl_path = store.get("downloadPath") + dl_label = store.get("downloadLabel") + return await render_template('index.html', airing=airing, watching=[FakeObj(i) for i in watching], dl_path=dl_path, dl_label=dl_label) + +@app.route("/addAnime") +async def add_show(): + id = request.args.get("id") + la = last_airing.get() + show = None + for i in la: + if i.id == id: + show = i + break + if show: + show.last_episode -=1 + store.ladd("watching",vars(show)) + return redirect(base_url) + else: + return await render_template("error.html", message="Show does not exist", base_url=base_url) + +@app.route("/removeAnime") +async def remove_show(): + id = request.args.get("id") + watching = store.get("watching") + for i in watching: + if id == i['id']: + store.lremvalue("watching",i) + return redirect(base_url) + return await render_template("error.html",message="Show does not exist", base_url=base_url) + +@app.route("/updatePath", methods=["POST"]) +async def set_path(): + path = (await request.form).get("path") + if os.path.isdir(path): + store.set("downloadPath", path) + return redirect(base_url) + else: + return await render_template("error.html", message="{} is not a valid path".format(path)) + +@app.route("/updateLabel", methods=["POST"]) +async def set_label(): + label = (await request.form).get("label") + store.set("downloadLabel", label) + return redirect(base_url) + +@app.route("/updateCreds", methods=["POST"]) +async def update_creds(): + form = await request.form + username = form.get("user") + password = form.get("password") + try: + await login_qb(username, password) + store.set("qbUser", username) + store.set("qbPass", password) + return redirect(base_url) + except qbLoginException: + return await render_template("error.html", message="Invalid credentials. Try again", base_url=base_url) + + +if __name__ == '__main__': + server_task = app.run_task("0.0.0.0") + loop = get_event_loop() + + loop.run_until_complete(gather(server_task,downloader(),checker(),dl_watchdog())) diff --git a/Animebyter/requirements.txt b/Animebyter/requirements.txt new file mode 100644 index 0000000..ffa3cef --- /dev/null +++ b/Animebyter/requirements.txt @@ -0,0 +1,4 @@ +feedparser +quart +aiohttp +pickledb \ No newline at end of file diff --git a/Animebyter/static/css/style.css b/Animebyter/static/css/style.css new file mode 100644 index 0000000..dd41112 --- /dev/null +++ b/Animebyter/static/css/style.css @@ -0,0 +1,38 @@ +table { + border-collapse: collapse; +} + +table, th, td { + border: 2px solid black; +} + +th { + padding: 7px; +} + +#topbar { + width: 100%; + height: 20%; +} + +#watching { + display: inline-block; +} + +#airing { + float:left; + margin-right: 150px; +} + +.text { + width: 400px; + overflow-wrap: break-word; +} + +input { + width: 200px; +} + +form { + padding-bottom: 5px; +} \ No newline at end of file diff --git a/Animebyter/templates/error.html b/Animebyter/templates/error.html new file mode 100644 index 0000000..fdc98b8 --- /dev/null +++ b/Animebyter/templates/error.html @@ -0,0 +1,17 @@ + + + + + + + Error + + +

{{ message }}

+ + + \ No newline at end of file diff --git a/Animebyter/templates/index.html b/Animebyter/templates/index.html new file mode 100644 index 0000000..88a4dc4 --- /dev/null +++ b/Animebyter/templates/index.html @@ -0,0 +1,71 @@ + + + + + + + + + ABdown + + + +
+

ABdown

+
+ Download path: +
+
+ Download label: +
+
+ Username: + Password: + +
+
+
+

Airing

+ + + + + + + + + {% for i in airing %} + + + + + + + {% endfor %} + +
TitleResolutionLast episodeAdd
{{ i.title }}{{ i.resolution }}{{ i.last_episode }}
+
+
+

Watching

+ + + + + + + + + {% for i in watching %} + + + + + + + {% endfor %} + +
TitleResolutionLast episodeRemove
{{ i.title }}{{ i.resolution }}{{ i.last_episode }}
+
+ + + \ No newline at end of file diff --git a/Caddy_Logger/caddy_logger.py b/Caddy_Logger/caddy_logger.py index f8560a0..8ae923e 100644 --- a/Caddy_Logger/caddy_logger.py +++ b/Caddy_Logger/caddy_logger.py @@ -3,20 +3,24 @@ from os import getenv from datetime import datetime import pymysql + class LogItem: - def __init__(self,line): - self.ip = line.split(" ")[0] - self.timestamp = datetime.strptime(line.split("[")[1].split("]")[0].split("+")[0].strip(),"%d/%b/%Y:%H:%M:%S") - req = line.split('"')[1].split(' ') - self.method = req[0] - self.path = req[1] - self.http = req[2] - self.status = int(line.split('"')[2].split(' ')[1]) + def __init__(self, line): + self.ip = line.split(" ")[0] + self.timestamp = datetime.strptime(line.split("[")[1].split("]")[ + 0].split("+")[0].strip(), "%d/%b/%Y:%H:%M:%S") + req = line.split('"')[1].split(' ') + self.method = req[0] + self.path = req[1] + self.http = req[2] + self.status = int(line.split('"')[2].split(' ')[1]) + print("Starting log server") -db_conn = pymysql.connect(host=getenv("db_host"),user=getenv("db_user"),password=getenv("db_pass"),db=getenv("db_db"),autocommit=True) +db_conn = pymysql.connect(host=getenv("db_host"), user=getenv( + "db_user"), password=getenv("db_pass"), db=getenv("db_db"), autocommit=True) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -s.bind((getenv("tcp_ip"),int(getenv("tcp_port")))) +s.bind((getenv("tcp_ip"), int(getenv("tcp_port")))) s.listen(True) with db_conn.cursor() as db: @@ -32,17 +36,23 @@ with db_conn.cursor() as db: """) while True: - conn, addr = s.accept() - print("Caddy connected from {}".format(addr)) - while True: - data = conn.recv(4096) - if not data: - break - data = data.decode("utf-8").split("]:")[1].strip() - itm = LogItem(data) - try: - with db_conn.cursor() as db: - db.execute("INSERT INTO `caddy_logs` (`ip`, `timestamp`, `path`, `method`, `http`, `status`) VALUES (%s, %s, %s, %s, %s, %s)",(itm.ip,itm.timestamp,itm.path,itm.method,itm.http,itm.status,)) - except Exception: - continue - conn.close() + conn, addr = s.accept() + print("Caddy connected from {}".format(addr)) + while True: + data = conn.recv(4096) + if not data: + break + data = data.decode("utf-8").split("]:")[1].strip() + itm = LogItem(data) + while True: + try: + with db_conn.cursor() as db: + db.execute("INSERT INTO `caddy_logs` (`ip`, `timestamp`, `path`, `method`, `http`, `status`) VALUES (%s, %s, %s, %s, %s, %s)", + (itm.ip, itm.timestamp, itm.path, itm.method, itm.http, itm.status,)) + break + except Exception: + db_conn = pymysql.connect(host=getenv("db_host"), user=getenv( + "db_user"), password=getenv("db_pass"), db=getenv("db_db"), autocommit=True) + continue + + conn.close() diff --git a/anime_scripts/cmpl.sh b/anime_scripts/cmpl.sh new file mode 100755 index 0000000..3d60359 --- /dev/null +++ b/anime_scripts/cmpl.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +name="$1"; +category="$2"; +content_path="$3"; +root_path="$4"; +save_path="$5"; +file_number="$6"; +size="$7"; +current_tracker="$8"; +hash="$9"; + +export JF_DIR=/Anime/Jellyfin-Anime +cd /scripts + +bash notif.sh "$name"; + +if [[ "$category" == "Anime" ]]; then + echo "Running jellyfin namer"; + if [[ -d "$root_path" ]]; then + bash jellyfin-namer.sh "$root_path"; + else + bash jellyfin-namer.sh "$save_path"; + fi +fi diff --git a/anime_scripts/jellyfin-namer.sh b/anime_scripts/jellyfin-namer.sh new file mode 100755 index 0000000..a953f65 --- /dev/null +++ b/anime_scripts/jellyfin-namer.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +function get_bottom_dir() { + IFS='/'; + read -ra ADDR <<< "$PWD"; + echo "${ADDR[-1]}"; + IFS=' '; +} + +function name_clean() { + local _out=$(echo "$1" | sed -e 's/\[[^][]*\]//g'); + _out=$(echo "$_out" | sed -e 's/([^()]*)//g'); + _out=$(echo "$_out" | sed 's/_/ /g'); + echo $(echo "$_out" | xargs); +} + +cd "$1"; + +bottom_dir=$(get_bottom_dir); +cleaned_dir=$(name_clean "$bottom_dir"); +mkdir "$JF_DIR/$cleaned_dir"; + +for i in *; do + cleaned_name=$(name_clean "$i"); + ln "$PWD/$i" "$JF_DIR/$cleaned_dir/$cleaned_name" >/dev/null 2>/dev/null; +done; + diff --git a/anime_scripts/subtitle-converter.sh b/anime_scripts/subtitle-converter.sh new file mode 100755 index 0000000..713b910 --- /dev/null +++ b/anime_scripts/subtitle-converter.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +function convert() { + res=$(ffprobe -v error -show_entries stream=codec_type,codec_name -of compact "$1" | grep -s "subtitle"); + if [[ "$res" == *"ass"* ]]; then + echo "Converting $1"; + ffmpeg -n -i "$1" -c:s srt "${i%.$ext}.srt" > /dev/null 2&> /dev/null; + else + echo "$1 not ASS. Skipping"; + fi +} + +if [[ -d "$1" ]]; then + cd "$1"; + shopt -s globstar + for i in **/*; do + ext="${i##*.}"; + convert "$i"; + done +elif [[ -f "$1" ]]; then + cd $(dirname "$1"); + name=$(basename "$1"); + ext="${name##*.}"; + convert "$name"; +fi