Merge pull request #44 from oxixes/channel_whitelist

Added channel whitelists and general fixes
This commit is contained in:
David
2023-04-22 19:56:44 +02:00
committed by GitHub
8 changed files with 121 additions and 28 deletions

BIN
.DS_Store vendored

Binary file not shown.

2
.gitignore vendored
View File

@@ -156,3 +156,5 @@ cython_debug/
#config folder #config folder
config/ config/
config.json config.json
.DS_Store

View File

@@ -6,22 +6,22 @@ This project is written in asycronous python and should be pretty quick.
# Installation # Installation
Check the [wiki](https://github.com/dmunozv04/iSponsorBlockTV/wiki/Installation) Check the [wiki](https://github.com/dmunozv04/iSponsorBlockTV/wiki/Installation)
Warning: armv7 builds have been deprecated
Warning: armv7 builds have been deprecated.
# Usage # 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. The last 5 videos' segments are cached to limit the number on queries on SponsorBlock and YouTube.
# Libraries used # Libraries used
- [pyatv](https://github.com/postlund/pyatv) Used to connect to the Apple TV - [pyatv](https://github.com/postlund/pyatv) Used to connect to the Apple TV
- [asyncio] and [aiohttp] - asyncio and [aiohttp](https://github.com/aio-libs/aiohttp)
- [async_lru] - [async-cache](https://github.com/iamsinghrajat/async-cache)
- [json]
# Projects using this proect # Projects using this proect
- [Home Assistant Addon](https://github.com/bertybuttface/addons/tree/main/isponsorblocktv) - [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 - [dmunozv04](https://github.com/dmunozv04) - creator and maintainer
- [HaltCatchFire](https://github.com/HaltCatchFire) - updated dependencies and improved skip logic - [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 # License
[![GNU GPLv3](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html) [![GNU GPLv3](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html)

View File

@@ -3,5 +3,6 @@
{"identifier": "", "airplay_credentials": ""} {"identifier": "", "airplay_credentials": ""}
], ],
"apikey":"", "apikey":"",
"skip_categories": ["sponsor"] "skip_categories": ["sponsor"],
"channel_whitelist": []
} }

View File

@@ -21,13 +21,45 @@ async def get_vid_id(title, artist, api_key, web_session):
url = constants.Youtube_api + "search" url = constants.Youtube_api + "search"
async with web_session.get(url, params=params) as resp: async with web_session.get(url, params=params) as resp:
data = await resp.json() data = await resp.json()
if "error" in data:
return
for i in data["items"]: for i in data["items"]:
if (i["id"]["kind"] != "youtube#video"):
continue
title_api = html.unescape(i["snippet"]["title"]) title_api = html.unescape(i["snippet"]["title"])
artist_api = html.unescape(i["snippet"]["channelTitle"]) artist_api = html.unescape(i["snippet"]["channelTitle"])
if title_api == title and artist_api == artist: if title_api == title and artist_api == artist:
return i["id"]["videoId"] return (i["id"]["videoId"], i["snippet"]["channelId"])
return 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"]
channels.append((i["snippet"]["channelId"], i["snippet"]["channelTitle"], subCount))
return channels
@listToTuple @listToTuple
@AsyncTTL(time_to_live=300, maxsize=5) @AsyncTTL(time_to_live=300, maxsize=5)

View File

@@ -3,7 +3,9 @@ import json
import asyncio import asyncio
from pyatv.const import DeviceModel from pyatv.const import DeviceModel
import sys import sys
import aiohttp
import asyncio
from . import api_helpers
def save_config(config, config_file): def save_config(config, config_file):
with open(config_file, "w") as f: with open(config_file, "w") as f:
@@ -74,9 +76,9 @@ def main(config, config_file, debug):
try: try:
for i in atvs: for i in atvs:
config["atvs"].append(i) config["atvs"].append(i)
print("done adding") print("Done adding")
except: 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 config["atvs"] = atvs
try: try:
@@ -84,12 +86,12 @@ def main(config, config_file, debug):
except: except:
apikey = "" apikey = ""
if 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: ") apikey = input("Enter your API key: ")
config["apikey"] = apikey config["apikey"] = apikey
else: else:
print( 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: ") apikey = input("Enter your API key: ")
config["apikey"] = apikey config["apikey"] = apikey
@@ -108,10 +110,58 @@ def main(config, config_file, debug):
skip_categories = [x for x in skip_categories if x != ''] # Remove empty strings skip_categories = [x for x in skip_categories if x != ''] # Remove empty strings
else: else:
categories = input( 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 config["skip_categories"] = skip_categories
print("config finished") try:
channel_whitelist = config["channel_whitelist"]
except:
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
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
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)
# Close web session asynchronously
loop.run_until_complete(web_session.close())
config["channel_whitelist"] = channel_whitelist
print("Config finished")
save_config(config, config_file) save_config(config, config_file)

View File

@@ -46,10 +46,10 @@ def app_start():
else: else:
try: # Check if config file has the correct structure 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 except: # If not, ask to setup the program
print("invalid config file, please run with --setup") print("invalid config file, please run with --setup")
sys.exit() sys.exit()
main.main( main.main(
config["atvs"], config["apikey"], config["skip_categories"], args.debug config["atvs"], config["apikey"], config["skip_categories"], config["channel_whitelist"], args.debug
) )

View File

@@ -13,12 +13,14 @@ class MyPushListener(pyatv.interface.PushListener):
web_session = None web_session = None
categories = ["sponsor"] categories = ["sponsor"]
whitelist = []
def __init__(self, apikey, atv, categories, web_session): def __init__(self, apikey, atv, categories, whitelist, web_session):
self.apikey = apikey self.apikey = apikey
self.rc = atv.remote_control self.rc = atv.remote_control
self.web_session = web_session self.web_session = web_session
self.categories = categories self.categories = categories
self.whitelist = whitelist
self.atv = atv self.atv = atv
def playstatus_update(self, updater, playstatus): def playstatus_update(self, updater, playstatus):
@@ -37,6 +39,7 @@ class MyPushListener(pyatv.interface.PushListener):
self.categories, self.categories,
self.atv, self.atv,
time_start, time_start,
self.whitelist
) )
) )
@@ -46,7 +49,7 @@ class MyPushListener(pyatv.interface.PushListener):
async def process_playstatus( 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)) logging.debug("App playing is:" + str(atv.metadata.app.identifier))
if ( if (
@@ -57,8 +60,12 @@ async def process_playstatus(
playstatus.title, playstatus.artist, apikey, web_session playstatus.title, playstatus.artist, apikey, web_session
) )
if vid_id: 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) for i in whitelist:
if vid_id[1] == i["id"]:
print("Channel whitelisted, skipping.")
return
segments = await api_helpers.get_segments(vid_id[0], web_session, categories)
print(segments) print(segments)
await time_to_segment( await time_to_segment(
segments, playstatus.position, rc, time_start, web_session segments, playstatus.position, rc, time_start, web_session
@@ -104,12 +111,12 @@ async def connect_atv(loop, identifier, airplay_credentials):
return await pyatv.connect(config, loop) 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"] identifier = atv_config["identifier"]
airplay_credentials = atv_config["airplay_credentials"] airplay_credentials = atv_config["airplay_credentials"]
atv = await connect_atv(event_loop, identifier, airplay_credentials) atv = await connect_atv(event_loop, identifier, airplay_credentials)
if atv: 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.listener = listener
atv.push_updater.start() atv.push_updater.start()
@@ -123,19 +130,19 @@ async def loop_atv(event_loop, atv_config, apikey, categories, web_session):
# reconnect to apple tv # reconnect to apple tv
atv = await connect_atv(event_loop, identifier, airplay_credentials) atv = await connect_atv(event_loop, identifier, airplay_credentials)
if atv: 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.listener = listener
atv.push_updater.start() atv.push_updater.start()
print("Push updater started") 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() loop = asyncio.get_event_loop_policy().get_event_loop()
if debug: if debug:
loop.set_debug(True) loop.set_debug(True)
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
web_session = aiohttp.ClientSession() web_session = aiohttp.ClientSession()
for i in atv_configs: 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() loop.run_forever()