From 73d1a024f3cd4d442f35f49a7c0af67e3e3190f0 Mon Sep 17 00:00:00 2001 From: oxixes Date: Sat, 22 Apr 2023 13:20:10 +0200 Subject: [PATCH 1/6] Added channels whitelist --- config.json.template | 3 +- iSponsorBlockTV/api_helpers.py | 34 +++++++++++++++++- iSponsorBlockTV/config_setup.py | 63 ++++++++++++++++++++++++++++----- iSponsorBlockTV/helpers.py | 4 +-- iSponsorBlockTV/main.py | 19 +++++----- 5 files changed, 103 insertions(+), 20 deletions(-) diff --git a/config.json.template b/config.json.template index c81e321..f5d303c 100644 --- a/config.json.template +++ b/config.json.template @@ -3,5 +3,6 @@ {"identifier": "", "airplay_credentials": ""} ], "apikey":"", - "skip_categories": ["sponsor"] + "skip_categories": ["sponsor"], + "channel_whitelist": [] } \ No newline at end of file diff --git a/iSponsorBlockTV/api_helpers.py b/iSponsorBlockTV/api_helpers.py index ae05041..6f7f5a1 100644 --- a/iSponsorBlockTV/api_helpers.py +++ b/iSponsorBlockTV/api_helpers.py @@ -21,13 +21,45 @@ async def get_vid_id(title, artist, api_key, web_session): url = constants.Youtube_api + "search" async with web_session.get(url, params=params) as resp: data = await resp.json() + + if "error" in data: + return + for i in data["items"]: + if (i["id"]["kind"] != "youtube#video"): + continue title_api = html.unescape(i["snippet"]["title"]) artist_api = html.unescape(i["snippet"]["channelTitle"]) if title_api == title and artist_api == artist: - return i["id"]["videoId"] + return (i["id"]["videoId"], i["snippet"]["channelId"]) return +@AsyncLRU(maxsize=10) +async def search_channels(channel, api_key, web_session): + channels = [] + params = {"q": channel, "key": api_key, "part": "snippet", "type": "channel", "maxResults": "5"} + url = constants.Youtube_api + "search" + async with web_session.get(url, params=params) as resp: + data = await resp.json() + + if "error" in data: + return channels + + for i in data["items"]: + # Get channel subcription number + params = {"id": i["snippet"]["channelId"], "key": api_key, "part": "statistics"} + url = constants.Youtube_api + "channels" + async with web_session.get(url, params=params) as resp: + channelData = await resp.json() + + if channelData["items"][0]["statistics"]["hiddenSubscriberCount"]: + subCount = "Hidden" + else: + subCount = channelData["items"][0]["statistics"]["subscriberCount"] + + channel.append((i["snippet"]["channelId"], i["snippet"]["channelTitle"], subCount)) + + return channels @listToTuple @AsyncTTL(time_to_live=300, maxsize=5) diff --git a/iSponsorBlockTV/config_setup.py b/iSponsorBlockTV/config_setup.py index ac268f3..e462230 100644 --- a/iSponsorBlockTV/config_setup.py +++ b/iSponsorBlockTV/config_setup.py @@ -3,7 +3,9 @@ import json import asyncio from pyatv.const import DeviceModel import sys - +import aiohttp +import asyncio +from . import api_helpers def save_config(config, config_file): with open(config_file, "w") as f: @@ -74,9 +76,9 @@ def main(config, config_file, debug): try: for i in atvs: config["atvs"].append(i) - print("done adding") + print("Done adding") except: - print("rewriting atvs (don't worry if none were saved before)") + print("Rewriting atvs (don't worry if none were saved before)") config["atvs"] = atvs try: @@ -84,12 +86,12 @@ def main(config, config_file, debug): except: apikey = "" if apikey != "": - if input("Apikey already specified. Change it? (y/n) ") == "y": + if input("API key already specified. Change it? (y/n) ") == "y": apikey = input("Enter your API key: ") config["apikey"] = apikey else: print( - "get youtube apikey here: https://developers.google.com/youtube/registering_an_application" + "Get youtube apikey here: https://developers.google.com/youtube/registering_an_application" ) apikey = input("Enter your API key: ") config["apikey"] = apikey @@ -108,10 +110,55 @@ def main(config, config_file, debug): skip_categories = [x for x in skip_categories if x != ''] # Remove empty strings else: categories = input( - "Enter skip categories (space sepparated) Options: [sponsor, selfpromo, exclusive_access, interaction, poi_highlight, intro, outro, preview, filler, music_offtopic:\n" + "Enter skip categories (space or comma sepparated) Options: [sponsor, selfpromo, exclusive_access, interaction, poi_highlight, intro, outro, preview, filler, music_offtopic:\n" ) - skip_categories = categories.split(" ") + skip_categories = categories.replace(",", " ").split(" ") + skip_categories = [x for x in skip_categories if x != ''] # Remove empty strings config["skip_categories"] = skip_categories - print("config finished") + try: + channel_whitelist = config["channel_whitelist"] + except: + channel_whitelist = [] + + if channel_whitelist != []: + if input("Do you want to whitelist any channels from being ad-blocked? (y/n) ") == "y": + web_session = aiohttp.ClientSession() + while True: + channel_info = {} + channel = input("Enter a channel name or \"/exit to exit\": ") + if channel == "/exit": + break + + results = asyncio.run(api_helpers.search_channels(channel, apikey, web_session)) + if len(results) == 0: + print("No channels found") + continue + + for i in range(len(results)): + print(f"{i}: {results[i][1]} - Subs: {results[i][2]}") + print("5: Enter a custom channel ID") + print("6: Go back") + + choice = -1 + choice = input("Select one option of the above [0-6]: ") + while choice not in [str(x) for x in range(7)]: + print("Invalid choice") + choice = input("Select one option of the above [0-6]: ") + + if choice == "5": + channel_info["id"] = input("Enter a channel ID: ") + channel_info["name"] = input("Enter the channel name: ") + channel_whitelist.append(channel_info) + continue + elif choice == "6": + continue + + channel_info["id"] = results[int(choice)][0] + channel_info["name"] = results[int(choice)][1] + channel_whitelist.append(channel_info) + + config["channel_whitelist"] = channel_whitelist + + print("Config finished") save_config(config, config_file) diff --git a/iSponsorBlockTV/helpers.py b/iSponsorBlockTV/helpers.py index 737c3de..daf6cd8 100644 --- a/iSponsorBlockTV/helpers.py +++ b/iSponsorBlockTV/helpers.py @@ -46,10 +46,10 @@ def app_start(): else: try: # Check if config file has the correct structure - config["atvs"], config["apikey"], config["skip_categories"] + config["atvs"], config["apikey"], config["skip_categories"], config["channel_whitelist"] except: # If not, ask to setup the program print("invalid config file, please run with --setup") sys.exit() main.main( - config["atvs"], config["apikey"], config["skip_categories"], args.debug + config["atvs"], config["apikey"], config["skip_categories"], config["channel_whitelist"], args.debug ) diff --git a/iSponsorBlockTV/main.py b/iSponsorBlockTV/main.py index 2e63280..feafef5 100644 --- a/iSponsorBlockTV/main.py +++ b/iSponsorBlockTV/main.py @@ -13,12 +13,14 @@ class MyPushListener(pyatv.interface.PushListener): web_session = None categories = ["sponsor"] + whitelist = [] - def __init__(self, apikey, atv, categories, web_session): + def __init__(self, apikey, atv, categories, whitelist, web_session): self.apikey = apikey self.rc = atv.remote_control self.web_session = web_session self.categories = categories + self.whitelist = whitelist self.atv = atv def playstatus_update(self, updater, playstatus): @@ -37,6 +39,7 @@ class MyPushListener(pyatv.interface.PushListener): self.categories, self.atv, time_start, + self.whitelist ) ) @@ -46,7 +49,7 @@ class MyPushListener(pyatv.interface.PushListener): async def process_playstatus( - playstatus, apikey, rc, web_session, categories, atv, time_start + playstatus, apikey, rc, web_session, categories, atv, time_start, whitelist ): logging.debug("App playing is:" + str(atv.metadata.app.identifier)) if ( @@ -57,7 +60,7 @@ async def process_playstatus( playstatus.title, playstatus.artist, apikey, web_session ) if vid_id: - print(vid_id) + print(f"ID: {vid_id[0]}, Channel ID: {vid_id[1]}") segments = await api_helpers.get_segments(vid_id, web_session, categories) print(segments) await time_to_segment( @@ -104,12 +107,12 @@ async def connect_atv(loop, identifier, airplay_credentials): return await pyatv.connect(config, loop) -async def loop_atv(event_loop, atv_config, apikey, categories, web_session): +async def loop_atv(event_loop, atv_config, apikey, categories, whitelist, web_session): identifier = atv_config["identifier"] airplay_credentials = atv_config["airplay_credentials"] atv = await connect_atv(event_loop, identifier, airplay_credentials) if atv: - listener = MyPushListener(apikey, atv, categories, web_session) + listener = MyPushListener(apikey, atv, categories, whitelist, web_session) atv.push_updater.listener = listener atv.push_updater.start() @@ -123,19 +126,19 @@ async def loop_atv(event_loop, atv_config, apikey, categories, web_session): # reconnect to apple tv atv = await connect_atv(event_loop, identifier, airplay_credentials) if atv: - listener = MyPushListener(apikey, atv, categories, web_session) + listener = MyPushListener(apikey, atv, categories, whitelist, web_session) atv.push_updater.listener = listener atv.push_updater.start() print("Push updater started") -def main(atv_configs, apikey, categories, debug): +def main(atv_configs, apikey, categories, whitelist, debug): loop = asyncio.get_event_loop_policy().get_event_loop() if debug: loop.set_debug(True) asyncio.set_event_loop(loop) web_session = aiohttp.ClientSession() for i in atv_configs: - loop.create_task(loop_atv(loop, i, apikey, categories, web_session)) + loop.create_task(loop_atv(loop, i, apikey, categories, whitelist, web_session)) loop.run_forever() From 975b2690a41fca8537ac3b4687713d11e356b931 Mon Sep 17 00:00:00 2001 From: oxixes Date: Sat, 22 Apr 2023 13:40:11 +0200 Subject: [PATCH 2/6] Added channel whitelist --- iSponsorBlockTV/main.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/iSponsorBlockTV/main.py b/iSponsorBlockTV/main.py index feafef5..dd09c69 100644 --- a/iSponsorBlockTV/main.py +++ b/iSponsorBlockTV/main.py @@ -61,6 +61,9 @@ async def process_playstatus( ) if vid_id: print(f"ID: {vid_id[0]}, Channel ID: {vid_id[1]}") + for i in whitelist: + if vid_id[1] == i["id"]: + return segments = await api_helpers.get_segments(vid_id, web_session, categories) print(segments) await time_to_segment( From 3f8e24c8a45a78acb337bf38f641ca3de582d486 Mon Sep 17 00:00:00 2001 From: oxixes Date: Sat, 22 Apr 2023 13:55:32 +0200 Subject: [PATCH 3/6] Improved README --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 226ad5b..29e0c26 100644 --- a/README.md +++ b/README.md @@ -6,22 +6,22 @@ This project is written in asycronous python and should be pretty quick. # Installation Check the [wiki](https://github.com/dmunozv04/iSponsorBlockTV/wiki/Installation) -Warning: armv7 builds have been deprecated + +Warning: armv7 builds have been deprecated. # Usage -Run iSponsorBLockTV in the same network as the Apple TV. +Run iSponsorBLockTV on the same network as the Apple TV. -It connect to the Apple TV, watch its activity and skip any sponsor segment using the [SponsorBlock](https://sponsor.ajay.app/) API. +It connects to the Apple TV, watches its activity and skips any sponsor segment using the [SponsorBlock](https://sponsor.ajay.app/) API. The last 5 videos' segments are cached to limit the number on queries on SponsorBlock and YouTube. # Libraries used - [pyatv](https://github.com/postlund/pyatv) Used to connect to the Apple TV -- [asyncio] and [aiohttp] -- [async_lru] -- [json] +- asyncio and [aiohttp](https://github.com/aio-libs/aiohttp) +- [async-cache](https://github.com/iamsinghrajat/async-cache) # Projects using this proect - [Home Assistant Addon](https://github.com/bertybuttface/addons/tree/main/isponsorblocktv) @@ -38,5 +38,6 @@ The last 5 videos' segments are cached to limit the number on queries on Sponsor - [dmunozv04](https://github.com/dmunozv04) - creator and maintainer - [HaltCatchFire](https://github.com/HaltCatchFire) - updated dependencies and improved skip logic +- [Oxixes](https://github.com/oxixes) - added support for channel whitelist and minor improvements # License [![GNU GPLv3](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html) From 224d00174b3cf641aee754382e68dadded688d63 Mon Sep 17 00:00:00 2001 From: oxixes Date: Sat, 22 Apr 2023 19:09:37 +0200 Subject: [PATCH 4/6] I'm dumb, whatever, fixed --- iSponsorBlockTV/config_setup.py | 61 ++++++++++++++++----------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/iSponsorBlockTV/config_setup.py b/iSponsorBlockTV/config_setup.py index e462230..c0a8749 100644 --- a/iSponsorBlockTV/config_setup.py +++ b/iSponsorBlockTV/config_setup.py @@ -121,42 +121,41 @@ def main(config, config_file, debug): except: channel_whitelist = [] - if channel_whitelist != []: - if input("Do you want to whitelist any channels from being ad-blocked? (y/n) ") == "y": - web_session = aiohttp.ClientSession() - while True: - channel_info = {} - channel = input("Enter a channel name or \"/exit to exit\": ") - if channel == "/exit": - break + if input("Do you want to whitelist any channels from being ad-blocked? (y/n) ") == "y": + web_session = aiohttp.ClientSession() + while True: + channel_info = {} + channel = input("Enter a channel name or \"/exit to exit\": ") + if channel == "/exit": + break - results = asyncio.run(api_helpers.search_channels(channel, apikey, web_session)) - if len(results) == 0: - print("No channels found") - continue - - for i in range(len(results)): - print(f"{i}: {results[i][1]} - Subs: {results[i][2]}") - print("5: Enter a custom channel ID") - print("6: Go back") + results = asyncio.run(api_helpers.search_channels(channel, apikey, web_session)) + if len(results) == 0: + print("No channels found") + continue + + for i in range(len(results)): + print(f"{i}: {results[i][1]} - Subs: {results[i][2]}") + print("5: Enter a custom channel ID") + print("6: Go back") - choice = -1 + choice = -1 + choice = input("Select one option of the above [0-6]: ") + while choice not in [str(x) for x in range(7)]: + print("Invalid choice") choice = input("Select one option of the above [0-6]: ") - while choice not in [str(x) for x in range(7)]: - print("Invalid choice") - choice = input("Select one option of the above [0-6]: ") - if choice == "5": - channel_info["id"] = input("Enter a channel ID: ") - channel_info["name"] = input("Enter the channel name: ") - channel_whitelist.append(channel_info) - continue - elif choice == "6": - continue - - channel_info["id"] = results[int(choice)][0] - channel_info["name"] = results[int(choice)][1] + if choice == "5": + channel_info["id"] = input("Enter a channel ID: ") + channel_info["name"] = input("Enter the channel name: ") channel_whitelist.append(channel_info) + continue + elif choice == "6": + continue + + channel_info["id"] = results[int(choice)][0] + channel_info["name"] = results[int(choice)][1] + channel_whitelist.append(channel_info) config["channel_whitelist"] = channel_whitelist From 890c3956deac551d491fd400f020bd17d580341d Mon Sep 17 00:00:00 2001 From: oxixes Date: Sat, 22 Apr 2023 19:15:36 +0200 Subject: [PATCH 5/6] Remove .DS_Store --- .DS_Store | Bin 6148 -> 0 bytes .gitignore | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 0c99c40b874154499ee89f7808d560af32f79dc5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}(Pm5FSHV+#*B|NXyX|ZapMr#j1yuqQD-wAqBw!5G15pBC>JSq+2beD6au; z#1rs3%-9CCl+_+Us34Ct{w8CO)A{0`4gj#ApLhTb0N|*EP92*Ug!)ORWX^aN5t*7J zi3deD&SX3_);cT$mVw!0fcEYJ#L$NnG8o^#cX2mMi_&%P+1$Ld@MV$nIYr9SHt@VA>6vK9l?2XOQsK&ppdp~wgfB(H4UX8AAG^-4cASEjX zAK?~_u@wJ=y*!ioFZ5D`0|};HKsk1F%|z<1*A$-Hes$_k2`0r^7US2M$od%@E67C% zU67C?ClY!P;Ifc#1{bgg`!mK-YZI5mM(gz){U_~bBt0&ykB(-;@THHQi2ePR0n5Of zGeGBqKqd4IRvOjOfsHBw5FK1}lv^ z9GEFSn0YfZ6$(>t$N7aC4$RkRTg!lDV3L7({n()U|L4E!|H&ZxWErpwd@2T*vmI^+ zn3B0$r;?+)R-m4ulF+zHqYA;sT*u0wt9T7n3HAvx5PgG{M)aWAKLUyd+gJwPm4OGy C|Kl$J diff --git a/.gitignore b/.gitignore index a060794..75c1fae 100644 --- a/.gitignore +++ b/.gitignore @@ -155,4 +155,6 @@ cython_debug/ #config folder config/ -config.json \ No newline at end of file +config.json + +.DS_Store \ No newline at end of file From 42c09ef588e6aa274134024f20f70b536a40c343 Mon Sep 17 00:00:00 2001 From: oxixes Date: Sat, 22 Apr 2023 19:50:37 +0200 Subject: [PATCH 6/6] Last fixes --- iSponsorBlockTV/api_helpers.py | 2 +- iSponsorBlockTV/config_setup.py | 8 ++++++-- iSponsorBlockTV/main.py | 3 ++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/iSponsorBlockTV/api_helpers.py b/iSponsorBlockTV/api_helpers.py index 6f7f5a1..81d06ce 100644 --- a/iSponsorBlockTV/api_helpers.py +++ b/iSponsorBlockTV/api_helpers.py @@ -57,7 +57,7 @@ async def search_channels(channel, api_key, web_session): else: subCount = channelData["items"][0]["statistics"]["subscriberCount"] - channel.append((i["snippet"]["channelId"], i["snippet"]["channelTitle"], subCount)) + channels.append((i["snippet"]["channelId"], i["snippet"]["channelTitle"], subCount)) return channels diff --git a/iSponsorBlockTV/config_setup.py b/iSponsorBlockTV/config_setup.py index c0a8749..ece5ed5 100644 --- a/iSponsorBlockTV/config_setup.py +++ b/iSponsorBlockTV/config_setup.py @@ -125,11 +125,13 @@ def main(config, config_file, debug): web_session = aiohttp.ClientSession() while True: channel_info = {} - channel = input("Enter a channel name or \"/exit to exit\": ") + channel = input("Enter a channel name or \"/exit\" to exit: ") if channel == "/exit": break - results = asyncio.run(api_helpers.search_channels(channel, apikey, web_session)) + task = loop.create_task(api_helpers.search_channels(channel, apikey, web_session)) + loop.run_until_complete(task) + results = task.result() if len(results) == 0: print("No channels found") continue @@ -156,6 +158,8 @@ def main(config, config_file, debug): channel_info["id"] = results[int(choice)][0] channel_info["name"] = results[int(choice)][1] channel_whitelist.append(channel_info) + # Close web session asynchronously + loop.run_until_complete(web_session.close()) config["channel_whitelist"] = channel_whitelist diff --git a/iSponsorBlockTV/main.py b/iSponsorBlockTV/main.py index dd09c69..439716c 100644 --- a/iSponsorBlockTV/main.py +++ b/iSponsorBlockTV/main.py @@ -63,8 +63,9 @@ async def process_playstatus( print(f"ID: {vid_id[0]}, Channel ID: {vid_id[1]}") for i in whitelist: if vid_id[1] == i["id"]: + print("Channel whitelisted, skipping.") return - segments = await api_helpers.get_segments(vid_id, web_session, categories) + segments = await api_helpers.get_segments(vid_id[0], web_session, categories) print(segments) await time_to_segment( segments, playstatus.position, rc, time_start, web_session