#include #include #include "filesystem.hpp" #include "functions.hpp" #include "tv_rename.hpp" #include "json.hpp" using json = nlohmann::json; #ifndef GUI #include #include #include #endif #ifdef _WIN32 #include #include #include #include #include constexpr const char_t *dir_divider = L"\\"; #define cout std::wcout #define cerr std::wcerr #define cin std::wcin #define toString( a ) utf8_to_wstring( a ) #else constexpr const char_t *dir_divider = "/"; #define cout std::cout #define cerr std::cerr #define cin std::cin #define TEXT( a ) a #define toString( a ) a #endif string api_token; Request r; std::vector< std::pair< string, string > > searchShow( const string &show, const string &language ) { r.addHeader( TEXT( "Accept: application/json" ) ); r.addHeader( TEXT( "Authorization: Bearer " ) + api_token ); r.addHeader( TEXT( "Accept-Language: " ) + language ); auto encoded_show = encodeUrl( show ); auto j = json::parse( r.get( TEXT( "/search/series?name=" ) + encoded_show ) ); std::vector< json > results; if( j["data"].is_array() ) { results = j["data"].get< std::vector< json > >(); } else { cout << toString( j ) << std::endl; return {}; } std::vector< std::pair< string, string > > ret; // find all possible shows for ( auto &x : results ) { auto show = toString( x["seriesName"].get< std::string >() ); auto id = toString( std::to_string( x["id"].get< int >() ) ); ret.emplace_back( show, id ); } r.clearHeader(); return ret; } // get show's ID string getShowId( 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 TV Show is the right one? " << std::flush; cin >> pos; cin.clear(); cin.ignore( 1, '\n' ); return search_results[pos-1].second; } string showNameFromId( const string &id, const string &language ) { r.addHeader( TEXT( "Accept: application/json" ) ); r.addHeader( TEXT( "Authorization: Bearer " ) + api_token ); r.addHeader( TEXT( "Accept-Language: " ) + language ); auto j = json::parse( r.get( TEXT( "/series/" ) + id ) ); // TODO check if got json std::string show = j["data"].get< json >()["seriesName"].get< std::string >(); return toString( show ); } // 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 ) { r.addHeader( TEXT( "Accept: application/json" ) ); r.addHeader( TEXT( "Authorization: Bearer " ) + api_token ); r.addHeader( TEXT( "Accept-Language: " ) + language ); string page = TEXT( "1" ); string season_query = TEXT( "airedSeason=" ); if( dvd ) season_query = TEXT( "dvdSeason=" ); std::vector< string > episodes; do { episodes.resize( episodes.size() * 2 ); auto j = json::parse( r.get( TEXT( "/series/" ) + id + TEXT( "/episodes/query?" ) + season_query + season + TEXT( "&page=" ) + page ) ); if( j["data"].is_array() ) { auto epdata = j["data"].get< std::vector< json > >(); if( episodes.size() < epdata.size() ) episodes.resize( epdata.size() ); for ( auto &x : epdata ) { if( x["episodeName"].is_string() ) { if( dvd ) { size_t index = x["dvdEpisodeNumber"].get< size_t >(); if( index > episodes.size() ) episodes.resize( index ); index--; episodes[index] = toString( x["episodeName"].get< std::string >() ); } else { size_t index = x["airedEpisodeNumber"].get< size_t >(); if( index > episodes.size() ) episodes.resize( index ); index--; episodes[index] = toString( x["episodeName"].get< std::string >() ); // some eps have whitespace at the end while( isspace( episodes[index].back() ) ) episodes[index].pop_back(); } } } } else { cerr << "Couldn't find episode names for season " << season << " of show " << showNameFromId( id, language ) << std::endl; } if( j["links"]["next"].is_null() ) break; page = toString( std::to_string( j["links"]["next"].get< size_t >() ) ); } while( 1 ); r.clearHeader(); return episodes; } std::vector< std::pair< std::pair< int, string >, std::pair< string, string > > > getRenamedFiles( const string &show, int season, const string id, const string &language, const string &pattern, const bool &linux, 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 {}; std::vector< std::pair< std::pair< int, string >, std::pair< string, string > > > renamed_files; for ( const auto &x : files ) { auto last = x.second.find_last_of( 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( "." ) ); // get desired filename auto name = compilePattern( pattern, season, x.first, og_name.substr( 0, pos ), episodes[ep_num], show ) + og_name.substr( pos ); // replace '/' with '|' for ( size_t i = 0; i < name.size(); i++ ) { if ( name[i] == '/' ) { name[i] = '|'; } } // replace characters illegal in windows if desired if ( !linux ) { 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[i] = 'i'; name.insert( i + 1, TEXT( "s less than" ) ); max += 11; } else if ( name[i] == '>' ) { name[i] = 'i'; name.insert( i + 1, TEXT( "s more than" ) ); max += 11; } else if ( name[i] == ':' ) { name[i] = ' '; name.insert( i + 1, 1, '-' ); max++; } } for ( size_t i = 0; i < max; i++ ) { if ( name[i] == ' ' && name[i + 1] == ' ' ) { name.erase( i, 1 ); max--; i--; } } } renamed_files.emplace_back( std::pair< int, string >( x.first, dir ), std::pair< string, string >( og_name, name ) ); } } return renamed_files; } void singleSeason( const string &path, string &show, int season, string id, const string &language, const string &pattern, const bool &linux, const bool &trust, std::map< int, string > *files_ptr, bool print, bool dvd ) { if ( id.empty() ) id = getShowId( show, language ); 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 << "Couldn't find episodes with season " << season << std::endl; return; } auto renamed_files = getRenamedFiles( show, season, id, language, pattern, linux, *files_ptr, dvd ); if( print || !trust ) { for ( auto renamed = renamed_files.begin(); renamed != renamed_files.end(); ++renamed ) { cout << renamed->second.first << " --> " << renamed->second.second << std::endl; } if ( !trust ) { cout << "Does this seem ok? (y/n) "; string response; cin >> response; cin.clear(); cin.ignore( 1, '\n' ); if ( response[0] != 'y' && response[0] != 'Y' ) return; } } for ( auto renamed = renamed_files.begin(); renamed != renamed_files.end(); ++renamed ) { FSLib::rename( renamed->first.second + dir_divider + renamed->second.first, renamed->first.second + dir_divider + renamed->second.second ); if( found_files == nullptr ) { files_ptr[0][renamed->first.first] = renamed->first.second + dir_divider + renamed->second.second; } } if ( found_files != nullptr ) { delete found_files; } } void singleSeason( const string &path, 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, 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, flags & TV_DVD ); } } } void allSeasons( const string &path, 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, flags & TV_DVD ); } } std::vector< std::pair< string, string > > getLangs() { std::vector< std::pair< string, string > > langs; r.addHeader( TEXT( "Accept: application/json" ) ); r.addHeader( TEXT( "Authorization: Bearer " ) + api_token ); auto j = json::parse( r.get( TEXT( "/languages" ) ) ); r.clearHeader(); auto langs_json = j["data"].get< std::vector< json > >(); for ( auto &x : langs_json ) { langs.emplace_back( toString( x["abbreviation"].get< std::string >() ), toString( x["name"].get< std::string >() ) ); } return langs; } void printLangs() { for ( auto &x : getLangs() ) cout << x.first << " - " << x.second << std::endl; } bool findLanguage( const char_t *language ) { for ( auto &x : getLangs() ) { if ( x.first == language ) return true; } return false; } bool authenticate( const std::string &api_key ) { #ifdef _WIN32 r.setServer( TEXT( "api.thetvdb.com" ) ); #else r.setServer( "https://api.thetvdb.com" ); #endif r.addHeader( TEXT( "Accept: application/json" ) ); r.addHeader( TEXT( "Content-Type: application/json" ) ); auto j = json::parse( r.post( TEXT( "/login" ), "{ \"apikey\": \"" + api_key + "\" }" ) ); api_token = toString( j["token"].get< std::string >() ); r.clearHeader(); // TODO check return code return true; } #endif