#include #include #include #include "filesystem.hpp" #include "functions.hpp" #include "tv_rename.hpp" #include "rapidjson/include/rapidjson/document.h" #ifndef GUI #include #include #endif #ifdef _WIN32 #include "resources_windows.h" #include #include #include #include #include constexpr const char_t *_tv_rename_dir_divider = L"\\"; #define cout std::wcout #define cerr std::wcerr #define cin std::wcin #define toString( a ) utf8_to_wstring( a ) #else #include "resources_linux.h" constexpr const char_t *_tv_rename_dir_divider = "/"; #define cout std::cout #define cerr std::cerr #define cin std::cin #define toString( a ) a #endif string _tv_rename_api_token; Request _tv_rename_request; std::vector< std::pair< string, string > > searchShow( const string &show, const string &language ) { Request &request = _tv_rename_request; request.addHeader( TEXT( "Accept: application/json" ) ); request.addHeader( TEXT( "Authorization: Bearer " ) + _tv_rename_api_token ); request.addHeader( TEXT( "Accept-Language: " ) + language ); auto encoded_show = encodeUrl( show ); rapidjson::Document json; json.Parse( request.get( TEXT( "/search/series?name=" ) + encoded_show ).c_str() ); if ( json.HasParseError() ) return {}; const rapidjson::Value &results = json["data"]; if ( !results.IsArray() ) { return {}; } std::vector< std::pair< string, string > > ret; // find all possible shows for ( size_t i = 0; i < results.Size(); i++ ) { auto show = toString( results[i]["seriesName"].GetString() ); auto id = toString( std::to_string( results[i]["id"].GetInt() ) ); ret.emplace_back( show, id ); } request.clearHeader(); return ret; } #ifndef GUI // get show's ID string getShowId( const string &show, const string &language ) { size_t order{}, pos{}; auto search_results = searchShow( show, language ); for ( const auto &x : search_results ) { cout << ++order << ". " << x.first << std::endl; } cout << _( WHICH_SHOW ) << " " << std::flush; cin >> pos; cin.clear(); cin.ignore( 1, '\n' ); return search_results[pos - 1].second; } #endif string showNameFromId( const string &id, const string &language ) { Request &request = _tv_rename_request; request.addHeader( TEXT( "Accept: application/json" ) ); request.addHeader( TEXT( "Authorization: Bearer " ) + _tv_rename_api_token ); request.addHeader( TEXT( "Accept-Language: " ) + language ); rapidjson::Document json; json.Parse( request.get( TEXT( "/series/" ) + id ).c_str() ); if ( json.HasParseError() ) return TEXT( "" ); return toString( json["data"]["seriesName"].GetString() ); } // get names for all episodes for a given season std::vector< string > getEpisodeNames( const string &id, const string &season, const string &language, bool dvd = false ) { Request &request = _tv_rename_request; request.addHeader( TEXT( "Accept: application/json" ) ); request.addHeader( TEXT( "Authorization: Bearer " ) + _tv_rename_api_token ); request.addHeader( TEXT( "Accept-Language: " ) + language ); string baseurl = TEXT( "/series/" ) + id + TEXT( "/episodes/query?" ); if ( dvd ) baseurl += TEXT( "dvdSeason=" ); else baseurl += TEXT( "airedSeason=" ); baseurl += season; baseurl += TEXT( "&page=" ); string page = TEXT( "1" ); std::vector< string > episodes; do { episodes.resize( episodes.size() * 2 ); rapidjson::Document json; json.Parse( request.get( baseurl + page ).c_str() ); if ( json.HasParseError() ) return {}; if ( json.FindMember( "data" ) == json.MemberEnd() ) break; if ( json["data"].IsArray() ) { rapidjson::Value &epdata = json["data"]; if ( episodes.size() < epdata.Size() ) episodes.resize( epdata.Size() ); for ( size_t i = 0; i < epdata.Size(); i++ ) { if ( epdata[i]["episodeName"].IsString() ) { size_t index = -1; if ( dvd ) index = epdata[i]["dvdEpisodeNumber"].GetInt(); else index = epdata[i]["airedEpisodeNumber"].GetInt(); if ( index == static_cast< size_t >( -1 ) ) continue; if ( index > episodes.size() ) episodes.resize( index ); index--; episodes[index] = toString( epdata[i]["episodeName"].GetString() ); // some eps have whitespace at the end while ( isspace( episodes[index].back() ) ) episodes[index].pop_back(); } } } else { cerr << _( NOT_FOUND_SHOW_SEASON, season.c_str(), showNameFromId( id, language ).c_str() ) << std::endl; } if ( json["links"]["next"].IsNull() ) break; page = toString( std::to_string( json["links"]["next"].GetInt() ) ); } while ( 1 ); request.clearHeader(); return episodes; } std::vector< std::tuple< int, string, string, string > > getRenamedFiles( const string &show, int season, const string &id, const string &language, const string &pattern, const bool &unix_names, const std::map< int, string > &files, bool dvd ) { auto season_num = toString( std::to_string( season ) ); auto episodes = getEpisodeNames( id, season_num, language, dvd ); if ( episodes.empty() ) return {}; if ( files.empty() ) return {}; // vector of pairs , std::vector< std::tuple< int, string, string, string > > renamed_files; for ( const auto &x : files ) { auto last = x.second.find_last_of( _tv_rename_dir_divider ); string og_name; string dir; if ( last == string::npos ) { og_name = x.second; dir = TEXT( "." ); } else { og_name = x.second.substr( last + 1 ); dir = x.second.substr( 0, last ); } unsigned long ep_num = x.first - 1; if ( ep_num < episodes.size() ) { auto pos = og_name.find_last_of( TEXT( "." ) ); string og_name_without_extension{}; string og_name_extension{}; if ( pos == string::npos ) { og_name_without_extension = og_name; } else { og_name_without_extension = og_name.substr( 0, pos ); og_name_extension = og_name.substr( pos ); } // get desired filename auto name = compilePattern( pattern, season, x.first, og_name_without_extension, episodes[ep_num], show ) + og_name_extension; // replace '/' with '|' for ( size_t i = 0; i < name.size(); i++ ) { if ( name[i] == '/' ) { name[i] = '|'; } } // replace characters illegal in windows if desired if ( !unix_names ) { name.erase( std::remove_if( name.begin(), name.end(), []( char_t x ) { return x == '?' || x == '"' || x == '\\' || x == '*'; } ), name.end() ); size_t max{ name.size() }; for ( size_t i = 0; i < max; i++ ) { if ( name[i] == '|' ) { name[i] = '-'; } else if ( name[i] == '<' ) { name.erase( i, 1 ); name.insert( i, TEXT( "<" ) ); max = name.size(); } else if ( name[i] == '>' ) { name.erase( i, 1 ); name.insert( i, TEXT( ">" ) ); max = name.size(); } else if ( name[i] == ':' ) { name[i] = ' '; name.insert( i + 1, TEXT( "- " ) ); max = name.size(); } } for ( size_t i = 0; i < max; i++ ) { if ( name[i] == ' ' && name[i + 1] == ' ' ) { name.erase( i, 1 ); max--; i--; } } } renamed_files.emplace_back( x.first, dir, og_name, name ); } } return renamed_files; } #ifndef _WIN32 std::map< string, string > getLangs() { std::map< string, string > langs; #else std::vector< std::pair< string, string > > getLangs() { std::vector< std::pair< string, string > > langs; #endif Request &request = _tv_rename_request; request.addHeader( TEXT( "Accept: application/json" ) ); request.addHeader( TEXT( "Authorization: Bearer " ) + _tv_rename_api_token ); rapidjson::Document d; d.Parse( request.get( TEXT( "/languages" ) ).c_str() ); request.clearHeader(); rapidjson::Value &lang_data = d["data"]; for ( size_t i = 0; i < lang_data.Size(); i++ ) { #ifndef _WIN32 langs[toString( lang_data[i]["abbreviation"].GetString() )] = toString( lang_data[i]["name"].GetString() ); #else langs.emplace_back( toString( lang_data[i]["abbreviation"].GetString() ), toString( lang_data[i]["name"].GetString() ) ); #endif } return langs; } bool authenticate( const std::string &api_key ) { Request &request = _tv_rename_request; #ifdef _WIN32 request.setServer( TEXT( "api.thetvdb.com" ) ); #else request.setServer( "https://api.thetvdb.com" ); #endif request.addHeader( TEXT( "Accept: application/json" ) ); request.addHeader( TEXT( "Content-Type: application/json" ) ); rapidjson::Document json; json.Parse( request.post( TEXT( "/login" ), "{ \"apikey\": \"" + api_key + "\" }" ) .c_str() ); if ( json.HasParseError() ) return false; _tv_rename_api_token = toString( json["token"].GetString() ); request.clearHeader(); // TODO check return code return true; } void singleSeason( const string &path, const string &show, int season, string id, const string &language, const string &pattern, const bool &unix_names, const bool &trust, std::map< int, string > *files_ptr, bool print, bool dvd ) { #ifndef GUI if ( id.empty() ) id = getShowId( show, language ); #endif std::map< int, std::map< int, string > > *found_files = nullptr; if ( files_ptr == nullptr ) { found_files = new std::map< int, std::map< int, string > >; iterateFS( *found_files, path ); if ( found_files->find( season ) != found_files->end() ) files_ptr = &( *found_files )[season]; } if ( files_ptr == nullptr ) { cerr << _( NOT_FOUND_FOR_SEASON ) << " " << season << std::endl; return; } auto renamed_files = getRenamedFiles( show, season, id, language, pattern, unix_names, *files_ptr, dvd ); if ( renamed_files.empty() ) goto end; if ( print || !trust ) { for ( const auto &renamed : renamed_files ) { cout << std::get< 2 >( renamed ) << " --> " << std::get< 3 >( renamed ) << std::endl; } if ( !trust ) { cout << _( CONFIRMATION ) << " (" << _( YES_1 ) << "/" << _( NO_1 ) << ") " << std::flush; string response; cin >> response; cin.clear(); cin.ignore( 1, '\n' ); if ( response[0] != _( YES_1 )[0] && response[0] != _( YES_2 )[0] ) return; } } for ( const auto &renamed : renamed_files ) { FSLib::rename( std::get< 1 >( renamed ) + _tv_rename_dir_divider + std::get< 2 >( renamed ), std::get< 1 >( renamed ) + _tv_rename_dir_divider + std::get< 3 >( renamed ) ); if ( found_files == nullptr ) { files_ptr[0][std::get< 0 >( renamed )] = std::get< 1 >( renamed ) + _tv_rename_dir_divider + std::get< 3 >( renamed ); } } end: if ( found_files != nullptr ) { delete found_files; } } void singleSeason( const string &path, const string &show, int season, string id, const string &language, const string &pattern, const size_t &flags, std::map< int, string > *files_ptr, bool print ) { singleSeason( path, show, season, id, language, pattern, flags & TV_LINUX, flags & TV_TRUST, files_ptr, print, flags & TV_DVD ); } #ifndef GUI void multipleSeasons( const string &path, const string &show, const std::set< int > &seasons, const string &language, const string &pattern, const size_t &flags ) { std::map< int, std::map< int, string > > season_map; iterateFS( season_map, path ); auto id = getShowId( show, language ); for ( auto &x : season_map ) { if ( seasons.find( x.first ) != seasons.end() ) { singleSeason( path, show, x.first, id, language, pattern, flags & TV_LINUX, flags & TV_TRUST, &x.second, true, flags & TV_DVD ); } } } void allSeasons( const string &path, const string &show, const string &language, const string &pattern, const size_t &flags ) { std::map< int, std::map< int, string > > seasons; // get all season number from this directory and subdirectories iterateFS( seasons, path ); auto id = getShowId( show, language ); for ( auto &x : seasons ) { singleSeason( path, show, x.first, id, language, pattern, flags & TV_LINUX, flags & TV_TRUST, &x.second, true, flags & TV_DVD ); } } void printLangs() { for ( auto &x : getLangs() ) cout << x.first << " - " << x.second << std::endl; } #endif bool findLanguage( const char_t *language ) { for ( auto &x : getLangs() ) { if ( x.first == language ) return true; } return false; } bool validID( const string &id ) { Request &request = _tv_rename_request; request.addHeader( TEXT( "Accept: application/json" ) ); request.addHeader( TEXT( "Authorization: Bearer " ) + _tv_rename_api_token ); request.get( TEXT( "/series/" ) + id ); return request.lastResponseCode() == 200; }