From 14b475ef2d7e438ef1d640189e3637c93a1d8019 Mon Sep 17 00:00:00 2001 From: sharky555 Date: Sat, 30 Apr 2022 16:37:32 -0600 Subject: [PATCH] initial commit --- .gitignore | 4 + README.md | 63 +++++ copycommand.txt | 1 + jpspy.py | 117 ++++++++ json_data/config.json.example | 22 ++ json_data/dictionary.json | 536 +++++++++++++++++++++++++++++++++++++ requirements.txt | 8 + upload.py | 605 ++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 1356 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 copycommand.txt create mode 100644 jpspy.py create mode 100644 json_data/config.json.example create mode 100644 json_data/dictionary.json create mode 100644 requirements.txt create mode 100644 upload.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ee6533 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +__pycache__ +config.json +*session.dat +*.torrent \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..edc39c1 --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +This project is **NOT** being maintained as i'd rather rewrite it from scratch. + +## Overview +**JPS-AU-TV** is a tool for automating the uploading process on jpopsuki.eu. + +**Features:** +- JPS Client. +- FTP Support + +**Installation:** +- Install requirements +``` +pip install -r requirements.txt +``` + +## Command Usage +``` +python autoupload.py {command_name} {ID/URL} +``` +Command | Description | Example +------------- | ------------- | ------------- +-d, --debug | Provides additional information on upload for debugging purposes | `python autoupload.py -d` +-f, --freeleech | Enables freeleech (VIP+ Userclass Requirement) | `python autoupload.py -f -dir "Z:\Music\Korean\Ego\Ego - E [2020.01.02] [EP] [WEB-MP3]"` +-t, --tags | Add additional tags to upload, separated with comma | `python autoupload.py -t "korean, female.vocalist" -dir "Z:\Music\Korean\Ego\Ego - E [2020.01.02] [EP] [WEB-MP3]"` +-dry, --dryrun | Carries out all actions other than the upload itself.| `python autoupload.py -dir "Z:\Music\Korean\Ego\Ego - E [2020.01.02] [EP] [WEB-MP3]" -dry` + +## Config.json + +- It's not recommended to use both local watch/download folders and ftp watch/download folders at the same time as it will result in seeding from 2 locations. + +**credentials:** + +Config | Description | Example +------------- | ------------- | ------------- +Username | JPopSuki Username | Slyy +Password | JPopSuki Password | Password + +**local_prefs** + +Config | Description | Example +------------- | ------------- | ------------- +add_to_watch_folder | moves .torrent file to local watch folder | `true/false` +add_to_downloads_folder | moves torrent data to local downloads folder | `true/false` +local_watch_folder | directory of local watch folder | `Z:/watch/Transmission` +local_downloads_folder | directory of local downloads folder | `Z:/downloads` + + +**ftp_prefs:** + +Config | Description | Example +------------- | ------------- | ------------- +enable_ftp | enable ftp mode, if enabled suggested to disable local watch and downloads folders | `true/false` +add_to_watch_folder | transfer .torrent file to watch folder on FTP server | `true/false` +add_to_downloads_folder | transfer torrent data to downloads folder on FTP server | `true/false` +ftp_server | url of ftp server | haze.seedhost.eu +ftp_username | username of ftp account | slyy +ftp_password | password of ftp account | password +ftp_watch_folder | directory of ftp watch folder | `/downloads/watch/transmission` +ftp_downloads_folder | directory of ftp downloads folder | `/downloads` + + +## Disclaimer +- The usage of this script **may be** illegal in your country. It's your own responsibility to inform yourself of Copyright Law. diff --git a/copycommand.txt b/copycommand.txt new file mode 100644 index 0000000..610b44c --- /dev/null +++ b/copycommand.txt @@ -0,0 +1 @@ +python upload.py -i "C:\file.ts" --releasetype "PV" --artist "aimer" --title "test" --tags "pop" --year "2022" --mediasource "HDTV" --imagepath "C:\file.ts_thumbs_[2022.04.29_19.06.07].jpg" --torrentgroupdescription "a long description!!!!" --torrentdescription "placeholder" diff --git a/jpspy.py b/jpspy.py new file mode 100644 index 0000000..2b3b32c --- /dev/null +++ b/jpspy.py @@ -0,0 +1,117 @@ +import os +import pickle +import datetime +from urllib.parse import urlparse +import requests + + +class MyLoginSession: + def __init__(self, + loginUrl, + loginData, + loginTestUrl, + loginTestString, + sessionFileAppendix='_session.dat', + maxSessionTimeSeconds=30 * 60, + proxies=None, + userAgent='Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1', + debug=False, + forceLogin=False, + **kwargs): + """ + save some information needed to login the session + + you'll have to provide 'loginTestString' which will be looked for in the + responses html to make sure, you've properly been logged in + + 'proxies' is of format { 'https' : 'https://user:pass@server:port', 'http' : ... + 'loginData' will be sent as post data (dictionary of id : value). + 'maxSessionTimeSeconds' will be used to determine when to re-login. + """ + urlData = urlparse(loginUrl) + + self.proxies = proxies + self.loginData = loginData + self.loginUrl = loginUrl + self.loginTestUrl = loginTestUrl + self.maxSessionTime = maxSessionTimeSeconds + self.sessionFile = urlData.netloc + sessionFileAppendix + self.userAgent = userAgent + self.loginTestString = loginTestString + self.debug = debug + + self.login(forceLogin, **kwargs) + + def modification_date(self, filename): + """ + return last file modification date as datetime object + """ + t = os.path.getmtime(filename) + return datetime.datetime.fromtimestamp(t) + + def login(self, forceLogin=False, **kwargs): + """ + login to a session. Try to read last saved session from cache file. If this fails + do proper login. If the last cache access was too old, also perform a proper login. + Always updates session cache file. + """ + wasReadFromCache = False + if self.debug: + print('loading or generating session...') + if os.path.exists(self.sessionFile) and not forceLogin: + time = self.modification_date(self.sessionFile) + + # only load if file less than 30 minutes old + lastModification = (datetime.datetime.now() - time).seconds + if lastModification < self.maxSessionTime: + with open(self.sessionFile, "rb") as f: + self.session = pickle.load(f) + wasReadFromCache = True + if self.debug: + print("loaded session from cache (last access %ds ago) " + % lastModification) + if not wasReadFromCache: + self.session = requests.Session() + self.session.headers.update({'user-agent': self.userAgent}) + res = self.session.post(self.loginUrl, data=self.loginData, + proxies=self.proxies, **kwargs) + + if self.debug: + print('created new session with login') + self.saveSessionToCache() + + # test login + res = self.session.get(self.loginTestUrl) + if res.text.lower().find(self.loginTestString.lower()) < 0: + if self.debug: + print(res.text) + raise Exception("could not log into provided site '%s'" + " (did not find successful login string)" + % self.loginUrl) + + def saveSessionToCache(self): + """ + save session to a cache file + """ + # always save (to update timeout) + with open(self.sessionFile, "wb") as f: + pickle.dump(self.session, f) + if self.debug: + print('updated session cache-file %s' % self.sessionFile) + + def retrieveContent(self, url, method="get", postData=None, postDataFiles=None, **kwargs): + """ + return the content of the url with respect to the session. + + If 'method' is not 'get', the url will be called with 'postData' + as a post request. + """ + if method == 'get': + res = self.session.get(url, proxies=self.proxies, **kwargs) + else: + res = self.session.post(url, data=postData, proxies=self.proxies, files=postDataFiles, **kwargs) + + # the session has been updated on the server, so also update in cache + self.saveSessionToCache() + + return res diff --git a/json_data/config.json.example b/json_data/config.json.example new file mode 100644 index 0000000..a649d45 --- /dev/null +++ b/json_data/config.json.example @@ -0,0 +1,22 @@ +{ + "credentials": { + "username": "username", + "password": "password" + }, + "local_prefs": { + "add_to_watch_folder": false, + "add_to_downloads_folder": false, + "local_watch_folder": "C:/watch", + "local_downloads_folder": "C:/downloads" + }, + "ftp_prefs": { + "enable_ftp": false, + "add_to_watch_folder": false, + "add_to_downloads_folder": false, + "ftp_server": "server url", + "ftp_username": "username", + "ftp_password": "password", + "ftp_watch_folder": "/downloads/watch/transmission", + "ftp_downloads_folder": "/downloads" + } +} \ No newline at end of file diff --git a/json_data/dictionary.json b/json_data/dictionary.json new file mode 100644 index 0000000..fc90cd1 --- /dev/null +++ b/json_data/dictionary.json @@ -0,0 +1,536 @@ +{ + "release_types": { + "Album": "Album", + "Single": "Single", + "EP": "Album", + "OST": "Album", + "싱글": "Single", + "EP(미니)": "Album", + "정규": "Album", + "컴필레이션": "Album", + "베스트": "Album", + "미니": "Album" + }, + "genres": { + "R&B": "rnb", + "소울": "Soul", + "힙합": "hip.hop", + "랩": "Rap", + "영화": "Movie", + "로맨스": "Romance", + "드라마": "OST", + "TV 드라마": "OST", + "애니메이션": "anime", + "인디": "Indie", + "인디힙합": "Indie Hip-Hop", + "재즈 힙합": "Jazz-Hop", + "댄스 팝": "Dance", + "발라드": "Ballad", + "댄스": "Dance", + "포크 팝": "Folk", + "팝": "Pop", + "팝 락": "Pop.Rock", + "인디 락": "Indie.Rock", + "락": "Rock", + "메탈": "Metal", + "인디 팝": "Indie.Pop", + "일렉트로닉": "Electronic", + "일렉트로닉 팝": "Electro", + "인디일렉트로닉": "Indie.Electronic", + "신스 팝": "Synth-Pop", + "J-POP": "J-Pop", + "재즈": "Jazz", + "성인가요": "Trot", + "월드뮤직": "World Music", + "국악": "Traditional", + "종교": "Religious", + "CCM": "CCM", + "어린이": "Child", + "태교": "Taegyo", + "캐롤": "Christmas", + "트랩": "Trap", + "얼터너티브 팝": "Alternative.Pop", + "얼터너티브": "Alternative", + "뉴에이지": "New Age", + "켈틱": "Celtic", + "켈틱 퓨전": "Celtic.Fusion", + "퓨전": "Fusion", + "에스닉 퓨전": "Ethnic.Fusion", + "레게": "Reggae", + "댄스홀": "Dancehall", + "하우스": "House", + "트로트": "Trot", + "얼터너티브 락": "Alternative.Rock", + "덥": "Dub", + "싸이키델릭": "Psychedelic", + "인스트루멘탈 힙합": "Instrumental.Hip-Hop", + "인스트루멘탈": "Instrumental", + "클래식": "Classic", + "컨트리": "Country", + "종교음악": "Religious", + "전통음악": "Traditional", + "블루스": "Blues", + "라틴": "Latin", + "기타": "Other", + "기능성음악": "Functional", + "인디포크": "indie.folk", + "포크": "Folk", + "어쿠스틱": "Acoustic", + "Hip-Hop": "hip.hop" + }, + "artist": { + "오아": "OA", + "이고": "Ego", + "ハルカトミユキ": null, + "琴音": null, + "下村陽子 × suis from ヨルシカ": null, + "川島ケイジ": null, + "裸体": null, + "空音": null, + "さかいゆう": null, + "美波": null, + "アルカラ": null, + "윤상": null, + "ブレッド & バター": null, + "Official髭男dism": null, + "優里": null, + "サニーデイ・サービス": null, + "ずっと真夜中でいいのに。": null, + "やなぎなぎ": null, + "米津玄師": null, + "梶浦由記": null, + "澁谷逆太郎": null, + "ポルカドットスティングレイ": null, + "김트와친구들": null, + "安斉かれん": null, + "坂口有望": null, + "空想委員会": null, + "ヨルシカ": null, + "向井太一": null, + "ペンギンラッシュ": null, + "黒子首": null, + "中島みゆき": null, + "ハリィさんとスイカくらぶ": null, + "堀込高樹": null, + "堀込泰行": null, + "スピラ・スピカ": null, + "17歳とベルリンの壁": null, + "天野月": null, + "ソールドシュガー": null, + "ナンカノユメ": null, + "ルルルルズ": null, + "東京事変": null, + "藍井エイル": null, + "阿部真央": null, + "赤いくらげ": null, + "週末CITY PLAY BOYZ": null, + "林 浩司": null, + "蒼山幸子": null, + "フラスコテーション": null, + "ゑんら": null, + "ハンブレッダーズ": null, + "鈴木このみ": null, + "みゆな": null, + "ビッケブランカ": null, + "めありー": null, + "キタニタツヤ": null, + "イロメガネ": null, + "ヤユヨ": null, + "ピロカルピン": null, + "ツユ": null, + "リリー楽綺団": null, + "山崎ハコ": null, + "いきものがかり": null, + "はるまきごはん": null, + "おくみずき": null, + "渣泥": null, + "竹渕慶": null, + "早見沙織": null, + "倖田來未": null, + "世武裕子": null, + "ラブリーサマーちゃん": null, + "SUPER☆GiRLS": null, + "österreich": null, + "フレデリック": null, + "ズーカラデル": null, + "神山羊": null, + "太田ひな": null, + "ヤバイTシャツ屋さん": null, + "當山みれい": null, + "大森靖子": null, + "大原櫻子": null, + "東京スカパラダイスオーケストラ": null, + "三月のパンタシア": null, + "雨のパレード": null, + "川崎鷹也": null, + "中島 美嘉": null, + "加藤ミリヤ": null, + "りぶ": null, + "雨ニマケテモ": null, + "三浦大知": null, + "コブクロ": null, + "ももいろクローバーZ": null, + "手嶌葵": null, + "Nao☆": null, + "尾崎裕哉": null, + "マーティ・フリードマン": null, + "幾田りら": null, + "山本彩": null, + "ビッケブランカ VS 岡崎体育": null, + "まるりとりゅうが": null, + "藤原さくら": null, + "藤井風": null, + "sicboy": "", + "LUCA & haruka nakamura": "arca", + "伊沢麻未": null, + "マカロニえんぴつ": null, + "チャラン・ポ・ランタン": null, + "鈴木瑛美子": null, + "神はサイコロを振らない": null, + "宇野実彩子": "AAA", + "ウルトラタワー": null, + "空白ごっこ": null, + "Cö shu Nie": null, + "くるり": null, + "流線形 & 一十三十一": null, + "清水翔太": null, + "あれくん": null, + "秋山黄色": null, + "웬디": "WENDY", + "瀧川ありさ": null, + "キリンジ": null, + "ユアネス": null, + "クレナズム": null, + "H△G": null, + "電音部": null, + "武藤彩未": null, + "中島美嘉": null, + "雫": null, + "坂本真綾": null, + "たかやん": null, + "布袋寅泰": null, + "アイラヴミー": null, + "ナナヲアカリ": null, + "福山雅治": null, + "Jacob&よみぃ": null, + "クミコ": null, + "リュックと添い寝ごはん": null, + "眉村ちあき": null, + "ちゃんみな & SKY-HI": null, + "関口シンゴ": null, + "角巻わため": null, + "Snail’s House": null, + "ロザリーナ": null, + "ニノミヤユイ": null, + "シド": null, + "森内寛樹": null, + "TK from 凛として時雨": null, + "スダンナユズユリー": null, + "ヤなことそっとミュート": null, + "足立佳奈": null, + "Rude-α": null, + "崎山蒼志": null, + "押尾コータロー×DEPAPEPE×崎山蒼志": null, + "清竜人": null, + "竹内アンナ": null, + "クレイユーキーズ with yui": "FLOWER FLOWER", + "fhána": null, + "カサリンチュ": null, + "西川貴教": null, + "瑛人": null, + "SMOKIN’theJAZZ": null, + "ピノキオピー": null, + "佐藤千亜妃": null, + "+α/あるふぁきゅん。": null, + "平井大": null, + "大橋トリオ": null, + "はるかりまあこ": null, + "海蔵亮太": null, + "関取花": null, + "八月二雪": null, + "ぼっちぼろまる & YACA IN DA HOUSE": null, + "ひじり": null, + "映秀。": null, + "吉澤嘉代子": null, + "山出愛子": null, + "SOIL &“PIMP”SESSIONS": null, + "R": "PKCZ", + "STUTS×SIKK-O×鈴木真海子": null, + "DJ松永": null, + "R-指定": null, + "輪入道": null, + "大森玲子": null, + "さユり": null, + "はてな": null, + "春ねむり": null, + "立山秋航": null, + "ナノ": null, + "雄之助": null, + "鈴木雅之": null, + "セイレーン": null, + "リーガルリリー": null, + "ドレスコーズ": null, + "校庭カメラガールドライ": null, + "門脇更紗": null, + "にしな": null, + "羽生まゐご": null, + "浜崎あゆみ": null, + "林明日香": null, + "木下百花": null, + "塩入冬湖": null, + "井筒昭雄": null, + "さだまさし": null, + "ねこね、こねこね。": null, + "ちゃんみな": null, + "冬にわかれて": null, + "알리샤": "Alisha", + "堤博明/照井順政/桶狭間ありさ": null, + "東京初期衝動": null, + "星野源": null, + "印象派": null, + "TOKYOてふてふ": null, + "ハク。": null, + "ヒトリエ": null, + "レルエ": null, + "モーモールルギャバン": null, + "Rin音": null, + "なきごと": null, + "おいしくるメロンパン": null, + "タイトル未定": null, + "アンテナガール": null, + "みんなのこどもちゃん": null, + "我儘ラキア": null, + "おーるどにゅーすぺーぱー": null, + "まこみなみん": null, + "ピューパ!!": null, + "みぃなとルーチ": null, + "名取さな": null, + "どーぷちゃん": null, + "藤井フミヤ": null, + "ハッカドール": null, + "変態紳士クラブ": null, + "広瀬こはる": null, + "富樫美鈴,水原朋也": null, + "橘直美": null, + "四宮小次郎": null, + "タクミ・アルディーニ": null, + "幸平創真": null, + "佐久間正英": null, + "ネクライトーキー": null, + "神田沙也加": null, + "PUNPEE×VaVa×OMSB": "SUMMIT", + "優里香": null, + "般若/ZORN/SHINGO★西成": null, + "トベタ・バジュン": null, + "ウォルピスカーター": null, + "レイラ": null, + "前島麻由": null, + "からっぽペペロンチーノ": null, + "椎名豪 featuring 中川奈美": null, + "清水あいり": null, + "吉田凜音": null, + "ππ来来 & ケンモチヒデフミ": null, + "yuigot & ぷにぷに電機": null, + "春乃こね子": null, + "TEMPLIME & 星宮とと": null, + "虹河ラキ": null, + "和楽器バンド": null, + "ACAね": null, + "夜とSAMPO": "", + "STUTS & 松たか子 with 3exes": null, + "伶": null, + "後藤まりこアコースティックviolence POP": null, + "ジェニーハイ": null, + "神様、僕は気づいてしまった": null, + "佐々木恵梨": null, + "ヴィヴィ(Vo.八木海莉)": null, + "須田景凪": null, + "一二三": null, + "メランコリーメランコリー": null, + "こゑだ": null, + "RUNG HYANG x claquepot x 向井太一": null, + "アポロノーム": null, + "クレイユーキーズ with DAZBEE": null, + "鬱P": null, + "安田レイ": null, + "揺らぎ": null, + "starscream & 栄免建設": null, + "小林私": null, + "くっつくパピー": null, + "久石 譲": null, + "どんぐりず": null, + "maeshima soshi & Rin音": null, + "WAЯROCK": null, + "サイプレス上野とロベルト吉野": null, + "かめりあ": null, + "畠中祐": null, + "坂本美雨 with CANTUS": null, + "re:plus × Ai Ninomiya": "R.A.", + "☆Taku Takahashi": null, + "クリス・ハート": null, + "神はサイコロを振らない × アユニ・D": null, + "夏目間風": null, + "大和田慧": null, + "カモメサノダブルクリックバンド": null, + "カネヨリマサル": null, + "荒田洸": null, + "景山将太": null, + "ゆに": null, + "土岐麻子": null, + "カワゴエリエ": null, + "ほのかりん": null, + "島爺": null, + "回路-kairo-": null, + "メ・ガーネ": null, + "스타트라인": "Startline", + "アイナ・ジ・エンド": null, + "フレンズ": null, + "鞘師里保": null, + "鈴木愛理": null, + "鈴木愛理 × Blue Vintage": null, + "仮谷せいら": null, + "from AAA": "SHINJIRO ATAE", + "周防パトラ": null, + "緑黄色社会": null, + "DE DE MOUSE, TANUKI, 一十三十一": null, + "あたらよ": null, + "神谷千尋": null, + "銀銀": null, + "みきまりあ": null, + "カンザキイオリ": null, + "おはようございます": null, + "ザ・モアイズユー": null, + "岸田教団 & THE明星ロケッツ": null, + "亜咲花": null, + "久石譲": null, + "青葉市子": null, + "青山テルマ": null, + "角野隼斗": null, + "オルターリードコード": null, + "ππ来来": null, + "毒島大蛇": null, + "Mi☆nA": null, + "パスピエ": null, + "ラックライフ": null, + "Maika Loubté": null, + "羊文学": null, + "鈴木真海子": null, + "埼玉最終兵器": null, + "星宮とと+TEMPLIME": null, + "ゴホウビ": null, + "15才と大森靖子": null, + "フミンニッキ": null, + "コンニチワトーキョー": null, + "ゆある": null, + "おはようツインテール": null, + "クレイジーケンバンド": null, + "ササノマリイ": null, + "日の当たる場所 & しの": null, + "絢香": null, + "Night Tempo/菊池桃子": null, + "月詠み": null, + "松尾太陽": null, + "今市隆二": null, + "コトフル": null, + "大国主": null, + "奈々生": null, + "悪羅王": null, + "∴ [yueni]": null, + "大沼パセリ": null, + "音楽的同位体 可不": "KAFU", + "神はサイコロを振らない & キタニタツヤ": null, + "ヰ世界情緒": null, + "春猿火": null, + "理芽": null, + "花譜": null, + "波羅ノ鬼": null, + "かいりきベア": null, + "平手友梨奈": null, + "高梨康治": null, + "保刈久明": null, + "沢田完": null, + "L'Arc~en~Ciel": null, + "星街すいせい": null, + "高槻かなこ": null, + "杏里": null, + "Leeu & こゆき": null, + "幸祜": null, + "りりあ。": null, + "DECO*27×堀江晶太": "kemu", + "溫蒂漫步": null, + "天輝おこめ": null, + "さよならポニーテール": null, + "ひかりのなかに": null, + "猫jealousy": null, + "パソコン音楽クラブ": null, + "家入レオ": null, + "アイマリン": null, + "オレンジスパイニクラブ": null, + "あいみょん": null, + "坂本美雨": null, + "岡崎体育": null, + "梶原岳人": null, + "三代目 J SOUL BROTHERS from EXILE TRIBE": null, + "高橋諒": null, + "麗奈": null, + "ポルノグラフィティ": null, + "ASIAN KUNG-FU GENERATION & 世武裕子": null, + "Cö Shu Nie": null, + "OKAMOTO’S": null, + "マハラージャン": null, + "ストレイテナー": null, + "スキマスイッチ": null, + "東京○X問題": null, + "ハルカミライ": null, + "明くる夜の羊": null, + "あるくとーーふ": null, + "岡部啓一・MONACA": null, + "光宗信吉": null, + "もさを。": null, + "ラバーキャロッツ": null, + "ウソツキ": null, + "長沼秀樹": null, + "眩暈SIREN": null, + "et-アンド-": null, + "フジファブリック": null, + "平沢進": null, + "ゲシュタルト乙女": null, + "エイプリルブルー": null, + "鹿乃": null, + "吉岡聖恵": null, + "鳳凰火凛": null, + "ケツメイシ": null, + "サイダーガール": null, + "cali≠gari": null, + "岸田教団&THE明星ロケッツ": null, + "ichigo from 岸田教団&THE明星ロケッツ": null, + "オレスカバンド": null, + "夏芽すやり & かききまなみ": null, + "サカナクション": null, + "聴色": null, + "みるきーうぇい": null, + "大塚愛": null, + "橋本絵莉子": null, + "CHANGMIN from 東方神起": null, + "ミクロミカ": null, + "和田アキ子": null, + "坂本龍一": null, + "天使うと": null, + "ブルー・ペパーズ": null, + "DÉ DÉ MOUSE & ぷにぷに電機": null, + "山崎あおい": null, + "milet×Aimer×幾田りら": null, + "数原龍友": null, + "稲葉曇": null, + "八木海莉": null, + "澤野弘之": null, + "澤田空海理": null, + "バウンダリー": null, + "がんばれ!隠れ汗プロジェクト": null, + "おとなりアイニー": null, + "高橋祐理・森重秀太 × mzsrz": "Zero PLANET", + "寺田創一": null, + "きゃりーぱみゅぱみゅ": null, + "macico & おかもとえみ": null + } +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3b58ca0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +bs4 +langdetect +mutagen +requests +torf +tqdm +html5lib +pymediainfo \ No newline at end of file diff --git a/upload.py b/upload.py new file mode 100644 index 0000000..b7be578 --- /dev/null +++ b/upload.py @@ -0,0 +1,605 @@ +# Standard library packages +from pickle import FALSE +import re +import os +import sys +import shutil +import string +import argparse +import html +from urllib.parse import urlparse +import json +import ftplib + +# Third-party packages +import requests +from bs4 import BeautifulSoup +from torf import Torrent +from tqdm import tqdm +from langdetect import detect +from pymediainfo import MediaInfo +from pathlib import Path + +# JPS-AU files +import jpspy + +def asciiart (): + print(""" + ██╗██████╗ ███████╗ █████╗ ██╗ ██╗ ████████╗██╗ ██╗ + ██║██╔══██╗██╔════╝ ██╔══██╗██║ ██║ ╚══██╔══╝██║ ██║ + ██║██████╔╝███████╗█████╗███████║██║ ██║█████╗██║ ██║ ██║ +██ ██║██╔═══╝ ╚════██║╚════╝██╔══██║██║ ██║╚════╝██║ ╚██╗ ██╔╝ +╚█████╔╝██║ ███████║ ██║ ██║╚██████╔╝ ██║ ╚████╔╝ + ╚════╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ + +""") + +# Get arguments using argparse +def getargs(): + """ + Get arguments using argparse + """ + parser = argparse.ArgumentParser() + parser.add_argument('-i', '--input', help='Initiate upload on input file', nargs='?', required=True) + parser.add_argument('-d', '--debug', help='Enable debug mode.', action='store_true') + parser.add_argument("-dry", "--dryrun", help="Dryrun will carry out all actions other than the actual upload to SM.", action="store_true") + parser.add_argument("-rt", "--releasetype", help='Set the release type. (PV, Music Performance, TV Music, TV Variety, TV-Drama)', nargs='?') + parser.add_argument("-a", "--artist", help='Set the artist. (Romaji\English). Split multiple with ","', nargs='?') + parser.add_argument("-oa", "--originalartist", help='Set the artist. (Original Language)', nargs='?') + parser.add_argument("-ti", "--title", help='Set the title. (Romaji\English)', nargs='?') + parser.add_argument("-oti", "--originaltitle", help='Set the title. (Original Language)', nargs='?') + parser.add_argument("-t", "--tags", help="Add additional tags to the upload. At least 2 tags are required", nargs='?') + parser.add_argument("-y", "--year", help='Set the torrent year (YYYYMMDD or YYYY).', nargs='?') + parser.add_argument("-fl", "--freeleech", help="Enables freeleech.", action="store_true") + parser.add_argument("-eti", "--editiontitle", help='Set the edition title', nargs='?') + parser.add_argument("-ey", "--editionyear", help='Set the torrent edition year (YYYYMMDD or YYYY).', nargs='?') + parser.add_argument("-ms", "--mediasource", help='Set the media source. (HDTV, Web)', nargs='?') + parser.add_argument("-im", "--imagepath", help='Set the torrent cover', nargs='?') + parser.add_argument("-tdes", "--torrentdescription", help='Add a torrent description', nargs='?') + parser.add_argument("-tgdes", "--torrentgroupdescription", help='Add a torrent group description. This is a required argument.', nargs='?', required=True) + parser.add_argument("-f", "--formattype", help='Set the media source. (MPEG, MPEG2, AVI, MKV, MP4, h264)', nargs='?') + + return parser.parse_args() + +# Acquire the authkey used for torrent files from upload.php +def getauthkey(): + uploadpage = j.retrieveContent("https://jpopsuki.eu/upload.php") + soup = BeautifulSoup(uploadpage.text, 'html5lib') + rel2 = str(soup.select('#wrapper #content .thin')) + # Regex returns multiple matches, could be optimized. + authkey = re.findall("(?<=value=\")(.*)(?=\")", rel2)[0] + return authkey + + +def copytree(src, dst, symlinks=False, ignore=None): + for item in os.listdir(src): + s = os.path.join(src, item) + d = os.path.join(dst, item) + if os.path.isdir(s): + shutil.copytree(s, d, symlinks, ignore) + else: + shutil.copy2(s, d) + +# Creates torrent file using torf module. +def createtorrent(authkey, filepath, releasedata): + t = Torrent(path=filepath, + trackers=[authkey]) # Torf requires we store authkeys in a list object. This makes it easier to add multiple announce urls. + # Set torrent to private as standard practice for private trackers + t.private = True + t.generate() + ## Format releasedata to bring a suitable torrent name. + # The reason we don't just use the directory name is because of an error in POSTING. + # POSTS do not seem to POST hangul/jp characters alongside files. + filename = f"{releasedata['title']} [{releasedata['media']}-{releasedata['format']}].torrent" + filename = filename.replace("/","/").replace(":",":").replace("?","?").replace("\"","") + try: + t.write(filename) + print("_" * 100) + print("Torrent creation:\n") + print(f"{filename} has been created.") + except: + print("_" * 100) + print("Torrent creation:\n") + os.remove(filename) + print(f"{filename} already exists, existing torrent will be replaced.") + t.write(filename) + print(f"{filename} has been created.") + + return filename + +def add_to_hangul_dict(hangul , english , category): + hangul = str(hangul) + english = str(english) + + categories = ['version','general','artist','genres', 'label', 'distr'] + file = f"json_data/dictionary.json" + json_file = open(file, 'r', encoding='utf-8', errors='ignore') + dictionary = json.load(json_file) + json_file.close() + + new = dict() + for cats in dictionary: + #== Create the categories in the new temp file + new[cats] = dict() + + for key,value in dictionary[cats].items(): + #== List all the old items into the new dict + new[cats][key] = value + + if hangul in new[category].keys(): + + if new[category].get(hangul) is None: + + if english != 'None': + new[category][hangul] = english + + else: + #== Only update if English word has been supplied ==# + if english != 'None': + new[category][hangul] = english + else: + + if english == 'None': + new[category][hangul] = None + else: + new[category][hangul] = english + + json_write = open(file, 'w+', encoding='utf-8') + json_write.write(json.dumps(new, indent=4, ensure_ascii=False)) + json_write.close() + +def translate(string, category, result=None, output=None): + + file = "json_data/dictionary.json" + with open(file, encoding='utf-8', errors='ignore') as f: + dictionary = json.load(f, strict=False) + + category = str(category) + string = str(string) + search = dictionary[category] + string = string.strip() + + if string == 'Various Artists': + output = ['Various Artists',None] + else: + #== NO NEED TO SEARCH - STRING HAS HANGUL+ENGLISH or HANGUL+HANGUL ==# + if re.search("\((?P.*)\)", string): + #== Complete translation, add to dictionary with both values ==# + + #== Contains parentheses, need to split + parenthesis = string.split("(") + pre_parenthesis = parenthesis[0].strip() + in_parenthesis = parenthesis[1].replace(")","").strip() + + #== Check the order of the parentheses ==# + + if re.search("[^\u0000-\u007F]+",pre_parenthesis) and re.search("[^\u0000-\u007F]+",in_parenthesis): + #== Both hangul + first = 'kr' + second = 'kr' + else: + if re.search("[^\u0000-\u007F]+",pre_parenthesis): + first = 'kr' + second = 'eng' + else: + first = 'eng' + second = 'kr' + + if first == 'kr' and second == 'eng': + #== Hangul first ==# + hangul = pre_parenthesis + english = in_parenthesis + add_to_hangul_dict(hangul,english,category) + + elif first == 'eng' and second == 'kr': + #== English first ==# + hangul = in_parenthesis + english = pre_parenthesis + add_to_hangul_dict(hangul,english,category) + elif first == 'kr' and second == 'kr': + #== Both Hangul ==# + hangul = pre_parenthesis + english = None + add_to_hangul_dict(pre_parenthesis,None,category) + add_to_hangul_dict(hangul,None,category) + else: + #== Both English + hangul = None + english = pre_parenthesis + + output = [hangul,english] + + #== No parentheses - HANGUL + else: + + #== If the input string is a full Hangul word - check dictionary and then add if necessary) + if re.search("[^\u0000-\u007F]+", string): + + if string in search.keys(): + #== yes + if search.get(string) is None: + #== If the keyword does not have a translation, add it to the dictionary ==# + output = [string,None] + else: + #== Translation already exists, output the result in a list ==# + output = [string,search.get(string)] + else: + output = [string,None] + add_to_hangul_dict(string, None, category) + + #== Full English name -- leave it + else: + for key,value in search.items(): + if key == string: + output = [value,string] + break + else: + output = [string,string] + + return output + +def gatherdata(): + """ + Retrieve data about the upload. Ask for user input if necessary. + + :return: releasedata: dict + """ + releasedata = {"submit": "true"} + releasedata["album_desc"] = torrentgroupdescription + + if torrentdescription: + releasedata['release_desc'] = torrentdescription + + list_of_types = ["PV", "Music Performance", "TV Music", "TV Variety", "TV-Drama"] + if releasetype in list_of_types: + if releasetype == "PV": + releasedata["type"] = "PV" + elif releasetype == "TV Music": + releasedata["type"] = "TV-Music" + elif releasetype == "TV Variety": + releasedata["type"] = "TV-Variety" + elif releasetype == "TV-Drama": + releasedata["type"] = "TV-Drama" + else: + while(True): + input_lang = input("\n" + "_" * 100 + "\nEnter a number to choose the upload type. \n1=PV\n2=TV-Music\n3=TV-Variety\n4=TV-Drama\n") + if input_lang == "1": + releasedata["type"] = "PV" + break + elif input_lang == "2": + releasedata["type"] = "TV-Music" + break + elif input_lang == "3": + releasedata["type"] = "TV-Variety" + break + elif input_lang == "4": + releasedata["type"] = "TV-Drama" + break + print("Invalid choice.") + + if artist: + releasedata['artist'] = artist + else: + print(artist) + input_english_artist = input("\n" + "_" * 100 + "\nEnter the romaji/english ARTIST name.\n") + releasedata['artist'] = input_english_artist + + if originalartist: + releasedata['artistjp'] = originalartist + else: + input_artist = input("\n" + "_" * 100 + "\nEnter the original ARTIST name. Press enter to skip if this torrent has the artist name already english or already has a artist page.\n") + releasedata['artistjp'] = input_artist + + if title: + releasedata['title'] = title + else: + input_english_title = input("\n" + "_" * 100 + "\nEnter the romaji/english TITLE:\n") + releasedata['title'] = input_english_title + + if originaltitle: + releasedata['titlejp'] = originaltitle + else: + input_title = input("\n" + "_" * 100 + "\nEnter the original TITLE. Press enter to skip.\n") + releasedata['titlejp'] = input_title + + if year: + releasedata["releasedate"] = year + else: + input_year = input("\n" + "_" * 100 + "\nEnter the year as YYYYMMDD or YYYY.\n") + releasedata["releasedate"] = input_year + + if editiontitle: + releasedata['remaster_title'] = editiontitle + else: + input_editiontitle = input("\n" + "_" * 100 + "\nEnter the edition TITLE. Press enter to skip.\n") + if input_editiontitle != "": + if editionyear: + releasedata["remaster_year"] = editionyear + else: + input_editionyear = input("\n" + "_" * 100 + "\nEnter the edition year as YYYY.\n") + releasedata["remaster_year"] = input_editionyear + releasedata['remaster_title'] = input_editiontitle + + if formattype: + releasedata['format'] = formattype + else: + while(True): + input_format = input("\n" + "_" * 100 + "\nEnter a number to choose the format. \n1=MPEG\n2=MPEG2\n3=AVI\n4=MKV\n5=MP4\n6=h264\n") + if input_format == "1": + releasedata["format"] = "MPEG" + break + elif input_format == "2": + releasedata["format"] = "MPEG2" + break + elif input_format == "3": + releasedata["format"] = "AVI" + break + elif input_format == "4": + releasedata["format"] = "MKV" + break + elif input_format == "5": + releasedata["format"] = "MP4" + break + elif input_format == "6": + releasedata["format"] = "h264" + break + print("Invalid choice.") + + if mediasource: + releasedata['media'] = mediasource + else: + while(True): + input_media = input("\n" + "_" * 100 + "\nEnter a number to choose the media source. \n1=HDTV\n2=Web\n") + if input_media == "1": + releasedata["media"] = "HDTV" + break + elif input_media == "2": + releasedata["media"] = "Web" + break + print("Invalid choice.") + + if tags: + releasedata["tags"] = tags + else: + while(True): + input_tags = input("\n" + "_" * 100 + "\nEnter the tags. Separate multiple with \",\". Minimum 1 tag required.\n") + if len(input_tags.split(",")) != 0: + releasedata["tags"] = input_tags + break + else: + print("Please enter at least one tag.") + + return releasedata + +# MediaInfo.parse doesnt work properly right now. it has duplicate lines +def add_mediainfo_to_releasedata(filename, releasedata): + """ + Retrieve mediainfo and append it to the releasedata dictionary. + :return: releasedata: dict + """ + mediainfosall = "" + mediainfosall += str(MediaInfo.parse(filename, output="text")) + replacement = str(Path(filename).parent) + mediainfosall = mediainfosall.replace(replacement, '') + #releasedata["release_desc"] += "\n\n" + mediainfosall + print(mediainfosall) + return releasedata + +# Simple function to split a string up into characters +def split(word): + return [char for char in word] + +def detectlanguage(string): + ## Language Detect + # This is a required check as we don't want to enter non-english/romaji characters into the title field. + characters = split(string) + language_list = [] + for c in characters: + try: + language = detect(c) + language_list.append(language) + except: + langauge = "error" + + if 'ko' or 'ja' in language_list: + en = False + else: + en = True + + return en + +def uploadtorrent(torrent, cover, releasedata): + + # POST url. + uploadurl = "https://jpopsuki.eu/upload.php" + + # Dataset containing all of the information obtained from our FLAC files. + data = releasedata + + if debug: + print('_' * 100) + print('Release Data:\n') + print(releasedata) + + try: + postDataFiles = { + 'file_input': open(torrent, 'rb'), + 'userfile': open(cover, 'rb') + } + except FileNotFoundError: + + print("_" * 100) + print('File not found!\nPlease confirm file locations and names. Cover image or .torrent file could not be found') + sys.exit() + + + + # If dryrun argument has not ben passed we will POST the results to JPopSuki. + if dryrun != True: + JPSres = j.retrieveContent(uploadurl, "post", data, postDataFiles) + print('\nUpload POSTED') + + ## TODO Filter through JPSres.text and create error handling based on responses + #print(JPSres.text) + +# Function for transferring the contents of the torrent as well as the torrent. +def ftp_transfer(fileSource, fileDestination, directory, folder_name, watch_folder): + + # Create session + session = ftplib.FTP(cfg['ftp_prefs']['ftp_server'],cfg['ftp_prefs']['ftp_username'],cfg['ftp_prefs']['ftp_password']) + # Set session encoding to utf-8 so we can properly handle hangul/other special characters + session.encoding='utf-8' + + # Successful FTP Login Print + print("_" * 100) + print("FTP Login Successful") + print(f"Server Name: {cfg['ftp_prefs']['ftp_server']} : Username: {cfg['ftp_prefs']['ftp_username']}\n") + + if cfg['ftp_prefs']['add_to_downloads_folder']: + + # Create folder based on the directory name of the folder within the torrent. + try: + session.mkd(f"{fileDestination}/{folder_name}") + print(f'Created directory {fileDestination}/{folder_name}') + except ftplib.error_perm: + pass + + # Notify user we are beginning the transfer. + print(f"Beginning transfer...") + # Set current folder to the users preferred destination + session.cwd(f"{fileDestination}/{folder_name}") + # Transfer each file in the chosen directory + for file in os.listdir(directory): + with open(f"{directory}/{file}",'rb') as f: + filesize = os.path.getsize(f"{directory}/{file}") + ## Transfer file + # tqdm used for better user feedback. + with tqdm(unit = 'blocks', unit_scale = True, leave = False, miniters = 1, desc = f'Uploading [{file}]', total = filesize) as tqdm_instance: + session.storbinary('STOR ' + file, f, 2048, callback = lambda sent: tqdm_instance.update(len(sent))) + print(f"{file} | Complete!") + f.close() + + if cfg['ftp_prefs']['add_to_watch_folder']: + with open(fileSource,'rb') as t: + # Set current folder to watch directory + session.cwd(watch_folder) + ## Transfer file + # We avoid tqdm here due to the filesize of torrent files. + # Most connections will upload these within 1-3s, resulting in near useless progress bars. + session.storbinary(f"STOR {torrentfile}", t) + print(f"{torrentfile} | Sent to watch folder!") + t.close() + # Quit session when complete. + session.quit() + +def localfileorganization(torrent, directory, watch_folder, downloads_folder): + # Move torrent directory to downloads_folder + if cfg['local_prefs']['add_to_downloads_folder']: + try: + os.mkdir(os.path.join(downloads_folder, os.path.basename(directory))) + except FileExistsError: + pass + copytree(directory, os.path.join(downloads_folder, os.path.basename(directory))) + shutil.rmtree(directory) + if cfg['local_prefs']['add_to_watch_folder']: + os.rename(torrent, f"{watch_folder}/{torrent}") + +if __name__ == "__main__": + + asciiart() + args = getargs() + + # TODO consider calling args[] directly, we will then not need this line + dryrun = debug = tags = artist = title = formattype = imagepath = freeleech = None + originalartist = originaltitle = torrentdescription = torrentgroupdescription = editiontitle = editionyear = year = mediasource = releasetype = None + inputfile = args.input + + torrentgroupdescription = args.torrentgroupdescription + torrentdescription = args.torrentdescription + if args.dryrun: + dryrun = True + + if args.debug: + debug = True + + if args.freeleech: + freeleech = True + + if args.releasetype: + releasetype = args.releasetype + + if args.title: + title = args.title + + if args.artist: + artist = args.artist + + if args.originalartist: + originalartist = args.originalartist + + if args.originaltitle: + originaltitle = args.originaltitle + + if args.editiontitle: + editiontitle = args.editiontitle + + if args.year: + year = args.year + + if args.editionyear: + editionyear = args.editionyear + + if args.mediasource: + mediasource = args.mediasource + + if args.formattype: + format_type = args.formattype + + if args.tags: + tags = args.tags + + if args.imagepath: + imagepath = args.imagepath + + # Load login credentials from JSON and use them to create a login session. + with open(f'json_data/config.json') as f: + cfg = json.load(f) + loginData = {'username': cfg['credentials']['username'], 'password': cfg['credentials']['password']} + loginUrl = "https://jpopsuki.eu/login.php" + loginTestUrl = "https://jpopsuki.eu" + successStr = "Latest 5 Torrents" + + # j is an object which can be used to make requests with respect to the loginsession + j = jpspy.MyLoginSession(loginUrl, loginData, loginTestUrl, successStr, debug=args.debug) + # Acquire authkey + authkey = getauthkey() + # Gather data of the file + releasedata = gatherdata() + # releasedata_and_mediainfo = add_mediainfo_to_releasedata(inputfile, releasedata) + # Folder_name equals the last folder in the path, this is used to rename .torrent files to something relevant. + # folder_name = os.path.basename(os.path.normpath(directory)) + # Identifying cover.jpg path + # cover_path = directory + "/" + cfg['local_prefs']['cover_name'] + + # Create torrent file. + torrentfile = createtorrent(authkey, inputfile, releasedata) + + # Upload torrent to JPopSuki + uploadtorrent(torrentfile, imagepath, releasedata) + + # Setting variable for watch/download folders + ftp_watch_folder = cfg['ftp_prefs']['ftp_watch_folder'] + ftp_downloads_folder = cfg['ftp_prefs']['ftp_downloads_folder'] + local_watch_folder = cfg['local_prefs']['local_watch_folder'] + local_downloads_folder = cfg['local_prefs']['local_downloads_folder'] + + + # if cfg['ftp_prefs']['enable_ftp']: + # ftp_transfer(fileSource=torrentfile, fileDestination=ftp_downloads_folder, directory=directory, folder_name=folder_name, watch_folder=ftp_watch_folder) + + # if cfg['local_prefs']['add_to_watch_folder'] or cfg['local_prefs']['add_to_downloads_folder']: + # localfileorganization(torrent=torrentfile, directory=directory, watch_folder=local_watch_folder, downloads_folder=local_downloads_folder) + + if not dryrun: + if cfg['local_prefs']['add_to_watch_folder']: + os.rename(torrentfile, f"{local_watch_folder}/{torrentfile}")