def makeInfoResponse(erinaSearchResponse): """ Makes the response for info queries on Line """ if isAnError(erinaSearchResponse): return "Sorry an error occured while searching for your anime" return """Anime: {anime} Season: {season}{year} Number of episodes: {episodes} Average Duration: {duration}min Status: {status} Genres: {genres} Studio: {studios} {description} {link} """.format( anime=(str(erinaSearchResponse.title) if erinaSearchResponse.title is not None else "Unknown"), season=(str(erinaSearchResponse.season) if erinaSearchResponse.season is not None else (str(erinaSearchResponse.year) if erinaSearchResponse.year is not None else "N/A")), year=(("of " + str(erinaSearchResponse.year) if erinaSearchResponse.year is not None else "") if erinaSearchResponse.season is None else ""), episodes=(str(erinaSearchResponse.number_of_episodes) if erinaSearchResponse.number_of_episodes is not None else "??"), duration=(str(erinaSearchResponse.episode_duration) if erinaSearchResponse.episode_duration is not None else "??"), status=(str(erinaSearchResponse.status) if erinaSearchResponse.status is not None else "Unknown"), genres=(str(erinaSearchResponse.genres)), studios=(create_nice_list([studio for studio in erinaSearchResponse.studios if studio.is_animation_studio]) if erinaSearchResponse.studios is not None else "Unknown"), description=(str(erinaSearchResponse.description) if len(str(erinaSearchResponse.description)) <= 200 else str(erinaSearchResponse.description)[:177] + "..."), link=(str(erinaSearchResponse.link) if erinaSearchResponse.link is not None else "") )
def makeDescriptionResponse(erinaSearchResponse): """ Makes the response for description queries on Line """ if isAnError(erinaSearchResponse): return "Sorry an error occured while searching for your anime" limit = 1020 - len(str(erinaSearchResponse.link)) return """{description} {link} """.format( description=(str(erinaSearchResponse.description) if len(str(erinaSearchResponse.description)) <= limit else str(erinaSearchResponse.description)[:limit - 3] + "..."), link=(str(erinaSearchResponse.link) if erinaSearchResponse.link is not None else "") )
def on_direct_message(message): """ DM Receiving """ directMessagesHistory.append(message) log("ErinaTwitter", "New direct message from @" + str(message.message_create['sender_id'])) if Twitter.dmAskingForSauce(message): StatsAppend(TwitterStats.directMessagingHit, str(message.message_create['sender_id'])) image = Twitter.getDirectMedia(message) if image is not None: searchResult = imageSearch(image) ErinaTwitter.dm(makeImageResponse(searchResult), message.message_create['sender_id']) elif isAnError(image): ErinaTwitter.dm( "An error occured while retrieving information on the anime", message.message_create['sender_id']) else: ErinaTwitter.dm( "You did not send any image along with your message", message.message_create['sender_id'])
def ErinaServer_Endpoint_API_search(): cooldown = None try: if not ServerConfig.public_api: if "key" not in request.values: return makeResponse( request_args=request.values, cooldown=None, code=400, error="NO_KEY", error_message= "This API is not public and no key got provided along with the request" ) else: currentKey = request.values.get("key") if not isfile(erina_dir + "/ErinaServer/Erina/auth/apiAuth/" + currentKey + ".erina"): return makeResponse( request_args=request.values, cooldown=None, code=401, error="WRONG_KEY", error_message="The given key isn't registered") else: currentAuth = authReader.APIAuth(currentKey) currentAuth.authFile.append(str(time()) + "\n") if currentKey in rate_limiting_api_map: rate = time() - rate_limiting_api_map[currentKey] if rate > currentAuth.rate_limit: rate_limiting_api_map[currentKey] = time() else: return makeResponse( request_args=request.values, cooldown=currentAuth.rate_limit - rate, code=429, error="RATE_LIMITED", error_message= "You have exceeded your rate limit") else: rate_limiting_api_map[currentKey] = time() cooldown = currentAuth.rate_limit if "format" in request.values: format = request.values.get("format").lower() else: format = "json" if "anilistID" in request.values: StatsAppend( APIStats.searchEndpointCall, f"AniListID >>> {str(request.values.get('anilistID'))}") result = erinasearch.anilistIDSearch( request.values.get("anilistID")) if "client" in request.values: if request.values.get("client") == "line": return makeResponse( request_args=request.values, cooldown=cooldown, data=LineParser.makeInfoResponse(result)) elif request.values.get("client") == "discord": return makeResponse( request_args=request.values, cooldown=cooldown, data=DiscordParser.makeInfoResponse(result)[2]) elif "anime" in request.values: StatsAppend( APIStats.searchEndpointCall, f"Anime Search >>> {str(request.values.get('anime'))}") result = erinasearch.searchAnime(request.values.get("anime")) if "client" in request.values: if request.values.get("client") == "line": return makeResponse( request_args=request.values, cooldown=cooldown, data=LineParser.makeInfoResponse(result)) elif request.values.get("client") == "discord": return makeResponse( request_args=request.values, cooldown=cooldown, data=DiscordParser.makeInfoResponse(result)[2]) elif "image" in request.values: StatsAppend(APIStats.searchEndpointCall, "Image Search") result = erinasearch.imageSearch(request.values.get("image")) if "client" in request.values: if request.values.get("client") == "twitter": return makeResponse(request_args=request.values, cooldown=cooldown, data=TwitterParser.makeTweet(result)) elif request.values.get("client") == "line": return makeResponse( request_args=request.values, cooldown=cooldown, data=LineParser.makeImageResponse(result)) elif request.values.get("client") == "discord": return makeResponse( request_args=request.values, cooldown=cooldown, data=DiscordParser.makeImageResponse(result)[2]) else: return makeResponse( request_args=request.values, cooldown=cooldown, data={ "authorizedArgs": [ "anilistID", "anime", "image", "minify", "client", "format" ], "optionalArgs": ["minify", "client", "format"] }, code=400, error="MISSING_ARG", error_message="An argument is missing from your request") if not isAnError(result): if format == "text" or format == "html": return makeResponse(request_args=request.values, cooldown=cooldown, data=result.as_text()) else: return makeResponse(request_args=request.values, cooldown=cooldown, data=result.as_dict()) else: if result.type == "ANILIST_NOT_FOUND": return makeResponse( request_args=request.values, cooldown=cooldown, code=404, data={ "error": result.type, "message": result.message, "timestamp": result.timestamp, "formattedTimestamp": result.formatted_timestamp }, error="ANILIST_NOT_FOUND", error_message="AniList could not find your anime") return makeResponse( request_args=request.values, cooldown=cooldown, data={ "error": result.type, "message": result.message, "timestamp": result.timestamp, "formattedTimestamp": result.formatted_timestamp }, code=500, error=result.type, error_message= "An error occured while retrieving the information") except: traceback.print_exc() return makeResponse(request_args=request.values, cooldown=cooldown, code=500, error=str(exc_info()[0]))
def makeTweet(erinaSearchResponse): """ Formats ErinaSearch's response for Twitter """ if isAnError(erinaSearchResponse) or isAnError( erinaSearchResponse.detectionResult) or isAnError( erinaSearchResponse.animeResult): return None elif erinaSearchResponse.low_similarity: return None else: tweetResult = "" animeResult = erinaSearchResponse.animeResult detectionResult = erinaSearchResponse.detectionResult if animeResult is not None: # If it is an anime episode = "?" if isinstance(detectionResult, SauceNAOCache) and detectionResult.part is not None: episode = detectionResult.part elif detectionResult.episode is not None: episode = detectionResult.episode tweetResult = """Here is the sauce! Anime: {anime} Episode: {episode}/{episodes} {timing} Studio: {studios} Genres: {genres} {link} {description} """.format(anime=(str(animeResult.title) if animeResult.title is not None else "Unknown"), episode=str(episode), episodes=(str(animeResult.number_of_episodes) if animeResult.number_of_episodes is not None else "?"), timing=(('(at around ' + str(detectionResult.timing) + ')') if detectionResult.timing is not None else ''), studios=(create_nice_list([ studio for studio in animeResult.studios if studio.is_animation_studio ]) if animeResult.studios is not None else "Unknown"), genres=((str(animeResult.genres)) if animeResult.genres is not None else "Unknown"), link=((str(animeResult.link)) if animeResult.link is not None else ""), description=((str(animeResult.description)) if animeResult.description is not None else "")) elif isinstance( detectionResult, SauceNAOCache ) and detectionResult.is_manga: # if it comes from SauceNAO and is a manga tweetResult = """Here is the sauce! Manga: {manga} Author: {author} Chapter: {chapter} Similarity: {similarity}% {link} """.format(manga=((str(detectionResult.title)) if detectionResult.title is not None else "Unknown"), author=((str(detectionResult.author)) if detectionResult.author is not None else "Unknown"), chapter=((str(detectionResult.part)) if detectionResult.part is not None else "??"), similarity=((str(round(detectionResult.similarity, 2))) if detectionResult.similarity is not None else "N/A"), link=((str(detectionResult.link)) if detectionResult.link is not None else "")) else: tweetResult = """Here is the sauce! Title: {title} Author: {author} Database: {database} Similarity: {similarity}% {link} """.format(title=((str(detectionResult.title)) if detectionResult.title is not None else "Unknown"), author=((str(detectionResult.author)) if detectionResult.author is not None else "Unknown"), database=((str(detectionResult.database)) if detectionResult.database is not None else "Unknown"), similarity=((str(round(detectionResult.similarity, 2))) if detectionResult.similarity is not None else "N/A"), link=((str(detectionResult.link)) if detectionResult.link is not None else "")) if len(tweetResult) >= 280: tweetResult = tweetResult[:277] + "..." return tweetResult
def makeImageResponse(erinaSearchResponse): """ Makes the response for image queries on Discord """ if isAnError(erinaSearchResponse) or isAnError( erinaSearchResponse.detectionResult) or isAnError( erinaSearchResponse.animeResult): return None, None, None else: discordResult = "" if erinaSearchResponse.low_similarity: discordResult = "⚠️The similarity seems low\n" animeResult = erinaSearchResponse.animeResult detectionResult = erinaSearchResponse.detectionResult if animeResult is not None: # If it is an anime episode = "?" if isinstance(detectionResult, SauceNAOCache) and detectionResult.part is not None: episode = detectionResult.part elif detectionResult.episode is not None: episode = detectionResult.episode discordResult = """Here is the sauce! **Anime**: {anime} **Episode**: {episode}/{episodes} {timestamp} **Studio**: {studios} **Genres**: {genres} **Similarity**: {similarity}% {link} {description} """.format(anime=(str(animeResult.title) if animeResult.title is not None else "Unknown"), episode=str(episode), episodes=(str(animeResult.number_of_episodes) if animeResult.number_of_episodes is not None else "?"), timestamp=(('(at around ' + str(detectionResult.timing) + ')') if detectionResult.timing is not None else ''), studios=(create_nice_list([ studio for studio in animeResult.studios if studio.is_animation_studio ]) if animeResult.studios is not None else "Unknown"), genres=((str(animeResult.genres)) if animeResult.genres is not None else "Unknown"), similarity=((str(round(detectionResult.similarity, 2))) if detectionResult.similarity is not None else "N/A"), link=((str(animeResult.link)) if animeResult.link is not None else ""), description=((str(animeResult.description)) if animeResult.description is not None else "")) elif isinstance(detectionResult, SauceNAOCache): # if it comes from SauceNAO if detectionResult.is_manga: # if it is a manga discordResult = """Here is the sauce! **Manga**: {manga} **Author**: {author} **Chapter**: {chapter} **Similarity**: {similarity}% {link} """.format(manga=((str(detectionResult.title)) if detectionResult.title is not None else "Unknown"), author=((str(detectionResult.author)) if detectionResult.author is not None else "Unknown"), chapter=((str(detectionResult.part)) if detectionResult.part is not None else "??"), similarity=((str(round(detectionResult.similarity, 2))) if detectionResult.similarity is not None else "N/A"), link=((str(detectionResult.link)) if detectionResult.link is not None else "")) else: discordResult = """Here is the sauce! **Title**: {title} **Author**: {author} **Database**: {database} **Similarity**: {similarity}% {link} """.format(title=((str(detectionResult.title)) if detectionResult.title is not None else "Unknown"), author=((str(detectionResult.author)) if detectionResult.author is not None else "Unknown"), database=((str(detectionResult.database)) if detectionResult.database is not None else "Unknown"), similarity=((str(round(detectionResult.similarity, 2))) if detectionResult.similarity is not None else "N/A"), link=((str(detectionResult.link)) if detectionResult.link is not None else "")) if len(discordResult) >= 1000: discordResult = discordResult[:997] + "..." return str(detectionResult.title), (str( animeResult.cover_image) if animeResult.cover_image is not None else None), discordResult
def search_anime_by_hash(image_hash): """ Let you search through Erina's database and other database (Manami Projects' anime_offline_database and AniList API) with the hash of an image/scene from an anime (average hash/aHash from the image_hash python module)\n © Anime no Sekai - 2020 Project Erina """ if isAnError(image_hash): # If there is an error return image_hash ########################## # DATABASE SEARCH # ########################## def search_anime_in_erina_database(): if SearchConfig.thresholds.erina_similarity > 98.4375: for anime in os.listdir(erina_db_path): if anime in ['.DS_Store', ".gitkeep"]: continue else: for folder in os.listdir(erina_db_path + anime): if anime == '.DS_Store': continue if os.path.isfile(erina_db_path + anime + '/' + folder + '/' + str(image_hash) + '.erina'): StatsAppend(DatabaseStats.erinaDatabaseLookups, 1) return parser.ErinaFile( "erina_database", anime + '/' + folder + '/' + str(image_hash) + '.erina' ).content, 100, anime + '/' + folder + '/' + str( image_hash) + '.erina' else: distance_dict = {} iteration = 0 for anime in os.listdir(erina_db_path): if anime in ['.DS_Store', ".gitkeep"]: continue else: for folder in os.listdir(erina_db_path + anime): if folder == '.DS_Store': continue for file in os.listdir(erina_db_path + anime + '/' + folder): if file == '.DS_Store': continue iteration += 1 distance = hamming_distance( file.replace('.erina', ''), str(image_hash)) if distance == 1: return parser.ErinaFile( "erina_database", anime + '/' + folder + '/' + file).content, ( 1 - (1 / 64) ) * 100, anime + '/' + folder + '/' + str( image_hash) + '.erina' else: distance_dict[anime + '/' + folder + '/' + file] = distance StatsAppend(DatabaseStats.erinaDatabaseLookups, iteration) threshold = int( (SearchConfig.thresholds.erina_similarity * 64) / 100) similarities = list(range(2, len(list(range(threshold, 64))))) for distance in similarities: for element in distance_dict: if distance_dict[element] == distance: return parser.ErinaFile( "erina_database", element).content, ( 1 - (distance / 64) ) * 100, anime + '/' + folder + '/' + str( image_hash) + '.erina' return None, None, None ########################## # CACHE SEARCHING # ########################## def search_anime_in_erina_cache(): if os.path.isfile(f"{str(erina_dir)}/{str(image_hash)}.erina"): return parser.erina_parser.ErinaCache( TextFile(f"{str(erina_dir)}/{str(image_hash)}.erina").read()) return None def search_anime_in_tracemoe_cache(): if os.path.isfile(tracemoe_cache_path + str(image_hash) + '.erina'): return parser.tracemoe_parser.TraceMOECache( TextFile(tracemoe_cache_path + str(image_hash) + '.erina').read()) return None def search_anime_in_saucenao_cache(): if os.path.isfile(saucenao_cache_path + str(image_hash) + '.erina'): return parser.saucenao_parser.SauceNAOCache( TextFile(saucenao_cache_path + str(image_hash) + '.erina').read()) return None def search_anime_in_iqdb_cache(): if os.path.isfile(iqdb_cache_path + str(image_hash) + '.erina'): return parser.iqdb_parser.IQDBCache( TextFile(iqdb_cache_path + str(image_hash) + '.erina').read()) return None ########################## # SEARCHING # ########################## similaritiesDict = {} erina_cache_result = search_anime_in_erina_cache() if erina_cache_result is None or isAnError( erina_cache_result ) or erina_cache_result.similarity < SearchConfig.thresholds.erina_similarity: erina_database_result, erina_database_similarity, erina_database_path = search_anime_in_erina_database( ) if erina_database_result is None or isAnError( erina_database_result ) or erina_database_similarity < SearchConfig.thresholds.erina_similarity: tracemoe_cache_result = search_anime_in_tracemoe_cache() if tracemoe_cache_result is None or isAnError( tracemoe_cache_result) or ( tracemoe_cache_result.similarity if tracemoe_cache_result.similarity is not None else 0) < SearchConfig.thresholds.tracemoe_similarity: saucenao_cache_result = search_anime_in_saucenao_cache() if saucenao_cache_result is None or isAnError( saucenao_cache_result) or ( saucenao_cache_result.similarity if saucenao_cache_result.similarity is not None else 0) < SearchConfig.thresholds.saucenao_similarity: iqdb_cache_result = search_anime_in_iqdb_cache() if iqdb_cache_result is None or isAnError( iqdb_cache_result) or ( iqdb_cache_result.similarity if iqdb_cache_result.similarity is not None else 0) < SearchConfig.thresholds.iqdb_similarity: tracemoe_api_result = erinacache.tracemoe_caching( image_hash) if isAnError( tracemoe_api_result ) or tracemoe_api_result.similarity < SearchConfig.thresholds.tracemoe_similarity: if not isAnError( tracemoe_api_result ) and tracemoe_api_result.similarity is not None: similaritiesDict[ tracemoe_api_result] = tracemoe_api_result.similarity saucenao_api_result = erinacache.saucenao_caching( image_hash) if isAnError( saucenao_api_result ) or saucenao_api_result.similarity < SearchConfig.thresholds.saucenao_similarity: if not isAnError( saucenao_api_result ) and saucenao_api_result.similarity is not None: similaritiesDict[ saucenao_api_result] = saucenao_api_result.similarity iqdb_api_result = erinacache.iqdb_caching( image_hash) if isAnError( iqdb_api_result ) or iqdb_api_result.similarity < SearchConfig.thresholds.iqdb_similarity: if not isAnError( iqdb_api_result ) and iqdb_api_result.similarity is not None: similaritiesDict[ iqdb_api_result] = iqdb_api_result.similarity #### UNDER THE DEFINED SIMILARITY THRESHOLD if len(similaritiesDict) > 0: bestResult = max( similaritiesDict.items(), key=operator.itemgetter(1))[0] if isinstance(bestResult, TraceMOECache): return ImageSearchResult( bestResult, bestResult.similarity, anilist_id_search. search_anime_by_anilist_id( bestResult.anilist_id), low_similarity=True, image_hash=image_hash) elif isinstance( bestResult, SauceNAOCache): return ImageSearchResult( bestResult, bestResult.similarity, (title_search.searchAnime( bestResult.title) if bestResult.is_anime else None), low_similarity=True, image_hash=image_hash) elif isinstance(bestResult, IQDBCache): return ImageSearchResult( bestResult, bestResult.similarity, None, low_similarity=True, image_hash=image_hash) else: #### IF NO RESULT ARE LEFT return SearchingError( "NO_RESULT", "No result found.") else: return SearchingError( "NO_RESULT", "No result found.") else: # IF FOUND IN IQDB API return ImageSearchResult( iqdb_api_result, iqdb_api_result.similarity, None, image_hash=image_hash) else: # IF FOUND IN SAUCENAO API return ImageSearchResult( saucenao_api_result, saucenao_api_result.similarity, (title_search.searchAnime( saucenao_api_result.title) if saucenao_api_result.is_anime else None), image_hash=image_hash) else: # IF FOUND IN TRACEMOE API return ImageSearchResult( tracemoe_api_result, tracemoe_api_result.similarity, anilist_id_search.search_anime_by_anilist_id( tracemoe_api_result.anilist_id), image_hash=image_hash) else: # IF FOUND IN IQDB CACHE return ImageSearchResult(iqdb_cache_result, iqdb_cache_result.similarity, None, image_hash=image_hash) else: # IF FOUND IN SAUCE NAO CACHE return ImageSearchResult( saucenao_cache_result, saucenao_cache_result.similarity, (title_search.searchAnime(saucenao_cache_result.title) if saucenao_cache_result.is_anime else None), image_hash=image_hash) else: # IF FOUND IN TRACEMOE CACHE return ImageSearchResult( tracemoe_cache_result, tracemoe_cache_result.similarity, anilist_id_search.search_anime_by_anilist_id( tracemoe_cache_result.anilist_id), image_hash=image_hash) else: # IF FOUND IN ERINA DATABASE erinacache.erina_caching(str(image_hash), erina_database_path, erina_database_similarity, erina_database_result.anilist_id) return ImageSearchResult( erina_database_result, erina_database_similarity, anilist_id_search.search_anime_by_anilist_id( erina_database_result.anilist_id), image_hash=image_hash) else: # IF FOUND IN ERINA CACHE return ImageSearchResult(parser.ErinaFile("erina_database", erina_cache_result.path), erina_cache_result.similarity, anilist_id_search.search_anime_by_anilist_id( erina_cache_result.anilist_id), image_hash=image_hash) return None
def makeImageResponse(erinaSearchResponse): """ Makes the response for image queries on Line """ if isAnError(erinaSearchResponse) or isAnError(erinaSearchResponse.detectionResult) or isAnError(erinaSearchResponse.animeResult): return "Sorry an error occured while searching for your anime" else: lineResult = "" if erinaSearchResponse.low_similarity: lineResult = "⚠️The similarity seems low\n" animeResult = erinaSearchResponse.animeResult detectionResult = erinaSearchResponse.detectionResult if animeResult is not None: # If it is an anime episode = "?" if isinstance(detectionResult, SauceNAOCache) and detectionResult.part is not None: episode = detectionResult.part elif detectionResult.episode is not None: episode = detectionResult.episode lineResult = """Here is the sauce! Anime: {anime} Episode: {episode}/{episodes} {timestamp} Studio: {studios} Genres: {genres} Similarity: {similarity}% {link} {description} """.format( anime=(str(animeResult.title) if animeResult.title is not None else "Unknown"), episode=str(episode), episodes=(str(animeResult.number_of_episodes) if animeResult.number_of_episodes is not None else "?"), timestamp=(('(at around ' + str(detectionResult.timing) + ')') if detectionResult.timing is not None else ''), studios=(create_nice_list([studio for studio in animeResult.studios if studio.is_animation_studio]) if animeResult.studios is not None else "Unknown"), genres=((str(animeResult.genres)) if animeResult.genres is not None else "Unknown"), similarity=((str(round(detectionResult.similarity, 2))) if detectionResult.similarity is not None else "N/A"), link=((str(animeResult.link)) if animeResult.link is not None else ""), description=((str(animeResult.description)) if animeResult.description is not None else "") ) elif isinstance(detectionResult, SauceNAOCache): # if it comes from SauceNAO if detectionResult.is_manga: # if it is a manga lineResult = """Here is the sauce! Manga: {manga} Author: {author} Chapter: {chapter} Similarity: {similarity}% {link} """.format( manga=((str(detectionResult.title)) if detectionResult.title is not None else "Unknown"), author=((str(detectionResult.author)) if detectionResult.author is not None else "Unknown"), chapter=((str(detectionResult.part)) if detectionResult.part is not None else "??"), similarity=((str(round(detectionResult.similarity, 2))) if detectionResult.similarity is not None else "N/A"), link=((str(detectionResult.link)) if detectionResult.link is not None else "") ) else: lineResult = """Here is the sauce! Title: {title} Author: {author} Database: {database} Similarity: {similarity}% {link} """.format( title=((str(detectionResult.title)) if detectionResult.title is not None else "Unknown"), author=((str(detectionResult.author)) if detectionResult.author is not None else "Unknown"), database=((str(detectionResult.database)) if detectionResult.database is not None else "Unknown"), similarity=((str(round(detectionResult.similarity, 2))) if detectionResult.similarity is not None else "N/A"), link=((str(detectionResult.link)) if detectionResult.link is not None else "") ) if len(lineResult) >= 1000: lineResult = lineResult[:997] + "..." return lineResult