From 4faefaac6b1ee73f47e2d01ee87828b5819e7929 Mon Sep 17 00:00:00 2001 From: snow flurry Date: Fri, 10 May 2024 17:07:23 -0700 Subject: [PATCH] uhhh --- .../.config/mpv/script-opts/sponsorblock.conf | 1 + base/.config/mpv/scripts/sponsorblock.lua | 569 ++++++++++++++++++ .../mpv/scripts/sponsorblock_shared/main.lua | 3 + .../sponsorblock_shared/sponsorblock.py | 122 ++++ .../sponsorblock_shared/sponsorblock.txt | 1 + base/.gitconfig | 2 + base/.mkshrc | 76 ++- base/.ssh/config | 137 +++-- base/bin/genpw | 12 +- 9 files changed, 859 insertions(+), 64 deletions(-) create mode 100644 base/.config/mpv/script-opts/sponsorblock.conf create mode 100644 base/.config/mpv/scripts/sponsorblock.lua create mode 100644 base/.config/mpv/scripts/sponsorblock_shared/main.lua create mode 100644 base/.config/mpv/scripts/sponsorblock_shared/sponsorblock.py create mode 100644 base/.config/mpv/scripts/sponsorblock_shared/sponsorblock.txt diff --git a/base/.config/mpv/script-opts/sponsorblock.conf b/base/.config/mpv/script-opts/sponsorblock.conf new file mode 100644 index 0000000..b4edd86 --- /dev/null +++ b/base/.config/mpv/script-opts/sponsorblock.conf @@ -0,0 +1 @@ +skip_categories=sponsor,intro diff --git a/base/.config/mpv/scripts/sponsorblock.lua b/base/.config/mpv/scripts/sponsorblock.lua new file mode 100644 index 0000000..96bfb24 --- /dev/null +++ b/base/.config/mpv/scripts/sponsorblock.lua @@ -0,0 +1,569 @@ +-- sponsorblock.lua +-- +-- This script skips sponsored segments of YouTube videos +-- using data from https://github.com/ajayyy/SponsorBlock + +local ON_WINDOWS = package.config:sub(1,1) ~= "/" + +local options = { + server_address = "https://sponsor.ajay.app", + + python_path = ON_WINDOWS and "python" or "python3", + + -- Categories to fetch + categories = "sponsor,intro,outro,interaction,selfpromo,filler", + + -- Categories to skip automatically + skip_categories = "sponsor", + + -- If true, sponsored segments will only be skipped once + skip_once = true, + + -- Note that sponsored segments may ocasionally be inaccurate if this is turned off + -- see https://blog.ajay.app/voting-and-pseudo-randomness-or-sponsorblock-or-youtube-sponsorship-segment-blocker + local_database = false, + + -- Update database on first run, does nothing if local_database is false + auto_update = true, + + -- How long to wait between local database updates + -- Format: "X[d,h,m]", leave blank to update on every mpv run + auto_update_interval = "6h", + + -- User ID used to submit sponsored segments, leave blank for random + user_id = "", + + -- Name to display on the stats page https://sponsor.ajay.app/stats/ leave blank to keep current name + display_name = "", + + -- Tell the server when a skip happens + report_views = true, + + -- Auto upvote skipped sponsors + auto_upvote = false, + + -- Use sponsor times from server if they're more up to date than our local database + server_fallback = true, + + -- Create chapters at sponsor boundaries for OSC display and manual skipping + make_chapters = true, + + -- Minimum duration for sponsors (in seconds), segments under that threshold will be ignored + min_duration = 1, + + -- Fade audio for smoother transitions + audio_fade = false, + + -- Audio fade step, applied once every 100ms until cap is reached + audio_fade_step = 10, + + -- Audio fade cap + audio_fade_cap = 0, + + -- Fast forward through sponsors instead of skipping + fast_forward = false, + + -- Playback speed modifier when fast forwarding, applied once every second until cap is reached + fast_forward_increase = .2, + + -- Playback speed cap + fast_forward_cap = 2, + + -- Length of the sha256 prefix (3-32) when querying server, 0 to disable + sha256_length = 4, + + -- Pattern for video id in local files, ignored if blank + -- Recommended value for base youtube-dl is "-([%w-_]+)%.[mw][kpe][v4b]m?$" + local_pattern = "", + + -- Legacy option, use skip_categories instead + skip = true +} + +mp.options = require "mp.options" +mp.options.read_options(options, "sponsorblock") + +local legacy = mp.command_native_async == nil +--[[ +if legacy then + options.local_database = false +end +--]] +options.local_database = false + +local utils = require "mp.utils" +scripts_dir = mp.find_config_file("scripts") + +local sponsorblock = utils.join_path(scripts_dir, "sponsorblock_shared/sponsorblock.py") +local uid_path = utils.join_path(scripts_dir, "sponsorblock_shared/sponsorblock.txt") +local database_file = options.local_database and utils.join_path(scripts_dir, "sponsorblock_shared/sponsorblock.db") or "" +local youtube_id = nil +local ranges = {} +local init = false +local segment = {a = 0, b = 0, progress = 0, first = true} +local retrying = false +local last_skip = {uuid = "", dir = nil} +local speed_timer = nil +local fade_timer = nil +local fade_dir = nil +local volume_before = mp.get_property_number("volume") +local categories = {} +local all_categories = {"sponsor", "intro", "outro", "interaction", "selfpromo", "preview", "music_offtopic", "filler"} +local chapter_cache = {} + +for category in string.gmatch(options.skip_categories, "([^,]+)") do + categories[category] = true +end + +function file_exists(name) + local f = io.open(name,"r") + if f ~= nil then io.close(f) return true else return false end +end + +function t_count(t) + local count = 0 + for _ in pairs(t) do count = count + 1 end + return count +end + +function time_sort(a, b) + if a.time == b.time then + return string.match(a.title, "segment end") + end + return a.time < b.time +end + +function parse_update_interval() + local s = options.auto_update_interval + if s == "" then return 0 end -- Interval Disabled + + local num, mod = s:match "^(%d+)([hdm])$" + + if num == nil or mod == nil then + mp.osd_message("[sponsorblock] auto_update_interval " .. s .. " is invalid", 5) + return nil + end + + local time_table = { + m = 60, + h = 60 * 60, + d = 60 * 60 * 24, + } + + return num * time_table[mod] +end + +function clean_chapters() + local chapters = mp.get_property_native("chapter-list") + local new_chapters = {} + for _, chapter in pairs(chapters) do + if chapter.title ~= "Preview segment start" and chapter.title ~= "Preview segment end" then + table.insert(new_chapters, chapter) + end + end + mp.set_property_native("chapter-list", new_chapters) +end + +function create_chapter(chapter_title, chapter_time) + local chapters = mp.get_property_native("chapter-list") + local duration = mp.get_property_native("duration") + table.insert(chapters, {title=chapter_title, time=(duration == nil or duration > chapter_time) and chapter_time or duration - .001}) + table.sort(chapters, time_sort) + mp.set_property_native("chapter-list", chapters) +end + +function process(uuid, t, new_ranges) + start_time = tonumber(string.match(t, "[^,]+")) + end_time = tonumber(string.sub(string.match(t, ",[^,]+"), 2)) + for o_uuid, o_t in pairs(ranges) do + if (start_time >= o_t.start_time and start_time <= o_t.end_time) or (o_t.start_time >= start_time and o_t.start_time <= end_time) then + new_ranges[o_uuid] = o_t + return + end + end + category = string.match(t, "[^,]+$") + if categories[category] and end_time - start_time >= options.min_duration then + new_ranges[uuid] = { + start_time = start_time, + end_time = end_time, + category = category, + skipped = false + } + end + if options.make_chapters and not chapter_cache[uuid] then + chapter_cache[uuid] = true + local category_title = (category:gsub("^%l", string.upper):gsub("_", " ")) + create_chapter(category_title .. " segment start (" .. string.sub(uuid, 1, 6) .. ")", start_time) + create_chapter(category_title .. " segment end (" .. string.sub(uuid, 1, 6) .. ")", end_time) + end +end + +function getranges(_, exists, db, more) + if type(exists) == "table" and exists["status"] == "1" then + if options.server_fallback then + mp.add_timeout(0, function() getranges(true, true, "") end) + else + return mp.osd_message("[sponsorblock] database update failed, gave up") + end + end + if db ~= "" and db ~= database_file then db = database_file end + if exists ~= true and not file_exists(db) then + if not retrying then + mp.osd_message("[sponsorblock] database update failed, retrying...") + retrying = true + end + return update() + end + if retrying then + mp.osd_message("[sponsorblock] database update succeeded") + retrying = false + end + local sponsors + local args = { + options.python_path, + sponsorblock, + "ranges", + db, + options.server_address, + youtube_id, + options.categories, + tostring(options.sha256_length) + } + if not legacy then + sponsors = mp.command_native({name = "subprocess", capture_stdout = true, playback_only = false, args = args}) + else + sponsors = utils.subprocess({args = args}) + end + mp.msg.debug("Got: " .. string.gsub(sponsors.stdout, "[\n\r]", "")) + if not string.match(sponsors.stdout, "^%s*(.*%S)") then return end + if string.match(sponsors.stdout, "error") then return getranges(true, true) end + local new_ranges = {} + local r_count = 0 + if more then r_count = -1 end + for t in string.gmatch(sponsors.stdout, "[^:%s]+") do + uuid = string.match(t, "([^,]+),[^,]+$") + if ranges[uuid] then + new_ranges[uuid] = ranges[uuid] + else + process(uuid, t, new_ranges) + end + r_count = r_count + 1 + end + local c_count = t_count(ranges) + if c_count == 0 or r_count >= c_count then + ranges = new_ranges + end +end + +function fast_forward() + if options.fast_forward and options.fast_forward == true then + speed_timer = nil + mp.set_property("speed", 1) + end + local last_speed = mp.get_property_number("speed") + local new_speed = math.min(last_speed + options.fast_forward_increase, options.fast_forward_cap) + if new_speed <= last_speed then return end + mp.set_property("speed", new_speed) +end + +function fade_audio(step) + local last_volume = mp.get_property_number("volume") + local new_volume = math.max(options.audio_fade_cap, math.min(last_volume + step, volume_before)) + if new_volume == last_volume then + if step >= 0 then fade_dir = nil end + if fade_timer ~= nil then fade_timer:kill() end + fade_timer = nil + return + end + mp.set_property("volume", new_volume) +end + +function skip_ads(name, pos) + if pos == nil then return end + local sponsor_ahead = false + for uuid, t in pairs(ranges) do + if (options.fast_forward == uuid or not options.skip_once or not t.skipped) and t.start_time <= pos and t.end_time > pos then + if options.fast_forward == uuid then return end + if options.fast_forward == false then + mp.osd_message("[sponsorblock] " .. t.category .. " skipped") + mp.set_property("time-pos", t.end_time) + else + mp.osd_message("[sponsorblock] skipping " .. t.category) + end + t.skipped = true + last_skip = {uuid = uuid, dir = nil} + if options.report_views or options.auto_upvote then + local args = { + options.python_path, + sponsorblock, + "stats", + database_file, + options.server_address, + youtube_id, + uuid, + options.report_views and "1" or "", + uid_path, + options.user_id, + options.auto_upvote and "1" or "" + } + if not legacy then + mp.command_native_async({name = "subprocess", playback_only = false, args = args}, function () end) + else + utils.subprocess_detached({args = args}) + end + end + if options.fast_forward ~= false then + options.fast_forward = uuid + if speed_timer ~= nil then speed_timer:kill() end + speed_timer = mp.add_periodic_timer(1, fast_forward) + end + return + elseif (not options.skip_once or not t.skipped) and t.start_time <= pos + 1 and t.end_time > pos + 1 then + sponsor_ahead = true + end + end + if options.audio_fade then + if sponsor_ahead then + if fade_dir ~= false then + if fade_dir == nil then volume_before = mp.get_property_number("volume") end + if fade_timer ~= nil then fade_timer:kill() end + fade_dir = false + fade_timer = mp.add_periodic_timer(.1, function() fade_audio(-options.audio_fade_step) end) + end + elseif fade_dir == false then + fade_dir = true + if fade_timer ~= nil then fade_timer:kill() end + fade_timer = mp.add_periodic_timer(.1, function() fade_audio(options.audio_fade_step) end) + end + end + if options.fast_forward and options.fast_forward ~= true then + options.fast_forward = true + speed_timer:kill() + speed_timer = nil + mp.set_property("speed", 1) + end +end + +function vote(dir) + if last_skip.uuid == "" then return mp.osd_message("[sponsorblock] no sponsors skipped, can't submit vote") end + local updown = dir == "1" and "up" or "down" + if last_skip.dir == dir then return mp.osd_message("[sponsorblock] " .. updown .. "vote already submitted") end + last_skip.dir = dir + local args = { + options.python_path, + sponsorblock, + "stats", + database_file, + options.server_address, + youtube_id, + last_skip.uuid, + "", + uid_path, + options.user_id, + dir + } + if not legacy then + mp.command_native_async({name = "subprocess", playback_only = false, args = args}, function () end) + else + utils.subprocess({args = args}) + end + mp.osd_message("[sponsorblock] " .. updown .. "vote submitted") +end + +function update() + mp.command_native_async({name = "subprocess", playback_only = false, args = { + options.python_path, + sponsorblock, + "update", + database_file, + options.server_address + }}, getranges) +end + +function file_loaded() + local initialized = init + ranges = {} + segment = {a = 0, b = 0, progress = 0, first = true} + last_skip = {uuid = "", dir = nil} + chapter_cache = {} + local video_path = mp.get_property("path", "") + mp.msg.debug("Path: " .. video_path) + local video_referer = string.match(mp.get_property("http-header-fields", ""), "Referer:([^,]+)") or "" + mp.msg.debug("Referer: " .. video_referer) + + local urls = { + "ytdl://([%w-_]+).*", + "https?://youtu%.be/([%w-_]+).*", + "https?://w?w?w?%.?youtube%.com/v/([%w-_]+).*", + "/watch.*[?&]v=([%w-_]+).*", + "/embed/([%w-_]+).*" + } + youtube_id = nil + for i, url in ipairs(urls) do + youtube_id = youtube_id or string.match(video_path, url) or string.match(video_referer, url) + if youtube_id then break end + end + youtube_id = youtube_id or string.match(video_path, options.local_pattern) + + if not youtube_id or string.len(youtube_id) < 11 or (local_pattern and string.len(youtube_id) ~= 11) then return end + youtube_id = string.sub(youtube_id, 1, 11) + mp.msg.debug("Found YouTube ID: " .. youtube_id) + init = true + if not options.local_database then + getranges(true, true) + else + local exists = file_exists(database_file) + if exists and options.server_fallback then + getranges(true, true) + mp.add_timeout(0, function() getranges(true, true, "", true) end) + elseif exists then + getranges(true, true) + elseif options.server_fallback then + mp.add_timeout(0, function() getranges(true, true, "") end) + end + end + if initialized then return end + if options.skip then + mp.observe_property("time-pos", "native", skip_ads) + end + if options.display_name ~= "" then + local args = { + options.python_path, + sponsorblock, + "username", + database_file, + options.server_address, + youtube_id, + "", + "", + uid_path, + options.user_id, + options.display_name + } + if not legacy then + mp.command_native_async({name = "subprocess", playback_only = false, args = args}, function () end) + else + utils.subprocess_detached({args = args}) + end + end + if not options.local_database or (not options.auto_update and file_exists(database_file)) then return end + + if file_exists(database_file) then + local db_info = utils.file_info(database_file) + local cur_time = os.time(os.date("*t")) + local upd_interval = parse_update_interval() + if upd_interval == nil or os.difftime(cur_time, db_info.mtime) < upd_interval then return end + end + + update() +end + +function set_segment() + if not youtube_id then return end + local pos = mp.get_property_number("time-pos") + if pos == nil then return end + if segment.progress > 1 then + segment.progress = segment.progress - 2 + end + if segment.progress == 1 then + segment.progress = 0 + segment.b = pos + mp.osd_message("[sponsorblock] segment boundary B set, press again for boundary A", 3) + else + segment.progress = 1 + segment.a = pos + mp.osd_message("[sponsorblock] segment boundary A set, press again for boundary B", 3) + end + if options.make_chapters and not segment.first then + local start_time = math.min(segment.a, segment.b) + local end_time = math.max(segment.a, segment.b) + if end_time - start_time ~= 0 and end_time ~= 0 then + clean_chapters() + create_chapter("Preview segment start", start_time) + create_chapter("Preview segment end", end_time) + end + end + segment.first = false +end + +function select_category(selected) + for category in string.gmatch(options.categories, "([^,]+)") do + mp.remove_key_binding("select_category_"..category) + mp.remove_key_binding("kp_select_category_"..category) + end + submit_segment(selected) +end + +function submit_segment(category) + if not youtube_id then return end + local start_time = math.min(segment.a, segment.b) + local end_time = math.max(segment.a, segment.b) + if end_time - start_time == 0 or end_time == 0 then + mp.osd_message("[sponsorblock] empty segment, not submitting") + elseif segment.progress <= 1 then + segment.progress = segment.progress + 2 + local category_list = "" + for category_id, category in pairs(all_categories) do + local category_title = (category:gsub("^%l", string.upper):gsub("_", " ")) + category_list = category_list .. category_id .. ": " .. category_title .. "\n" + mp.add_forced_key_binding(tostring(category_id), "select_category_"..category, function() select_category(category) end) + mp.add_forced_key_binding("KP"..tostring(category_id), "kp_select_category_"..category, function() select_category(category) end) + end + mp.osd_message(string.format("[sponsorblock] press a number to select category for segment: %.2d:%.2d:%.2d to %.2d:%.2d:%.2d\n\n" .. category_list .. "\nyou can press Shift+G again for default (Sponsor) or hide this message with g", math.floor(start_time/(60*60)), math.floor(start_time/60%60), math.floor(start_time%60), math.floor(end_time/(60*60)), math.floor(end_time/60%60), math.floor(end_time%60)), 30) + else + mp.osd_message("[sponsorblock] submitting segment...", 30) + local submit + local args = { + options.python_path, + sponsorblock, + "submit", + database_file, + options.server_address, + youtube_id, + tostring(start_time), + tostring(end_time), + uid_path, + options.user_id, + category or "sponsor" + } + if not legacy then + submit = mp.command_native({name = "subprocess", capture_stdout = true, playback_only = false, args = args}) + else + submit = utils.subprocess({args = args}) + end + if string.match(submit.stdout, "success") then + segment = {a = 0, b = 0, progress = 0, first = true} + mp.osd_message("[sponsorblock] segment submitted") + if options.make_chapters then + clean_chapters() + create_chapter("Submitted segment start", start_time) + create_chapter("Submitted segment end", end_time) + end + elseif string.match(submit.stdout, "error") then + mp.osd_message("[sponsorblock] segment submission failed, server may be down. try again", 5) + elseif string.match(submit.stdout, "502") then + mp.osd_message("[sponsorblock] segment submission failed, server is down. try again", 5) + elseif string.match(submit.stdout, "400") then + mp.osd_message("[sponsorblock] segment submission failed, impossible inputs", 5) + segment = {a = 0, b = 0, progress = 0, first = true} + elseif string.match(submit.stdout, "429") then + mp.osd_message("[sponsorblock] segment submission failed, rate limited. try again", 5) + elseif string.match(submit.stdout, "409") then + mp.osd_message("[sponsorblock] segment already submitted", 3) + segment = {a = 0, b = 0, progress = 0, first = true} + else + mp.osd_message("[sponsorblock] segment submission failed", 5) + end + end +end + +mp.register_event("file-loaded", file_loaded) +mp.add_key_binding("g", "set_segment", set_segment) +mp.add_key_binding("G", "submit_segment", submit_segment) +mp.add_key_binding("h", "upvote_segment", function() return vote("1") end) +mp.add_key_binding("H", "downvote_segment", function() return vote("0") end) +-- Bindings below are for backwards compatibility and could be removed at any time +mp.add_key_binding(nil, "sponsorblock_set_segment", set_segment) +mp.add_key_binding(nil, "sponsorblock_submit_segment", submit_segment) +mp.add_key_binding(nil, "sponsorblock_upvote", function() return vote("1") end) +mp.add_key_binding(nil, "sponsorblock_downvote", function() return vote("0") end) diff --git a/base/.config/mpv/scripts/sponsorblock_shared/main.lua b/base/.config/mpv/scripts/sponsorblock_shared/main.lua new file mode 100644 index 0000000..2bbe7a2 --- /dev/null +++ b/base/.config/mpv/scripts/sponsorblock_shared/main.lua @@ -0,0 +1,3 @@ +-- This is a dummy main.lua +-- required for mpv 0.33 +-- do not delete \ No newline at end of file diff --git a/base/.config/mpv/scripts/sponsorblock_shared/sponsorblock.py b/base/.config/mpv/scripts/sponsorblock_shared/sponsorblock.py new file mode 100644 index 0000000..8370a6a --- /dev/null +++ b/base/.config/mpv/scripts/sponsorblock_shared/sponsorblock.py @@ -0,0 +1,122 @@ +import urllib.request +import urllib.parse +import hashlib +import sqlite3 +import random +import string +import json +import sys +import os + +if sys.argv[1] in ["submit", "stats", "username"]: + if not sys.argv[8]: + if os.path.isfile(sys.argv[7]): + with open(sys.argv[7]) as f: + uid = f.read() + else: + uid = "".join(random.choices(string.ascii_letters + string.digits, k=36)) + with open(sys.argv[7], "w") as f: + f.write(uid) + else: + uid = sys.argv[8] + +opener = urllib.request.build_opener() +opener.addheaders = [("User-Agent", "mpv_sponsorblock/1.0 (https://github.com/po5/mpv_sponsorblock)")] +urllib.request.install_opener(opener) + +if sys.argv[1] == "ranges" and (not sys.argv[2] or not os.path.isfile(sys.argv[2])): + sha = None + if 3 <= int(sys.argv[6]) <= 32: + sha = hashlib.sha256(sys.argv[4].encode()).hexdigest()[:int(sys.argv[6])] + times = [] + try: + response = urllib.request.urlopen(sys.argv[3] + "/api/skipSegments" + ("/" + sha + "?" if sha else "?videoID=" + sys.argv[4] + "&") + urllib.parse.urlencode([("categories", json.dumps(sys.argv[5].split(",")))])) + segments = json.load(response) + for segment in segments: + if sha and sys.argv[4] != segment["videoID"]: + continue + if sha: + for s in segment["segments"]: + times.append(str(s["segment"][0]) + "," + str(s["segment"][1]) + "," + s["UUID"] + "," + s["category"]) + else: + times.append(str(segment["segment"][0]) + "," + str(segment["segment"][1]) + "," + segment["UUID"] + "," + segment["category"]) + print(":".join(times)) + except (TimeoutError, urllib.error.URLError) as e: + print("error") + except urllib.error.HTTPError as e: + if e.code == 404: + print("") + else: + print("error") +elif sys.argv[1] == "ranges": + conn = sqlite3.connect(sys.argv[2]) + conn.row_factory = sqlite3.Row + c = conn.cursor() + times = [] + for category in sys.argv[5].split(","): + c.execute("SELECT startTime, endTime, votes, UUID, category FROM sponsorTimes WHERE videoID = ? AND shadowHidden = 0 AND votes > -1 AND category = ?", (sys.argv[4], category)) + sponsors = c.fetchall() + best = list(sponsors) + dealtwith = [] + similar = [] + for sponsor_a in sponsors: + for sponsor_b in sponsors: + if sponsor_a is not sponsor_b and sponsor_a["startTime"] >= sponsor_b["startTime"] and sponsor_a["startTime"] <= sponsor_b["endTime"]: + similar.append([sponsor_a, sponsor_b]) + if sponsor_a in best: + best.remove(sponsor_a) + if sponsor_b in best: + best.remove(sponsor_b) + for sponsors_a in similar: + if sponsors_a in dealtwith: + continue + group = set(sponsors_a) + for sponsors_b in similar: + if sponsors_b[0] in group or sponsors_b[1] in group: + group.add(sponsors_b[0]) + group.add(sponsors_b[1]) + dealtwith.append(sponsors_b) + best.append(max(group, key=lambda x:x["votes"])) + for time in best: + times.append(str(time["startTime"]) + "," + str(time["endTime"]) + "," + time["UUID"] + "," + time["category"]) + print(":".join(times)) +elif sys.argv[1] == "update": + try: + urllib.request.urlretrieve(sys.argv[3] + "/database.db", sys.argv[2] + ".tmp") + os.replace(sys.argv[2] + ".tmp", sys.argv[2]) + except PermissionError: + print("database update failed, file currently in use", file=sys.stderr) + sys.exit(1) + except ConnectionResetError: + print("database update failed, connection reset", file=sys.stderr) + sys.exit(1) + except TimeoutError: + print("database update failed, timed out", file=sys.stderr) + sys.exit(1) + except urllib.error.URLError: + print("database update failed", file=sys.stderr) + sys.exit(1) +elif sys.argv[1] == "submit": + try: + req = urllib.request.Request(sys.argv[3] + "/api/skipSegments", data=json.dumps({"videoID": sys.argv[4], "segments": [{"segment": [float(sys.argv[5]), float(sys.argv[6])], "category": sys.argv[9]}], "userID": uid}).encode(), headers={"Content-Type": "application/json"}) + response = urllib.request.urlopen(req) + print("success") + except urllib.error.HTTPError as e: + print(e.code) + except: + print("error") +elif sys.argv[1] == "stats": + try: + if sys.argv[6]: + urllib.request.urlopen(sys.argv[3] + "/api/viewedVideoSponsorTime?UUID=" + sys.argv[5]) + if sys.argv[9]: + urllib.request.urlopen(sys.argv[3] + "/api/voteOnSponsorTime?UUID=" + sys.argv[5] + "&userID=" + uid + "&type=" + sys.argv[9]) + except: + pass +elif sys.argv[1] == "username": + try: + data = urllib.parse.urlencode({"userID": uid, "userName": sys.argv[9]}).encode() + req = urllib.request.Request(sys.argv[3] + "/api/setUsername", data=data) + urllib.request.urlopen(req) + except: + pass diff --git a/base/.config/mpv/scripts/sponsorblock_shared/sponsorblock.txt b/base/.config/mpv/scripts/sponsorblock_shared/sponsorblock.txt new file mode 100644 index 0000000..2d6733e --- /dev/null +++ b/base/.config/mpv/scripts/sponsorblock_shared/sponsorblock.txt @@ -0,0 +1 @@ +HwYQ8h92cinV3Wi2YAnfTRbjQwVoi9mPfWds \ No newline at end of file diff --git a/base/.gitconfig b/base/.gitconfig index 956bb69..6c83b47 100644 --- a/base/.gitconfig +++ b/base/.gitconfig @@ -3,3 +3,5 @@ email = snow@datagirl.xyz [init] defaultBranch = trunk +[core] + autocrlf = input diff --git a/base/.mkshrc b/base/.mkshrc index fb2ff25..1df3a1d 100644 --- a/base/.mkshrc +++ b/base/.mkshrc @@ -2,14 +2,18 @@ if [ -f /etc/shrc ]; then . /etc/shrc +elif [ -f /opt/shrc ]; then + . /opt/shrc fi export EDITOR=nvim +export KANIDM_URL="https://idm.qalico.net" +export KANIDM_USER="$USER" + +# opt out of .NET telemetry +export DOTNET_CLI_TELEMETRY_OPTOUT=true ## Extra paths -if [ -f "$HOME/.cargo/env" ] ; then - . $HOME/.cargo/env -fi # Python packages go here if [ -d "$HOME/.local/bin" ] ; then @@ -20,6 +24,7 @@ fi if [ -d "/opt/homebrew" ] ; then PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/sbin:/usr/sbin:$PATH" HOMEBREW_CASK_OPTS="--appdir=~/Applications" + PATH="/opt/homebrew/opt/ruby/bin:$PATH" export HOMEBREW_CASK_OPTS # x86 brew hack if [ -d "$HOME/.x86_64/homebrew" ] ; then @@ -29,11 +34,20 @@ if [ -d "/opt/homebrew" ] ; then fi fi +if [ -f "$HOME/.cargo/env" ] ; then + PATH="$HOME/.cargo/bin:$PATH" +fi + # Flutter if [ -d "$HOME/.opt/flutter" ] ; then PATH="$HOME/.opt/flutter/bin:$PATH" fi +# Perl local lib +if [ -d "$HOME/perl5/lib/perl5" ] ; then + eval $(perl -I ~/perl5/lib/perl5 -Mlocal::lib) +fi + # Android SDK (for VMs) if [ -d "$HOME/Library/Android/sdk" ] ; then export ANDROID_SDK_ROOT=$HOME/Library/Android/sdk @@ -48,8 +62,8 @@ if [ "$TERM" = "screen" ] ; then export TERM fi -export LANG= -export LC_ALL=en_US.UTF-8 +export LANG=en_US.UTF-8 +export LC_ALL= ## Ripped from a custom /etc/shrc, not very useful here if [ -z "$UID" ] ; then @@ -77,7 +91,7 @@ getrv() { # so we can use it here. rv=${1:-$?} if [ "$rv" -ne "0" -a "$rv" -ne "130" ] ; then - printf "$rv | " + printf "${BRL}33m${BRR}%d${BRL}0m${BRR} » " "$rv" fi } @@ -92,9 +106,16 @@ get_ps1() { fi if [ -n "$SSH_TTY" ] ; then - printf "${BRL}33m${BRR}[SSH] " + printf "${BRL}32m${BRR}SSH » " fi - printf "${USER_COLOR}${HOST%%.*}:${BRL}39m${BRR}"; + + if _branch="$(git branch --show-current 2>/dev/null)" ; then + [ -z "${_branch}" ] && _branch="$(git rev-parse --short HEAD 2>/dev/null)" + printf "${BRL}97m${BRR}%s » " "${_branch}" + unset _branch + fi + + printf "${USER_COLOR}${HOST%%.*} ${BRL}39m${BRR}"; if [ -n "$BASH_VERSION" ] ; then printf "\w" elif [[ "${PWD#$HOME}" != "$PWD" ]] ; then @@ -102,7 +123,7 @@ get_ps1() { else printf "$PWD" fi - printf "${USER_COLOR} ${PROMPT}${BRL}0m${BRR} " + printf "${USER_COLOR} \n${BRL}0m${BRR}${PROMPT} " } case "$-" in *i*) @@ -131,6 +152,9 @@ case "$-" in *i*) # Hack to bubble up profile, see bin/tmux-shim alias tmux="$HOME/bin/tmux-shim" + # Alias dotfiles + alias dotfiles='git --git-dir=$HOME/.dotfiles.git/ --work-tree=$HOME' + # Set bash PS1 if [ -n "$BASH_VERSION" ] ; then PS1="$(get_ps1)" @@ -148,6 +172,39 @@ case "$-" in *i*) echo "$@" | bc } + ## Bitwarden auto-unlock + if command -v bw >/dev/null ; then + if ! command -v jq >/dev/null ; then + cat </dev/null ; then + export INFLUX_TOKEN="$(bw get password influx_token)" + fi + "$real_influx" "$@" + else + echo "influx not installed; not continuing" + fi + } + nconv() { if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then @@ -159,3 +216,4 @@ case "$-" in *i*) } ;; esac + diff --git a/base/.ssh/config b/base/.ssh/config index 7751931..7c6c7bc 100644 --- a/base/.ssh/config +++ b/base/.ssh/config @@ -1,53 +1,84 @@ -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 diff --git a/base/bin/genpw b/base/bin/genpw index 94e48d3..497cc8e 100755 --- a/base/bin/genpw +++ b/base/bin/genpw @@ -1,9 +1,17 @@ #!/bin/sh +if [ "$1" = "-n" ] ; then + template='[:alnum:]' + shift +else + template='[:alnum:]+=\-_' +fi + + if [ -z "$1" ] ; then - echo "usage: $0 [length]" >&2 + echo "usage: $0 [-n] length" >&2 exit 1 fi -LC_ALL=C tr -dc '[:alnum:]'