From 63a1b4f50157f60ea98999fa8af2c72c133db3f0 Mon Sep 17 00:00:00 2001 From: olari Date: Sun, 26 May 2019 23:05:28 +0300 Subject: [PATCH] Fix line-endings; Increase portability; Add speedtyper.py --- bash/disable_audio_delay.sh | 47 +- bash/disable_mouse_accel.sh | 21 +- bash/get_beatmaps.sh | 2 +- bash/install_arch.sh | 2 +- bash/install_osu.sh | 83 ++-- bash/install_steam.sh | 17 +- bash/setup_gnome.sh | 2 +- cpp/spinner.cpp | 86 ++-- python/bulk_renamer.py | 67 +-- python/discord_spammer.py | 53 +- python/encode_touhou_lossless.py | 514 ++++++++++---------- python/extract_cover_flac.py | 31 +- python/ftb_continuum_oregen_plotter.py | 91 ++-- python/idatocstylesigs.py | 29 +- python/speedtyper.py | 97 ++++ python/text/mal_list_render.py | 91 ++-- python/text/youtube_subscriptions_render.py | 29 +- python/verify_anime_collection.py | 3 +- python/web/get_7k_pp.py | 97 ++-- python/web/get_beatmaps.py | 71 +-- python/web/gogdb_scaper.py | 47 +- python/web/mal_top_fetcher.py | 3 + python/web/pwned_checker.py | 275 +++++------ python/web/sheethost_scraper.py | 117 ++--- python/web/subscomru_scraper.py | 133 ++--- web-vanilla/index.html | 28 +- web-vanilla/mania/mania.html | 70 +-- web-vanilla/mania/mania.js | 224 ++++----- web-vanilla/reaction/reaction.html | 26 +- web-vanilla/reaction/reaction.js | 176 +++---- web-vanilla/style.css | 26 +- web-vanilla/trill/trill.html | 26 +- web-vanilla/trill/trill.js | 204 ++++---- 33 files changed, 1447 insertions(+), 1341 deletions(-) create mode 100644 python/speedtyper.py diff --git a/bash/disable_audio_delay.sh b/bash/disable_audio_delay.sh index 9ff6cb0..56f413c 100755 --- a/bash/disable_audio_delay.sh +++ b/bash/disable_audio_delay.sh @@ -1,25 +1,22 @@ -#!/bin/bash - -# Discard on use -rm $0 - -# Increase nice and rtprio limits. -local user=$(cut -d' ' -f1 <<< $(who)) -echo "$user - nice -20 -$user - rtprio 99" | sudo tee --append /etc/security/limits.conf - -# Create pulseaudio config. -sudo mkdir -p /etc/pulse/daemon.conf.d/ -echo "high-priority = yes -nice-level = -15 - -realtime-scheduling = yes -realtime-priority = 50 - -resample-method = speex-float-0 - -default-fragments = 5 -default-fragment-size-msec = 2" | sudo tee --append /etc/pulse/daemon.conf.d/10-lower-latency.conf - -# Disable timer-based scheduling. -sudo sed -i 's/load-module module-udev-det*/load-module module-udev-detect tsched=0/g' /etc/pulse/default.pa +#!/usr/bin/env bash + +# Increase nice and rtprio limits. +local user=$(cut -d' ' -f1 <<< $(who)) +echo "$user - nice -20 +$user - rtprio 99" | sudo tee --append /etc/security/limits.conf + +# Create pulseaudio config. +sudo mkdir -p /etc/pulse/daemon.conf.d/ +echo "high-priority = yes +nice-level = -15 + +realtime-scheduling = yes +realtime-priority = 50 + +resample-method = speex-float-0 + +default-fragments = 5 +default-fragment-size-msec = 2" | sudo tee --append /etc/pulse/daemon.conf.d/10-lower-latency.conf + +# Disable timer-based scheduling. +sudo sed -i 's/load-module module-udev-det*/load-module module-udev-detect tsched=0/g' /etc/pulse/default.pa diff --git a/bash/disable_mouse_accel.sh b/bash/disable_mouse_accel.sh index 20afd6f..45c2d0f 100755 --- a/bash/disable_mouse_accel.sh +++ b/bash/disable_mouse_accel.sh @@ -1,12 +1,9 @@ -#!/bin/bash - -# Discard on use -rm $0 - -sudo mkdir -p /etc/X11/xorg.conf.d/ -echo 'Section "InputClass" - Identifier "My Mouse" - Driver "libinput" - MatchIsPointer "yes" - Option "AccelProfile" "flat" -EndSection' | sudo tee --append /etc/X11/xorg.conf.d/50-mouse-acceleration.conf +#!/usr/bin/env bash + +sudo mkdir -p /etc/X11/xorg.conf.d/ +echo 'Section "InputClass" + Identifier "My Mouse" + Driver "libinput" + MatchIsPointer "yes" + Option "AccelProfile" "flat" +EndSection' | sudo tee --append /etc/X11/xorg.conf.d/50-mouse-acceleration.conf diff --git a/bash/get_beatmaps.sh b/bash/get_beatmaps.sh index bef8482..9973c97 100644 --- a/bash/get_beatmaps.sh +++ b/bash/get_beatmaps.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash USERNAME="" PASSWORD="" diff --git a/bash/install_arch.sh b/bash/install_arch.sh index 4906d16..eecef82 100755 --- a/bash/install_arch.sh +++ b/bash/install_arch.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash KEYMAP="dvorak-programmer" DEVICE="/dev/sda" diff --git a/bash/install_osu.sh b/bash/install_osu.sh index ddea59f..30babe0 100755 --- a/bash/install_osu.sh +++ b/bash/install_osu.sh @@ -1,43 +1,40 @@ -#!/bin/bash - -# Discard on use -rm $0 - -# https://blog.thepoon.fr/osuLinuxAudioLatency/ -sudo pacman-key --keyserver hkps://hkps.pool.sks-keyservers.net -r C0E7D0CDB72FBE95 -sudo pacman-key --keyserver hkps://hkps.pool.sks-keyservers.net --lsign-key C0E7D0CDB72FBE95 - -echo "[thepoon] -Server = https://archrepo.thepoon.fr -Server = https://mirrors.celianvdb.fr/archlinux/thepoon -" | sudo tee --append /etc/pacman.conf - -sudo pacman -S wine-osu winetricks - -mkdir ~/osu! -cd ~/osu! -wget https://m1.ppy.sh/r/osu\!install.exe - -export WINEPREFIX="$HOME/.wine_osu" -export WINEARCH=win32 - -winetricks dotnet40 & - -cat < ~/osu!/start.sh -#!/bin/sh - -export WINEPREFIX="$HOME/.wine_osu" -export STAGING_AUDIO_DURATION=8000 -export PATH=/opt/wine-osu/bin:$PATH - -cd ~/osu! -wine osu!.exe "$@" -EOF - -cat < ~/osu!/kill.sh -#!/bin/sh - -export WINEPREFIX="$HOME/.wine_osu" - -wineserver -k -EOF +#!/usr/bin/env bash + +# https://blog.thepoon.fr/osuLinuxAudioLatency/ +sudo pacman-key --keyserver hkps://hkps.pool.sks-keyservers.net -r C0E7D0CDB72FBE95 +sudo pacman-key --keyserver hkps://hkps.pool.sks-keyservers.net --lsign-key C0E7D0CDB72FBE95 + +echo "[thepoon] +Server = https://archrepo.thepoon.fr +Server = https://mirrors.celianvdb.fr/archlinux/thepoon +" | sudo tee --append /etc/pacman.conf + +sudo pacman -S wine-osu winetricks + +mkdir ~/osu! +cd ~/osu! +wget https://m1.ppy.sh/r/osu\!install.exe + +export WINEPREFIX="$HOME/.wine_osu" +export WINEARCH=win32 + +winetricks dotnet40 & + +cat < ~/osu!/start.sh +#!/bin/sh + +export WINEPREFIX="$HOME/.wine_osu" +export STAGING_AUDIO_DURATION=8000 +export PATH=/opt/wine-osu/bin:$PATH + +cd ~/osu! +wine osu!.exe "$@" +EOF + +cat < ~/osu!/kill.sh +#!/bin/sh + +export WINEPREFIX="$HOME/.wine_osu" + +wineserver -k +EOF diff --git a/bash/install_steam.sh b/bash/install_steam.sh index 30aaa9d..6787a6b 100755 --- a/bash/install_steam.sh +++ b/bash/install_steam.sh @@ -1,10 +1,7 @@ -#!/bin/bash - -# Discard on use -rm $0 - -echo "[multilib] -Include = /etc/pacman.d/mirrorlist -" | sudo tee --append /etc/pacman.conf - -sudo pacman -S steam steam-native-runtime +#!/usr/bin/env bash + +echo "[multilib] +Include = /etc/pacman.d/mirrorlist +" | sudo tee --append /etc/pacman.conf + +sudo pacman -S steam steam-native-runtime diff --git a/bash/setup_gnome.sh b/bash/setup_gnome.sh index 8240bc9..eae9ef2 100755 --- a/bash/setup_gnome.sh +++ b/bash/setup_gnome.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash #yay -S yaru-gtk-theme yaru-sound-theme gnome-shell-extension-ubuntu-dock #yay -S yaru-icon-theme # needs to be installed seperately for some reason diff --git a/cpp/spinner.cpp b/cpp/spinner.cpp index 3689758..556fcd6 100755 --- a/cpp/spinner.cpp +++ b/cpp/spinner.cpp @@ -1,43 +1,43 @@ -#include -#include -#include -#include - -constexpr auto num_points = 10; -constexpr auto sleep_msec = 1; -constexpr auto radius = 100.0f; - -int main() { - const auto window_handle = FindWindow(nullptr, "osu!"); - if (!window_handle) { - printf("FATAL: Could not find osu!. (0x%X)\n", GetLastError()); - return -1; - } - - auto get_desired_pos = [&]() -> std::tuple { - static auto counter = 0; - counter++; - - RECT rect; - GetWindowRect(window_handle, &rect); - - const auto center_x = rect.left + (rect.right - rect.left) / 2; - const auto center_y = rect.top + (rect.bottom - rect.top) / 2; - - const auto angle = 3.1415926f * 2.0f * (float)counter / (float)num_points; - - if (counter > num_points) { - counter = 0; - } - - return { center_x + radius * sin(angle), center_y + radius * cos(angle) }; - }; - - while (!GetAsyncKeyState(VK_DELETE)) { - if (GetAsyncKeyState('P')) { - std::apply(SetCursorPos, get_desired_pos()); - } - - Sleep(sleep_msec); - } -} +#include +#include +#include +#include + +constexpr auto num_points = 10; +constexpr auto sleep_msec = 1; +constexpr auto radius = 100.0f; + +int main() { + const auto window_handle = FindWindow(nullptr, "osu!"); + if (!window_handle) { + printf("FATAL: Could not find osu!. (0x%X)\n", GetLastError()); + return -1; + } + + auto get_desired_pos = [&]() -> std::tuple { + static auto counter = 0; + counter++; + + RECT rect; + GetWindowRect(window_handle, &rect); + + const auto center_x = rect.left + (rect.right - rect.left) / 2; + const auto center_y = rect.top + (rect.bottom - rect.top) / 2; + + const auto angle = 3.1415926f * 2.0f * (float)counter / (float)num_points; + + if (counter > num_points) { + counter = 0; + } + + return { center_x + radius * sin(angle), center_y + radius * cos(angle) }; + }; + + while (!GetAsyncKeyState(VK_DELETE)) { + if (GetAsyncKeyState('P')) { + std::apply(SetCursorPos, get_desired_pos()); + } + + Sleep(sleep_msec); + } +} diff --git a/python/bulk_renamer.py b/python/bulk_renamer.py index 01e410e..dc3dfde 100755 --- a/python/bulk_renamer.py +++ b/python/bulk_renamer.py @@ -1,33 +1,34 @@ -#!/usr/bin/env python3 - -# bulk file renamer that you interface with using intermediary text file. - -import os - -def setup_file(): - files = open('files.txt', 'w') - - for file in os.listdir('.'): - files.write(file + '\n') - -def rename_files(): - new_names = open('files.txt', 'r') - - for file in os.listdir('.'): - new_name = str(new_names.readline()).rstrip('\n') - - if (new_name == file) or new_name == "files.txt": - continue - - os.rename(file, new_name) - -print("bulk renamer by Olari.\n") - -print("Generating files.txt") -print("Modify it to rename files\n") -setup_file() - -input("Waiting for input...\n") - -print("Renaming files") -rename_files() +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# bulk file renamer that you interface with using intermediary text file. + +import os + +def setup_file(): + files = open('files.txt', 'w') + + for file in os.listdir('.'): + files.write(file + '\n') + +def rename_files(): + new_names = open('files.txt', 'r') + + for file in os.listdir('.'): + new_name = str(new_names.readline()).rstrip('\n') + + if (new_name == file) or new_name == "files.txt": + continue + + os.rename(file, new_name) + +print("bulk renamer by Olari.\n") + +print("Generating files.txt") +print("Modify it to rename files\n") +setup_file() + +input("Waiting for input...\n") + +print("Renaming files") +rename_files() diff --git a/python/discord_spammer.py b/python/discord_spammer.py index 07535af..441b194 100755 --- a/python/discord_spammer.py +++ b/python/discord_spammer.py @@ -1,26 +1,27 @@ -#!/usr/bin/env python3 - -# deadsimple discord bot - -import discord -import asyncio - -client = discord.Client() - -async def task(): - await client.wait_until_ready() - - while not client.is_closed: - server = client.get_channel('channel id') - await client.send_message(server, 'message') - await asyncio.sleep(60) # time to sleep - -@client.event -async def on_ready(): - print('Logged in as') - print(client.user.name) - print(client.user.id) - print('------') - -client.loop.create_task(task()) -client.run('username', 'password') +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# deadsimple discord bot + +import discord +import asyncio + +client = discord.Client() + +async def task(): + await client.wait_until_ready() + + while not client.is_closed: + server = client.get_channel('channel id') + await client.send_message(server, 'message') + await asyncio.sleep(60) # time to sleep + +@client.event +async def on_ready(): + print('Logged in as') + print(client.user.name) + print(client.user.id) + print('------') + +client.loop.create_task(task()) +client.run('username', 'password') diff --git a/python/encode_touhou_lossless.py b/python/encode_touhou_lossless.py index d4e9be4..3a6ddd9 100755 --- a/python/encode_touhou_lossless.py +++ b/python/encode_touhou_lossless.py @@ -1,257 +1,257 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# overly complicated flac reencoder originally written for the big touhou music torrent on nyaa.si -# contains example of process calling and multithreading -# not recommended to use (probably destructive) - -import os, re, time -import subprocess -import wave - -from queue import Queue -from threading import Thread - -f = open("errlog", "w", encoding="UTF-8") - -def remove_if_exists(filename): - if os.path.exists(filename): - os.remove(filename) - -def opus_enc(queue, split_track_filename, track, quality=140.0): - subprocess.call([ - 'opusenc', - '--vbr', - '--bitrate', str(quality), - #'--comp', 10, #default - #'--framesize', '60', # default 20 - '--artist', track.performer, - '--comment', 'tracknumber={}'.format(track.index), - '--title', track.title, - '--date', track.cd_date, - '--genre', track.cd_genre, - '--album', track.cd_title, - split_track_filename, - '{}.opus'.format(os.path.splitext(split_track_filename)[0]), - ]) - queue.get() - -class Track(): - def __init__(self, track_index, filename, parent): - for member in ('cd_performer', 'cd_title', 'cd_date', 'cd_genre'): - setattr(self, member, getattr(parent, member)) - - self.filename = filename - self.filepath = filename[:filename.rfind('\\')+1] - self.title = '' - self.index = track_index - self.performer = self.cd_performer - self.time = { 1:0.0 } - - def __str__(self): - return "{} - {} - {}".format(self.index, self.title, self.time) - -class CueSheet(): - def __init__(self, filename): - self.filename = filename - self.filepath = filename[:filename.rfind('\\')+1] - - self.cd_performer = '' - self.cd_title = '' - self.cd_genre = '' - self.cd_date = '' - - self.current_file = '' - - self.tracks = [] - - self.regex_lst = ( - (re.compile(r'PERFORMER\s(.+)'), self.__performer), - (re.compile(r'REM DATE\s(.+)'), self.__date), - (re.compile(r'REM GENRE\s(.+)'), self.__genre), - (re.compile(r'TITLE\s(.+)'), self.__title), - (re.compile(r'FILE\s(.+)\sWAVE'), self.__file), - (re.compile(r'TRACK\s(\d{2})\sAUDIO'), self.__track), - (re.compile(r'INDEX\s(\d{2})\s(\d{1,3}:\d{2}:\d{2})'), self.__index), - ) - - def __str__(self): - value = "Title: {}\nPerformer: {}\nGenre: {}\nDate: {}\n".format(self.cd_title, self.cd_performer, self.cd_genre, self.cd_date) - for track in self.tracks: - value += ' ' + str(track) + '\n' - return value - - def read(self): - with open(self.filename, 'r', encoding='utf-8-sig') as f: - for line in f: - for regex, handler in self.regex_lst: - mobj = regex.match(line.strip()) - if mobj: - handler(*self.unquote(mobj.groups())) - - def split(self): - encoding_queue = multiprocessing.Queue(multiprocessing.cpu_count()) - - cds = set() - tracks = set() - - for i, track in enumerate(self.tracks): - # FATAL: sheet is not for .tta file - if track.filename[-4:] != '.tta': - f.write("\nFilename isn't .tta ({}):\n{}\n".format(track.filename, str(self))) - return - - track_path = track.filepath + ' - '.join((track.index, track.title)).replace('?', '').replace('\\', '').replace('\\', '').replace(':', '') - - track_opus = track_path + '.opus' - track_wav = track_path + '.wav' - - if os.path.exists(track_opus): - f.write("File already exists, continuing... ({})".format(track_opus)) - remove_if_exists(track_wav) - continue - - cd_wav = track.filename[:-4] + '.wav' - - # decode .tta if needed - if not os.path.exists(cd_wav): - # FATAL: no file to decode - if not os.path.exists(track.filename): - f.write("\nFile doesn't exist ({}):\n{}\n".format(track.filename, str(self))) - return - - result = subprocess.call([ - 'tta', #'ttaenc', - '-d', - track.filename, - #'-o', - cd_wav - ]) - - # FATAL: .tta decode failed - if result != 0: - f.write("Failed to decode .tta ({}):\n{}\n\n".format(track.index, str(self))) - return - - # remove .tta - remove_if_exists(track.filename) - - # split .wav into track - if not os.path.exists(track_wav): - wafi = wave.open(cd_wav, 'rb') - param_names = ('nchannels', 'sampwidth', 'framerate', 'nframes', 'comptype', 'compname') - params = wafi.getparams() - param_dict = dict(zip(param_names, params)) - - start = int(param_dict['framerate'] * track.time[1]) - stop = param_dict['nframes'] - if len(sheet.tracks) > i+1 and sheet.tracks[i+1].filename == track.filename: - stop = int(param_dict['framerate'] * sheet.tracks[i+1].time.get(0, sheet.tracks[i+1].time[1])) - - wafi_write = wave.open(track_wav, 'wb') - newparams = list(params) - newparams[3] = 0 - wafi_write.setparams( tuple(newparams) ) - - wafi.setpos(start) - wafi_write.writeframes(wafi.readframes(stop-start)) - wafi_write.close() - - wafi.close() - - encoding_queue.put(track_wav) - p = multiprocessing.Process( - target=opus_enc, - args=( - encoding_queue, - track_wav, - track - ) - ) - - p.start() - - if cd_wav not in cds: - cds.add(cd_wav) - - tracks.add(track_wav) - - while not encoding_queue.empty(): - time.sleep(0.2) - - for cd in cds: - remove_if_exists(cd) - - for track in tracks: - remove_if_exists(track) - - remove_if_exists(self.filename) - - print(self.filename, "done!") - - - def __performer(self, s): - if not self.tracks: - self.cd_performer = s - else: - self.tracks[-1].performer = s - - def __title(self, s): - if not self.tracks: - self.cd_title = s - else: - self.tracks[-1].title = s - - def __genre(self, s): - self.cd_genre = s - - def __date(self, s): - self.cd_date = s - - def __file(self, s): - self.current_file = s - - def __track(self, s): - self.tracks.append( Track(s, self.filepath + self.current_file, self) ) - - def __index(self, idx, s): - idx = int(idx) - self.tracks[-1].time[idx] = self.index_split(s) - - @staticmethod - def index_split(s): - t = s.split(':') - return float(t[0])*60 + float(t[1]) + float(t[2]) / 75.0 - - @staticmethod - def dqstrip(s): - if s[0] == '"' and s[-1] == '"': return s[1:-1] - return s - - @staticmethod - def unquote(t): - return tuple([CueSheet.dqstrip(s.strip()) for s in t]) - -class SplitterWorker(Thread): - def __init__(self, queue, filename): - Thread.__init__(self) - self.queue = queue - self.filename = filename - - def run(self): - sheet = CueSheet(self.filename) - sheet.read() - sheet.split() - -if __name__ == '__main__': - queue = Queue() - - for root, dirs, files in os.walk('.'): - for name in files: - if name[-4:].lower() == '.cue': - worker = SplitterWorker(queue, root + '\\' + name) - worker.daemon = True - worker.start() - - if os.path.exists('./stop'): - exit(1) +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# overly complicated flac reencoder originally written for the big touhou music torrent on nyaa.si +# contains example of process calling and multithreading +# not recommended to use (probably destructive) + +import os, re, time +import subprocess +import wave + +from queue import Queue +from threading import Thread + +f = open("errlog", "w", encoding="UTF-8") + +def remove_if_exists(filename): + if os.path.exists(filename): + os.remove(filename) + +def opus_enc(queue, split_track_filename, track, quality=140.0): + subprocess.call([ + 'opusenc', + '--vbr', + '--bitrate', str(quality), + #'--comp', 10, #default + #'--framesize', '60', # default 20 + '--artist', track.performer, + '--comment', 'tracknumber={}'.format(track.index), + '--title', track.title, + '--date', track.cd_date, + '--genre', track.cd_genre, + '--album', track.cd_title, + split_track_filename, + '{}.opus'.format(os.path.splitext(split_track_filename)[0]), + ]) + queue.get() + +class Track(): + def __init__(self, track_index, filename, parent): + for member in ('cd_performer', 'cd_title', 'cd_date', 'cd_genre'): + setattr(self, member, getattr(parent, member)) + + self.filename = filename + self.filepath = filename[:filename.rfind('\\')+1] + self.title = '' + self.index = track_index + self.performer = self.cd_performer + self.time = { 1:0.0 } + + def __str__(self): + return "{} - {} - {}".format(self.index, self.title, self.time) + +class CueSheet(): + def __init__(self, filename): + self.filename = filename + self.filepath = filename[:filename.rfind('\\')+1] + + self.cd_performer = '' + self.cd_title = '' + self.cd_genre = '' + self.cd_date = '' + + self.current_file = '' + + self.tracks = [] + + self.regex_lst = ( + (re.compile(r'PERFORMER\s(.+)'), self.__performer), + (re.compile(r'REM DATE\s(.+)'), self.__date), + (re.compile(r'REM GENRE\s(.+)'), self.__genre), + (re.compile(r'TITLE\s(.+)'), self.__title), + (re.compile(r'FILE\s(.+)\sWAVE'), self.__file), + (re.compile(r'TRACK\s(\d{2})\sAUDIO'), self.__track), + (re.compile(r'INDEX\s(\d{2})\s(\d{1,3}:\d{2}:\d{2})'), self.__index), + ) + + def __str__(self): + value = "Title: {}\nPerformer: {}\nGenre: {}\nDate: {}\n".format(self.cd_title, self.cd_performer, self.cd_genre, self.cd_date) + for track in self.tracks: + value += ' ' + str(track) + '\n' + return value + + def read(self): + with open(self.filename, 'r', encoding='utf-8-sig') as f: + for line in f: + for regex, handler in self.regex_lst: + mobj = regex.match(line.strip()) + if mobj: + handler(*self.unquote(mobj.groups())) + + def split(self): + encoding_queue = multiprocessing.Queue(multiprocessing.cpu_count()) + + cds = set() + tracks = set() + + for i, track in enumerate(self.tracks): + # FATAL: sheet is not for .tta file + if track.filename[-4:] != '.tta': + f.write("\nFilename isn't .tta ({}):\n{}\n".format(track.filename, str(self))) + return + + track_path = track.filepath + ' - '.join((track.index, track.title)).replace('?', '').replace('\\', '').replace('\\', '').replace(':', '') + + track_opus = track_path + '.opus' + track_wav = track_path + '.wav' + + if os.path.exists(track_opus): + f.write("File already exists, continuing... ({})".format(track_opus)) + remove_if_exists(track_wav) + continue + + cd_wav = track.filename[:-4] + '.wav' + + # decode .tta if needed + if not os.path.exists(cd_wav): + # FATAL: no file to decode + if not os.path.exists(track.filename): + f.write("\nFile doesn't exist ({}):\n{}\n".format(track.filename, str(self))) + return + + result = subprocess.call([ + 'tta', #'ttaenc', + '-d', + track.filename, + #'-o', + cd_wav + ]) + + # FATAL: .tta decode failed + if result != 0: + f.write("Failed to decode .tta ({}):\n{}\n\n".format(track.index, str(self))) + return + + # remove .tta + remove_if_exists(track.filename) + + # split .wav into track + if not os.path.exists(track_wav): + wafi = wave.open(cd_wav, 'rb') + param_names = ('nchannels', 'sampwidth', 'framerate', 'nframes', 'comptype', 'compname') + params = wafi.getparams() + param_dict = dict(zip(param_names, params)) + + start = int(param_dict['framerate'] * track.time[1]) + stop = param_dict['nframes'] + if len(sheet.tracks) > i+1 and sheet.tracks[i+1].filename == track.filename: + stop = int(param_dict['framerate'] * sheet.tracks[i+1].time.get(0, sheet.tracks[i+1].time[1])) + + wafi_write = wave.open(track_wav, 'wb') + newparams = list(params) + newparams[3] = 0 + wafi_write.setparams( tuple(newparams) ) + + wafi.setpos(start) + wafi_write.writeframes(wafi.readframes(stop-start)) + wafi_write.close() + + wafi.close() + + encoding_queue.put(track_wav) + p = multiprocessing.Process( + target=opus_enc, + args=( + encoding_queue, + track_wav, + track + ) + ) + + p.start() + + if cd_wav not in cds: + cds.add(cd_wav) + + tracks.add(track_wav) + + while not encoding_queue.empty(): + time.sleep(0.2) + + for cd in cds: + remove_if_exists(cd) + + for track in tracks: + remove_if_exists(track) + + remove_if_exists(self.filename) + + print(self.filename, "done!") + + + def __performer(self, s): + if not self.tracks: + self.cd_performer = s + else: + self.tracks[-1].performer = s + + def __title(self, s): + if not self.tracks: + self.cd_title = s + else: + self.tracks[-1].title = s + + def __genre(self, s): + self.cd_genre = s + + def __date(self, s): + self.cd_date = s + + def __file(self, s): + self.current_file = s + + def __track(self, s): + self.tracks.append( Track(s, self.filepath + self.current_file, self) ) + + def __index(self, idx, s): + idx = int(idx) + self.tracks[-1].time[idx] = self.index_split(s) + + @staticmethod + def index_split(s): + t = s.split(':') + return float(t[0])*60 + float(t[1]) + float(t[2]) / 75.0 + + @staticmethod + def dqstrip(s): + if s[0] == '"' and s[-1] == '"': return s[1:-1] + return s + + @staticmethod + def unquote(t): + return tuple([CueSheet.dqstrip(s.strip()) for s in t]) + +class SplitterWorker(Thread): + def __init__(self, queue, filename): + Thread.__init__(self) + self.queue = queue + self.filename = filename + + def run(self): + sheet = CueSheet(self.filename) + sheet.read() + sheet.split() + +if __name__ == '__main__': + queue = Queue() + + for root, dirs, files in os.walk('.'): + for name in files: + if name[-4:].lower() == '.cue': + worker = SplitterWorker(queue, root + '\\' + name) + worker.daemon = True + worker.start() + + if os.path.exists('./stop'): + exit(1) diff --git a/python/extract_cover_flac.py b/python/extract_cover_flac.py index a75f1ad..c9a23c4 100755 --- a/python/extract_cover_flac.py +++ b/python/extract_cover_flac.py @@ -1,16 +1,17 @@ -#!/usr/bin/env python3 - -# extracts cover from flac audio file - -from mutagen.flac import FLAC, Picture - -song = "cover.flac" - -var = FLAC(song) -pics = var.pictures -print (pics) -for p in pics: - if p.type == 3: #front cover - print("\nfound front cover") - with open("cover.jpg", "wb") as f: +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# extracts cover from flac audio file + +from mutagen.flac import FLAC, Picture + +song = "cover.flac" + +var = FLAC(song) +pics = var.pictures +print (pics) +for p in pics: + if p.type == 3: #front cover + print("\nfound front cover") + with open("cover.jpg", "wb") as f: f.write(p.data) \ No newline at end of file diff --git a/python/ftb_continuum_oregen_plotter.py b/python/ftb_continuum_oregen_plotter.py index 03765ac..bc8610a 100755 --- a/python/ftb_continuum_oregen_plotter.py +++ b/python/ftb_continuum_oregen_plotter.py @@ -1,45 +1,46 @@ -#!/usr/bin/env python3 - -import matplotlib.pyplot as plt -import numpy as np - -class Ore: - def __init__(self, name, low, high): - self.name = name - self.low = low - self.high = high - -oregen = ( - Ore('gold', 0, 32), - Ore('iron', 0, 64), - Ore('coal', 0, 128), - Ore('lapis', 0, 32), - Ore('diamond', 0, 16), - Ore('redstone', 0, 16), - Ore('emerald', 0, 16), - Ore('certuz', 24, 48), - Ore('apatite', 54, 96), - Ore('uranium', 16, 42), - Ore('ruby', 16, 42), - Ore('sapphire', 16, 42), - Ore('bauxite', 48, 72), - Ore('tungsten', 0, 10), - Ore('peridot', 16, 42), - Ore('copper', 40, 75), - Ore('tin', 20, 55), - Ore('silver', 5, 30), - Ore('lead', 5, 30), - Ore('aluminum', 48, 72), - Ore('nickel', 5, 20), - Ore('platinum', 7, 75), - Ore('iridium', 8, 75), -) - -oregen = sorted(oregen, key=lambda x: x.low, reverse=True) - -plt.boxplot([(x.high, x.low) for x in oregen], labels=[x.name.title() for x in oregen], vert=False) -plt.title('FTB Continuum Oregen') -plt.xlabel('Y-level') -plt.xticks() - -plt.savefig('oregen.svg', format='svg') +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import matplotlib.pyplot as plt +import numpy as np + +class Ore: + def __init__(self, name, low, high): + self.name = name + self.low = low + self.high = high + +oregen = ( + Ore('gold', 0, 32), + Ore('iron', 0, 64), + Ore('coal', 0, 128), + Ore('lapis', 0, 32), + Ore('diamond', 0, 16), + Ore('redstone', 0, 16), + Ore('emerald', 0, 16), + Ore('certuz', 24, 48), + Ore('apatite', 54, 96), + Ore('uranium', 16, 42), + Ore('ruby', 16, 42), + Ore('sapphire', 16, 42), + Ore('bauxite', 48, 72), + Ore('tungsten', 0, 10), + Ore('peridot', 16, 42), + Ore('copper', 40, 75), + Ore('tin', 20, 55), + Ore('silver', 5, 30), + Ore('lead', 5, 30), + Ore('aluminum', 48, 72), + Ore('nickel', 5, 20), + Ore('platinum', 7, 75), + Ore('iridium', 8, 75), +) + +oregen = sorted(oregen, key=lambda x: x.low, reverse=True) + +plt.boxplot([(x.high, x.low) for x in oregen], labels=[x.name.title() for x in oregen], vert=False) +plt.title('FTB Continuum Oregen') +plt.xlabel('Y-level') +plt.xticks() + +plt.savefig('oregen.svg', format='svg') diff --git a/python/idatocstylesigs.py b/python/idatocstylesigs.py index 05d35be..e3ba5c8 100755 --- a/python/idatocstylesigs.py +++ b/python/idatocstylesigs.py @@ -1,14 +1,15 @@ -#!/usr/bin/env python3 - -# converts code signatures found in ida to ones easily usable in c++ code - -import sys - -if len(sys.argv) < 2: - print("Usage: " + sys.argv[0] + " [ida style sig]") - exit(1) - -print( - '"' + ''.join([('\\x' + (byte if byte != '?' else '00')) for byte in sys.argv[1:]]) + '", "' + - ''.join([('x' if byte != '?' else '?') for byte in sys.argv[1:]]) + '"' -) +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# converts code signatures found in ida to ones easily usable in c++ code + +import sys + +if len(sys.argv) < 2: + print("Usage: " + sys.argv[0] + " [ida style sig]") + exit(1) + +print( + '"' + ''.join([('\\x' + (byte if byte != '?' else '00')) for byte in sys.argv[1:]]) + '", "' + + ''.join([('x' if byte != '?' else '?') for byte in sys.argv[1:]]) + '"' +) diff --git a/python/speedtyper.py b/python/speedtyper.py new file mode 100644 index 0000000..032580d --- /dev/null +++ b/python/speedtyper.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys +import curses + + +class Word(): + def __init__(self, target, x, y): + self.target = target + self.input = "" + self.x = x + self.y = y + + +def get_words(text, max_x, max_y): + curr_x = 0 + curr_y = 0 + + words = [] + for word in text.split(): + if curr_x + len(word) > max_x: + curr_y += 1 + curr_x = 0 + + if curr_y > max_y: + print("text too long, scrolling not implemented.") + exit(1) + + words.append(Word(word, curr_x, curr_y)) + curr_x += len(word) + 1 + + return words + + +def main_curses(stdscr, text): + curses.use_default_colors() + curses.init_pair(1, curses.COLOR_GREEN, -1) + curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_RED) + + key = old_max_y = old_max_x = curr_word = 0 + + while True: + # rewrap words on screen size change + max_y, max_x = stdscr.getmaxyx() + if max_y != old_max_y or max_x != old_max_x: + words = get_words(text, max_x, max_y) + old_max_x = max_x + old_max_y = max_y + + if key in (curses.KEY_BACKSPACE, 'KEY_BACKSPACE', '\b', '\x7f', 127): # fml + words[curr_word].input = words[curr_word].input[:-1] + elif key >= 32 and key <= 126: + words[curr_word].input += chr(key) + + # increment current word if its completed + if words[curr_word].input == words[curr_word].target: + curr_word += 1 + + # text complete + if curr_word == len(words): + break + + stdscr.clear() + + for word in words: + if word.input == word.target: + stdscr.addstr(word.y, word.x, word.target, curses.A_DIM) + elif word == words[curr_word]: + stdscr.addstr(word.y, word.x, word.target, curses.A_UNDERLINE) + else: + stdscr.addstr(word.y, word.x, word.target) + + for i, c in enumerate(words[curr_word].input): + if i >= len(words[curr_word].target) or words[curr_word].input[i] != words[curr_word].target[i]: + stdscr.addstr(words[curr_word].y, words[curr_word].x + i, c, curses.color_pair(2) | curses.A_BOLD) + else: + stdscr.addstr(words[curr_word].y, words[curr_word].x + i, c, curses.color_pair(1)) + + stdscr.move(words[curr_word].y, words[curr_word].x + len(words[curr_word].input)) + + key = stdscr.getch() + + +def main(argv): + from argparse import ArgumentParser + parser = ArgumentParser() + parser.add_argument("input") + args = parser.parse_args(argv[1:]) + + text = open(args.input).read() + + curses.wrapper(main_curses, text) + + +if __name__ == "__main__": + main(sys.argv) diff --git a/python/text/mal_list_render.py b/python/text/mal_list_render.py index d36daaf..83332b4 100755 --- a/python/text/mal_list_render.py +++ b/python/text/mal_list_render.py @@ -1,45 +1,46 @@ -#!/usr/bin/env python3 - -import bs4 -import sys -import os -import glob - -# converts MyAnimeList's XML exports to readable (but less informative) text files. - -animelists = glob.glob('animelist*.xml') - -for animelist in animelists: - with open(animelist, 'r') as xml, open(animelist + '.txt', 'w') as file: - soup = bs4.BeautifulSoup(xml.read(), 'html.parser') - - completed = [] - ptw = [] - movies = [] - for anime in soup.select('anime'): - line = anime.select('series_title')[0].text + ' ' + anime.select('my_watched_episodes')[0].text + '/' + anime.select('series_episodes')[0].text + '\n' - - series_type = anime.select('series_type')[0].text - status = anime.select('my_status')[0].text - - if series_type == 'Movie': - movies.append(line) - continue - - if status == 'Completed': - completed.append(line) - elif status == 'Plan to Watch': - ptw.append(line) - - for title in completed: - file.write(title) - - file.write('\n') - - for title in ptw: - file.write(title) - - file.write('\n') - - for title in movies: - file.write(title) +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import bs4 +import sys +import os +import glob + +# converts MyAnimeList's XML exports to readable (but less informative) text files. + +animelists = glob.glob('animelist*.xml') + +for animelist in animelists: + with open(animelist, 'r') as xml, open(animelist + '.txt', 'w') as file: + soup = bs4.BeautifulSoup(xml.read(), 'html.parser') + + completed = [] + ptw = [] + movies = [] + for anime in soup.select('anime'): + line = anime.select('series_title')[0].text + ' ' + anime.select('my_watched_episodes')[0].text + '/' + anime.select('series_episodes')[0].text + '\n' + + series_type = anime.select('series_type')[0].text + status = anime.select('my_status')[0].text + + if series_type == 'Movie': + movies.append(line) + continue + + if status == 'Completed': + completed.append(line) + elif status == 'Plan to Watch': + ptw.append(line) + + for title in completed: + file.write(title) + + file.write('\n') + + for title in ptw: + file.write(title) + + file.write('\n') + + for title in movies: + file.write(title) diff --git a/python/text/youtube_subscriptions_render.py b/python/text/youtube_subscriptions_render.py index b7b088c..f702f6f 100755 --- a/python/text/youtube_subscriptions_render.py +++ b/python/text/youtube_subscriptions_render.py @@ -1,14 +1,15 @@ -#!/usr/bin/env python3 - -# converts youtube subscriptions export .xml to simple text file - -with open('subscription_manager', 'r', encoding='utf-8') as f: - while f: - line = f.readline() - if not 'channel_id' in line: - continue - - idpos = line.find('channel_id=') + len('channel_id=') - - channel_id = line[idpos:idpos + len('UC3Y4vKAzTCqSdOt0ZeYWvTg')] - print('https://www.youtube.com/channel/' + channel_id) +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# converts youtube subscriptions export .xml to simple text file + +with open('subscription_manager', 'r', encoding='utf-8') as f: + while f: + line = f.readline() + if not 'channel_id' in line: + continue + + idpos = line.find('channel_id=') + len('channel_id=') + + channel_id = line[idpos:idpos + len('UC3Y4vKAzTCqSdOt0ZeYWvTg')] + print('https://www.youtube.com/channel/' + channel_id) diff --git a/python/verify_anime_collection.py b/python/verify_anime_collection.py index 589c06b..16f3db0 100755 --- a/python/verify_anime_collection.py +++ b/python/verify_anime_collection.py @@ -1,4 +1,5 @@ -#!/bin/env python3 +#!/usr/bin/env python +# -*- coding: utf-8 -*- import sys import os diff --git a/python/web/get_7k_pp.py b/python/web/get_7k_pp.py index e744592..24476cb 100755 --- a/python/web/get_7k_pp.py +++ b/python/web/get_7k_pp.py @@ -1,47 +1,50 @@ -import requests -import json -import sys - -if len(sys.argv) < 3: - print("usage: python get_7k_pp.py [api key] [username]") - exit(0) - -api_key = sys.argv[1] -usernames = sys.argv[2:] - -def get_json(url): - page = requests.get(url) - - try: - page.raise_for_status() - except: - print("Could not get '{}'".format(url)) - return [] - - return json.loads(page.text) - -def get_user_best(api_key, username): - return get_json("https://osu.ppy.sh/api/get_user_best?k={}&limit=100&m=3&u={}".format(api_key, username)) - -def get_beatmap(api_key, id): - return get_json("https://osu.ppy.sh/api/get_beatmaps?k={}&b={}".format(api_key, id)) - -for username in usernames: - scores_7k = [] - pp_7k = 0.0 - for score in get_user_best(api_key, username): - info = get_beatmap(api_key, score["beatmap_id"])[0] - if info["diff_size"] == '7': - # theres probably a prettier solution for this - percentage = 100.0 - for num in range(len(scores_7k)): - percentage *= 0.95 - - pp_7k += float(score["pp"]) / 100.0 * percentage - scores_7k.append("[{:.2f}+ ({:.2f}%)] {:.2f} {} [{}] {}k".format(pp_7k, percentage, float(score["pp"]), info["title"], info["version"], score["score"][:3])) - - print("7k pp for '{}'".format(username)) - print("Total = {}".format(pp_7k)) - - for score_7k in scores_7k: - print(score_7k) +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import requests +import json +import sys + +if len(sys.argv) < 3: + print("usage: python get_7k_pp.py [api key] [username]") + exit(0) + +api_key = sys.argv[1] +usernames = sys.argv[2:] + +def get_json(url): + page = requests.get(url) + + try: + page.raise_for_status() + except: + print("Could not get '{}'".format(url)) + return [] + + return json.loads(page.text) + +def get_user_best(api_key, username): + return get_json("https://osu.ppy.sh/api/get_user_best?k={}&limit=100&m=3&u={}".format(api_key, username)) + +def get_beatmap(api_key, id): + return get_json("https://osu.ppy.sh/api/get_beatmaps?k={}&b={}".format(api_key, id)) + +for username in usernames: + scores_7k = [] + pp_7k = 0.0 + for score in get_user_best(api_key, username): + info = get_beatmap(api_key, score["beatmap_id"])[0] + if info["diff_size"] == '7': + # theres probably a prettier solution for this + percentage = 100.0 + for num in range(len(scores_7k)): + percentage *= 0.95 + + pp_7k += float(score["pp"]) / 100.0 * percentage + scores_7k.append("[{:.2f}+ ({:.2f}%)] {:.2f} {} [{}] {}k".format(pp_7k, percentage, float(score["pp"]), info["title"], info["version"], score["score"][:3])) + + print("7k pp for '{}'".format(username)) + print("Total = {}".format(pp_7k)) + + for score_7k in scores_7k: + print(score_7k) diff --git a/python/web/get_beatmaps.py b/python/web/get_beatmaps.py index 8c50604..3db7d8f 100644 --- a/python/web/get_beatmaps.py +++ b/python/web/get_beatmaps.py @@ -1,34 +1,37 @@ -import sys - -from selenium import webdriver - -def login(driver, username, password): - driver.get("https://osu.ppy.sh/forum/ucp.php?mode=login") - driver.find_element_by_name("username").send_keys(username) - driver.find_element_by_name("password").send_keys(password) - driver.find_element_by_name("login").click() - -def main(argv): - if len(argv) < 5: - print("Usage: {} [USERNAME] [PASSWORD] [SEARCH PARAMS] [NUM PAGES]".format(argv[0])) - return - - username = argv[1] - password = argv[2] - - search_params = argv[3].strip('"') - num_pages = int(argv[4]) - - with webdriver.Firefox() as driver: - login(driver, username, password) - - for page_nr in range(1, num_pages + 1): - if page_nr == 0: - continue - - driver.get("https://old.ppy.sh/p/beatmaplist?{}&page={}".format(search_params, page_nr)) - for beatmap_elem in driver.find_elements_by_class_name("beatmap"): - print(beatmap_elem.get_property("id")) - -if __name__ == "__main__": - main(sys.argv) +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import sys + +from selenium import webdriver + +def login(driver, username, password): + driver.get("https://osu.ppy.sh/forum/ucp.php?mode=login") + driver.find_element_by_name("username").send_keys(username) + driver.find_element_by_name("password").send_keys(password) + driver.find_element_by_name("login").click() + +def main(argv): + if len(argv) < 5: + print("Usage: {} [USERNAME] [PASSWORD] [SEARCH PARAMS] [NUM PAGES]".format(argv[0])) + return + + username = argv[1] + password = argv[2] + + search_params = argv[3].strip('"') + num_pages = int(argv[4]) + + with webdriver.Firefox() as driver: + login(driver, username, password) + + for page_nr in range(1, num_pages + 1): + if page_nr == 0: + continue + + driver.get("https://old.ppy.sh/p/beatmaplist?{}&page={}".format(search_params, page_nr)) + for beatmap_elem in driver.find_elements_by_class_name("beatmap"): + print(beatmap_elem.get_property("id")) + +if __name__ == "__main__": + main(sys.argv) diff --git a/python/web/gogdb_scaper.py b/python/web/gogdb_scaper.py index 78824ef..0901e1e 100755 --- a/python/web/gogdb_scaper.py +++ b/python/web/gogdb_scaper.py @@ -1,23 +1,24 @@ -#!/usr/bin/env python3 - -import requests # http requests -import bs4 # html parser - -with open("titles.txt", "w", encoding="UTF-8") as file: - for index in range(1, 175): - url = "https://www.gogdb.org/products?page=" + str(index) - print(url) - - page = requests.get("https://www.gogdb.org/products?page=" + str(index)) - page.raise_for_status() - - soup = bs4.BeautifulSoup(page.text, "html.parser") - - producttable = soup.select("#product-table")[0] - titles = producttable.select("tr") - for title in titles: - if len(title.select(".col-type")) == 0: - continue - - if title.select(".col-type")[0].text == 'Game': - file.write(title.select(".col-name")[0].text.strip() + '\n') +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import requests # http requests +import bs4 # html parser + +with open("titles.txt", "w", encoding="UTF-8") as file: + for index in range(1, 175): + url = "https://www.gogdb.org/products?page=" + str(index) + print(url) + + page = requests.get("https://www.gogdb.org/products?page=" + str(index)) + page.raise_for_status() + + soup = bs4.BeautifulSoup(page.text, "html.parser") + + producttable = soup.select("#product-table")[0] + titles = producttable.select("tr") + for title in titles: + if len(title.select(".col-type")) == 0: + continue + + if title.select(".col-type")[0].text == 'Game': + file.write(title.select(".col-name")[0].text.strip() + '\n') diff --git a/python/web/mal_top_fetcher.py b/python/web/mal_top_fetcher.py index 5a88613..6c901c2 100755 --- a/python/web/mal_top_fetcher.py +++ b/python/web/mal_top_fetcher.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + import requests, bs4, time def get_titles(filename, title_type, maxrank): diff --git a/python/web/pwned_checker.py b/python/web/pwned_checker.py index 27223ba..33af0da 100755 --- a/python/web/pwned_checker.py +++ b/python/web/pwned_checker.py @@ -1,137 +1,138 @@ -#!/usr/bin/env python3 - -# reads SteamIDs from ./accounts.txt and outputs ban information into ./output.html - -import urllib.request -import json -import time - -steamapikey = "" - -# read file and remove trailing newline because we're making a list -account_lines = [line.rstrip("\n") for line in open("accounts.txt").readlines()] - -ids = [] -for line in account_lines: - # https://developer.valvesoftware.com/wiki/SteamID - Z = int(line.split(':')[2]) - V = 0x0110000100000000 # profile ID constant - Y = int(line.split(':')[1]) - W = Z * 2 + V + Y - ids.append(str(W)) - -# API takes in comma seperated steamids -ids_string = ",".join([x for x in ids]) - -# https://developer.valvesoftware.com/wiki/Steam_Web_API -summaries = json.load(urllib.request.urlopen("http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=" + steamapikey + "&steamids=" + ids_string)) -bans = json.load(urllib.request.urlopen("http://api.steampowered.com/ISteamUser/GetPlayerBans/v1/?key=" + steamapikey + "&steamids=" + ids_string)) - -output_file = open("output.html", "w", encoding="utf-8") - -output_file.write('\ -\n\ -\n\ - \n\ - \n\ - \n\ -\n\ - \n\ - \n\ - \n\ - \n\ - \n\ - \n\ - \n\ - \n\ - \n\ - \n\ - \n\ -') - -numbanned = 0 - -for i in range(len(ids)): - try: - for summary in summaries['response']['players']: - if summary['steamid'] == str(ids[i]): - break - except: - continue - - try: - for ban in bans['players']: - if ban['SteamId'] == str(ids[i]): - break - except: - continue - - status = "" - bantype = "" - bandays = "" - - if ban['VACBanned']: - status = "Pwned" - bantype = "VAC" - bandays = str(ban['DaysSinceLastBan']) - numbanned += 1 - - if ban['NumberOfGameBans'] > 0: - status = "Pwned" - bantype = "Gameban" - bandays = str(ban['DaysSinceLastBan']) - numbanned += 1 - - name = summary['personaname'] - name = name.replace("<", "<") # escape html tag names - name = name.replace(">", ">") - - logdays = str(int((time.time() - summary['lastlogoff']) / 86400)) # length of a day in epoch - - line_start = ' \n') - output_file.write(line_start + '' + str(ids[i]) + '\n') - output_file.write(line_start + name + '\n') - output_file.write(line_start + status + '\n') - output_file.write(line_start + bantype + '\n') - output_file.write(line_start + bandays + '\n') - output_file.write(line_start + logdays + '\n') - output_file.write(line_start + '"+ '\n') - output_file.write(' \n') - - i += 1 - -output_file.write('\ -
IDNameStatusTypeBanDaysLogDaysProfile
' if status != "Pwned" else ' ' - - output_file.write('
\n\ - ' + str(numbanned) + '/' + str(len(ids)) + ' banned\n\ - \n\ -\ -\n') +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# reads SteamIDs from ./accounts.txt and outputs ban information into ./output.html + +import urllib.request +import json +import time + +steamapikey = "" + +# read file and remove trailing newline because we're making a list +account_lines = [line.rstrip("\n") for line in open("accounts.txt").readlines()] + +ids = [] +for line in account_lines: + # https://developer.valvesoftware.com/wiki/SteamID + Z = int(line.split(':')[2]) + V = 0x0110000100000000 # profile ID constant + Y = int(line.split(':')[1]) + W = Z * 2 + V + Y + ids.append(str(W)) + +# API takes in comma seperated steamids +ids_string = ",".join([x for x in ids]) + +# https://developer.valvesoftware.com/wiki/Steam_Web_API +summaries = json.load(urllib.request.urlopen("http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=" + steamapikey + "&steamids=" + ids_string)) +bans = json.load(urllib.request.urlopen("http://api.steampowered.com/ISteamUser/GetPlayerBans/v1/?key=" + steamapikey + "&steamids=" + ids_string)) + +output_file = open("output.html", "w", encoding="utf-8") + +output_file.write('\ +\n\ +\n\ + \n\ + \n\ + \n\ +\n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ + \n\ +') + +numbanned = 0 + +for i in range(len(ids)): + try: + for summary in summaries['response']['players']: + if summary['steamid'] == str(ids[i]): + break + except: + continue + + try: + for ban in bans['players']: + if ban['SteamId'] == str(ids[i]): + break + except: + continue + + status = "" + bantype = "" + bandays = "" + + if ban['VACBanned']: + status = "Pwned" + bantype = "VAC" + bandays = str(ban['DaysSinceLastBan']) + numbanned += 1 + + if ban['NumberOfGameBans'] > 0: + status = "Pwned" + bantype = "Gameban" + bandays = str(ban['DaysSinceLastBan']) + numbanned += 1 + + name = summary['personaname'] + name = name.replace("<", "<") # escape html tag names + name = name.replace(">", ">") + + logdays = str(int((time.time() - summary['lastlogoff']) / 86400)) # length of a day in epoch + + line_start = ' \n') + output_file.write(line_start + '' + str(ids[i]) + '\n') + output_file.write(line_start + name + '\n') + output_file.write(line_start + status + '\n') + output_file.write(line_start + bantype + '\n') + output_file.write(line_start + bandays + '\n') + output_file.write(line_start + logdays + '\n') + output_file.write(line_start + '"+ '\n') + output_file.write(' \n') + + i += 1 + +output_file.write('\ +
IDNameStatusTypeBanDaysLogDaysProfile
' if status != "Pwned" else ' ' + + output_file.write('
\n\ + ' + str(numbanned) + '/' + str(len(ids)) + ' banned\n\ + \n\ +\ +\n') diff --git a/python/web/sheethost_scraper.py b/python/web/sheethost_scraper.py index 0ca0a35..b025ce6 100755 --- a/python/web/sheethost_scraper.py +++ b/python/web/sheethost_scraper.py @@ -1,58 +1,59 @@ -#!/usr/bin/env python3 - -import requests -import bs4 -import sys - -if len(sys.argv) < 4: - print('Usage: ' + sys.argv[0] + ' [login] [password] [page name]') - exit(1) - -login = sys.argv[1] -password = sys.argv[2] -page_name = sys.argv[3] - -def download_sheet(s, url): - page = s.get(url) - - try: - page.raise_for_status() - except: - print("Couldn't get %s" % url) - return - - soup = bs4.BeautifulSoup(page.text, 'html.parser') - - links = soup.select('a') - for link in links: - if '.pdf' in link.text: - with open(link.text[1:link.text.find('.pdf') + 4], 'wb') as f: - file = s.get(link.attrs['href']) - - try: - page.raise_for_status() - except: - print("Couldn't get %s" % link.text) - return - - for chunk in file.iter_content(100000): - f.write(chunk) - -with requests.session() as s: - login = s.post('https://hi10anime.com/wp-login.php', { 'login':login, 'password':password }) - login.raise_for_status() - - if not 'You have successfully logged in. Welcome back!' in login.text: - print("Couldn't log in") - exit(1) - - page = s.get('https://sheet.host/user/%s/sheets' % page_name) - page.raise_for_status() - - soup = bs4.BeautifulSoup(page.text, 'html.parser') - - titles = soup.select('.score-title') - - for title in titles: - print('Getting %s' % title.text) - download_sheet(s, title.attrs['href']) +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import requests +import bs4 +import sys + +if len(sys.argv) < 4: + print('Usage: ' + sys.argv[0] + ' [login] [password] [page name]') + exit(1) + +login = sys.argv[1] +password = sys.argv[2] +page_name = sys.argv[3] + +def download_sheet(s, url): + page = s.get(url) + + try: + page.raise_for_status() + except: + print("Couldn't get %s" % url) + return + + soup = bs4.BeautifulSoup(page.text, 'html.parser') + + links = soup.select('a') + for link in links: + if '.pdf' in link.text: + with open(link.text[1:link.text.find('.pdf') + 4], 'wb') as f: + file = s.get(link.attrs['href']) + + try: + page.raise_for_status() + except: + print("Couldn't get %s" % link.text) + return + + for chunk in file.iter_content(100000): + f.write(chunk) + +with requests.session() as s: + login = s.post('https://hi10anime.com/wp-login.php', { 'login':login, 'password':password }) + login.raise_for_status() + + if not 'You have successfully logged in. Welcome back!' in login.text: + print("Couldn't log in") + exit(1) + + page = s.get('https://sheet.host/user/%s/sheets' % page_name) + page.raise_for_status() + + soup = bs4.BeautifulSoup(page.text, 'html.parser') + + titles = soup.select('.score-title') + + for title in titles: + print('Getting %s' % title.text) + download_sheet(s, title.attrs['href']) diff --git a/python/web/subscomru_scraper.py b/python/web/subscomru_scraper.py index 8527da2..07eb874 100755 --- a/python/web/subscomru_scraper.py +++ b/python/web/subscomru_scraper.py @@ -1,66 +1,67 @@ -#!/usr/bin/env python3 - -import requests -import bs4 - -def download_file(url): - filename = url[url.rfind('/') + 1:] - - print('Downloading %s' % filename) - - file = requests.get(url) - - try: - file.raise_for_status() - except: - open(filename + '.failed', 'w') - - with open(filename, 'wb') as f: - for chunk in file.iter_content(100000): - f.write(chunk) - - f.close() - -def get_file_name(url): - page = requests.get(url) - page.raise_for_status() - - soup = bs4.BeautifulSoup(page.text, "html.parser") - - cells = soup.select('td.even') # gay retardness - for cell in cells: - text = cell.getText() - - if '.rar' in text or '.zip' in text or '.7z' in text: - return text - - -def scrape_site(url): - # split the url to use later for constructing new urls - base_url = url[:url.rfind('/') + 1] - url = url[url.rfind('/') + 1:] - - while True: - print('Getting %s' % url) - - page = requests.get(base_url + url) - page.raise_for_status() # throw on fail - - soup = bs4.BeautifulSoup(page.text, "html.parser") - - titles = soup.select('a[title]') - for title in titles: - link = title.attrs['href'] - - if 'id' in link and not 'dl' in link: # find content links - print('Found %s' % title.attrs['title']) - download_file(base_url + 'sub/enganime/' + get_file_name(base_url + link)) - - next_link = soup.select('span.pagenav_next > a') - if len(next_link) == 0: - print('End of site') - break - - url = next_link[0].attrs['href'] - -scrape_site('http://subs.com.ru/list.php?c=enganime&p=5&w=asc&d=1') +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import requests +import bs4 + +def download_file(url): + filename = url[url.rfind('/') + 1:] + + print('Downloading %s' % filename) + + file = requests.get(url) + + try: + file.raise_for_status() + except: + open(filename + '.failed', 'w') + + with open(filename, 'wb') as f: + for chunk in file.iter_content(100000): + f.write(chunk) + + f.close() + +def get_file_name(url): + page = requests.get(url) + page.raise_for_status() + + soup = bs4.BeautifulSoup(page.text, "html.parser") + + cells = soup.select('td.even') # gay retardness + for cell in cells: + text = cell.getText() + + if '.rar' in text or '.zip' in text or '.7z' in text: + return text + + +def scrape_site(url): + # split the url to use later for constructing new urls + base_url = url[:url.rfind('/') + 1] + url = url[url.rfind('/') + 1:] + + while True: + print('Getting %s' % url) + + page = requests.get(base_url + url) + page.raise_for_status() # throw on fail + + soup = bs4.BeautifulSoup(page.text, "html.parser") + + titles = soup.select('a[title]') + for title in titles: + link = title.attrs['href'] + + if 'id' in link and not 'dl' in link: # find content links + print('Found %s' % title.attrs['title']) + download_file(base_url + 'sub/enganime/' + get_file_name(base_url + link)) + + next_link = soup.select('span.pagenav_next > a') + if len(next_link) == 0: + print('End of site') + break + + url = next_link[0].attrs['href'] + +scrape_site('http://subs.com.ru/list.php?c=enganime&p=5&w=asc&d=1') diff --git a/web-vanilla/index.html b/web-vanilla/index.html index af90b37..1a3f688 100644 --- a/web-vanilla/index.html +++ b/web-vanilla/index.html @@ -1,15 +1,15 @@ - - - -JavaScript - - - - -

HTML/JavaScript mini-projects

- -
    -
  • Reaction test
  • -
  • Trill test
  • -
  • osu!mania playfield simulator
  • + + + +JavaScript + + + + +

    HTML/JavaScript mini-projects

    + + \ No newline at end of file diff --git a/web-vanilla/mania/mania.html b/web-vanilla/mania/mania.html index 27b87cf..414420e 100644 --- a/web-vanilla/mania/mania.html +++ b/web-vanilla/mania/mania.html @@ -1,36 +1,36 @@ - - - -osu!mania playfield - - - - - - - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - -
    - + + + +osu!mania playfield + + + + + + + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    +
    \ No newline at end of file diff --git a/web-vanilla/mania/mania.js b/web-vanilla/mania/mania.js index 2c37d6e..8a2aa8d 100644 --- a/web-vanilla/mania/mania.js +++ b/web-vanilla/mania/mania.js @@ -1,112 +1,112 @@ -var canvas; -var ctx; - -/* Base resolution in osu! skins used for scaling */ -const internal_width = 640; -const internal_height = 480; - -/* Updates per second */ -const framerate = 60; - -/* Colors for playfield */ -const colors = { - playfield: "#000", - lane_separator: "#222", - hitposition: "#AA0", - note: "#FFF" -}; - -/* Values retrieved from DOM */ -var options = { - columnstart: 0, - columnwidth: 0, - hitposition: 0, - scrollspeed: 0, - noteratio: 0, - keycount: 0 -} - -/* Clears the canvas */ -function clear() { - ctx.clearRect(0, 0, canvas.width, canvas.height); -} - -/* Draws a rectangle */ -function rect(x, y, w, h, color) { - ctx.fillStyle = color; - ctx.fillRect(x, y, w, h); -} - -/* Draws a string */ -function text(string, x, y, color) { - ctx.fillStyle = color; - ctx.fillText(string, x, y); -} - -/* Translates osu! coordinates to screen coordinates */ -function translate_pos(x, y) { - return { - x: Math.floor(x / internal_width * canvas.width), - y: Math.floor(y / internal_height * canvas.height) - } -} - -function draw() { - clear() - - const columnstart = translate_pos(options.columnstart, 0); - const columnwidth = translate_pos(options.columnwidth, 0); - const hitposition = translate_pos(options.columnstart, options.hitposition); - - const playfield_width = columnwidth.x * options.keycount; - const note_height = columnwidth.x * options.noteratio; - - // playfield background - rect(columnstart.x, 0, playfield_width, canvas.height, colors.playfield); - - // lane separators - for (var i = 0; i <= options.keycount; i++) { - rect(columnstart.x + i * columnwidth.x, 0, 1, canvas.height, colors.lane_separator); - } - - // hitpos - rect(hitposition.x, hitposition.y, playfield_width, 5, colors.hitposition); - - // notes - let y_pos = hitposition.y - note_height; - while (y_pos > 0) { - for (let i = 0; i < options.keycount; ++i) { - rect ( - columnstart.x + i * columnwidth.x, - y_pos, - columnwidth.x, note_height, colors.note - ); - - // completely inaccurate but i cba to simulate the whole playfield - y_pos -= options.scrollspeed * 3; - } - } -} - -window.onload = function () { - create_prologue(); - - canvas = document.getElementById("playfield"); - ctx = canvas.getContext("2d"); - - ctx.font = "15px Arial"; - ctx.textBaseline = "hanging"; - - for (let input of document.getElementsByTagName("input")) { - input.onchange = function () { - options[this.id] = this.value; - document.getElementById(this.id + "_out").innerText = this.value; - }; - - input.onchange(); - } - - setInterval(function () { - draw(); - }, 1000 / framerate); -}; +var canvas; +var ctx; + +/* Base resolution in osu! skins used for scaling */ +const internal_width = 640; +const internal_height = 480; + +/* Updates per second */ +const framerate = 60; + +/* Colors for playfield */ +const colors = { + playfield: "#000", + lane_separator: "#222", + hitposition: "#AA0", + note: "#FFF" +}; + +/* Values retrieved from DOM */ +var options = { + columnstart: 0, + columnwidth: 0, + hitposition: 0, + scrollspeed: 0, + noteratio: 0, + keycount: 0 +} + +/* Clears the canvas */ +function clear() { + ctx.clearRect(0, 0, canvas.width, canvas.height); +} + +/* Draws a rectangle */ +function rect(x, y, w, h, color) { + ctx.fillStyle = color; + ctx.fillRect(x, y, w, h); +} + +/* Draws a string */ +function text(string, x, y, color) { + ctx.fillStyle = color; + ctx.fillText(string, x, y); +} + +/* Translates osu! coordinates to screen coordinates */ +function translate_pos(x, y) { + return { + x: Math.floor(x / internal_width * canvas.width), + y: Math.floor(y / internal_height * canvas.height) + } +} + +function draw() { + clear() + + const columnstart = translate_pos(options.columnstart, 0); + const columnwidth = translate_pos(options.columnwidth, 0); + const hitposition = translate_pos(options.columnstart, options.hitposition); + + const playfield_width = columnwidth.x * options.keycount; + const note_height = columnwidth.x * options.noteratio; + + // playfield background + rect(columnstart.x, 0, playfield_width, canvas.height, colors.playfield); + + // lane separators + for (var i = 0; i <= options.keycount; i++) { + rect(columnstart.x + i * columnwidth.x, 0, 1, canvas.height, colors.lane_separator); + } + + // hitpos + rect(hitposition.x, hitposition.y, playfield_width, 5, colors.hitposition); + + // notes + let y_pos = hitposition.y - note_height; + while (y_pos > 0) { + for (let i = 0; i < options.keycount; ++i) { + rect ( + columnstart.x + i * columnwidth.x, + y_pos, + columnwidth.x, note_height, colors.note + ); + + // completely inaccurate but i cba to simulate the whole playfield + y_pos -= options.scrollspeed * 3; + } + } +} + +window.onload = function () { + create_prologue(); + + canvas = document.getElementById("playfield"); + ctx = canvas.getContext("2d"); + + ctx.font = "15px Arial"; + ctx.textBaseline = "hanging"; + + for (let input of document.getElementsByTagName("input")) { + input.onchange = function () { + options[this.id] = this.value; + document.getElementById(this.id + "_out").innerText = this.value; + }; + + input.onchange(); + } + + setInterval(function () { + draw(); + }, 1000 / framerate); +}; diff --git a/web-vanilla/reaction/reaction.html b/web-vanilla/reaction/reaction.html index 8e47f5f..91b45cd 100644 --- a/web-vanilla/reaction/reaction.html +++ b/web-vanilla/reaction/reaction.html @@ -1,14 +1,14 @@ - - - -Reaction time test - - - - - - - -
    -

    + + + +Reaction time test + + + + + + + +
    +

      \ No newline at end of file diff --git a/web-vanilla/reaction/reaction.js b/web-vanilla/reaction/reaction.js index 3648df9..b3d71ea 100644 --- a/web-vanilla/reaction/reaction.js +++ b/web-vanilla/reaction/reaction.js @@ -1,88 +1,88 @@ - -const states = { - START: 'start', - TIMEOUT: 'timeout', - READY: 'ready' -} - -const colors = { - TIMEOUT: 'crimson', - START: 'dodgerblue', - READY: 'limegreen' -} - -var state = states.START; -var times = []; - -var ready_time; -var ready_id; // timeout id - -const min_time = 1000; -const max_time = 3000; - -function handle_click() { - switch (state) { - // click received on start, activate timer and set state to timeout - case states.START: { - - // this is a hack to access thisptr from setTimeout - let self = this; - - ready_id = setTimeout(function () { - ready_time = Date.now(); - - self.style["background-color"] = colors.READY; - self.innerText = "Click now!"; - - state = states.READY; - }, min_time + (Math.random() * (max_time - min_time))); - - this.style["background-color"] = colors.TIMEOUT; - this.innerText = "Get ready!"; - - state = states.TIMEOUT; - } break; - - // click received during timeout, clear timeout and reset - case states.TIMEOUT: { - clearTimeout(ready_id); - - this.style["background-color"] = colors.START; - this.innerText = "Too early!"; - - state = states.START; - } break; - - // click received after timeout, calculate score and reset - case states.READY: { - let time = (Date.now() - ready_time); - times.push(time); - - let average = times.reduce((a, b) => a + b) / times.length; - - // update average text - document.getElementById("average").innerText = "Average: " + average + "ms"; - - // create and add time listing - var li = document.createElement("li"); - li.appendChild(document.createTextNode(time + "ms")); - document.getElementById("times").appendChild(li); - - this.style["background-color"] = colors.START; - this.innerText = time + "ms"; - - state = states.START; - } break; - - default: - break - } -} - -window.onload = function () { - create_prologue(); - - let area = document.getElementById("area"); - area.innerText = "Click here to begin!"; - area.onclick = handle_click; -} + +const states = { + START: 'start', + TIMEOUT: 'timeout', + READY: 'ready' +} + +const colors = { + TIMEOUT: 'crimson', + START: 'dodgerblue', + READY: 'limegreen' +} + +var state = states.START; +var times = []; + +var ready_time; +var ready_id; // timeout id + +const min_time = 1000; +const max_time = 3000; + +function handle_click() { + switch (state) { + // click received on start, activate timer and set state to timeout + case states.START: { + + // this is a hack to access thisptr from setTimeout + let self = this; + + ready_id = setTimeout(function () { + ready_time = Date.now(); + + self.style["background-color"] = colors.READY; + self.innerText = "Click now!"; + + state = states.READY; + }, min_time + (Math.random() * (max_time - min_time))); + + this.style["background-color"] = colors.TIMEOUT; + this.innerText = "Get ready!"; + + state = states.TIMEOUT; + } break; + + // click received during timeout, clear timeout and reset + case states.TIMEOUT: { + clearTimeout(ready_id); + + this.style["background-color"] = colors.START; + this.innerText = "Too early!"; + + state = states.START; + } break; + + // click received after timeout, calculate score and reset + case states.READY: { + let time = (Date.now() - ready_time); + times.push(time); + + let average = times.reduce((a, b) => a + b) / times.length; + + // update average text + document.getElementById("average").innerText = "Average: " + average + "ms"; + + // create and add time listing + var li = document.createElement("li"); + li.appendChild(document.createTextNode(time + "ms")); + document.getElementById("times").appendChild(li); + + this.style["background-color"] = colors.START; + this.innerText = time + "ms"; + + state = states.START; + } break; + + default: + break + } +} + +window.onload = function () { + create_prologue(); + + let area = document.getElementById("area"); + area.innerText = "Click here to begin!"; + area.onclick = handle_click; +} diff --git a/web-vanilla/style.css b/web-vanilla/style.css index 270eb97..2238a97 100644 --- a/web-vanilla/style.css +++ b/web-vanilla/style.css @@ -1,13 +1,13 @@ -body { - color: #444; - font: 18px/1.6 Arial, Helvetica, sans-serif; - margin: 40px auto; - max-width: 650px; - padding: 0 10px; -} - -h1, -h2, -h3 { - line-height: 1.2 -} +body { + color: #444; + font: 18px/1.6 Arial, Helvetica, sans-serif; + margin: 40px auto; + max-width: 650px; + padding: 0 10px; +} + +h1, +h2, +h3 { + line-height: 1.2 +} diff --git a/web-vanilla/trill/trill.html b/web-vanilla/trill/trill.html index 424a55c..10ac22e 100644 --- a/web-vanilla/trill/trill.html +++ b/web-vanilla/trill/trill.html @@ -1,14 +1,14 @@ - - - -Trill test - - - - - - - -
      -

      + + + +Trill test + + + + + + + +
      +

        \ No newline at end of file diff --git a/web-vanilla/trill/trill.js b/web-vanilla/trill/trill.js index e85b5e4..8380285 100644 --- a/web-vanilla/trill/trill.js +++ b/web-vanilla/trill/trill.js @@ -1,102 +1,102 @@ - -const states = { - GETKEYS: 'getkeys', - TRILLING: 'trilling', - READY: 'ready', - RESULT: 'result' -} - -const colors = { - GETKEYS: 'gray', - TRILLING: 'crimson', - READY: 'darkgray', - RESULT: 'dodgerblue' -} - -var state = states.GETKEYS; -var times = [] - -const max_count = 20; - -var key1, key2; -var time_start; -var counter = 0; - -function handle_press(e) { - switch (state) { - case states.GETKEYS: { - if (!key1) { - key1 = e.code; - - area.style["background-color"] = colors.GETKEYS; - area.innerText = "Press 2nd key!"; - } else { - key2 = e.code; - state = states.READY; - - area.style["background-color"] = colors.READY; - area.innerText = "Test will start on first keypress."; - } - } break; - - case states.READY: { - if (e.code == key1 || e.code == key2) { - state = states.TRILLING; - - time_start = Date.now(); - - area.style["background-color"] = colors.TRILLING; - handle_press(e); // update couter/text for first keypress - } - } break; - - case states.TRILLING: { - if (e.code == key1 || e.code == key2) { - counter++; - } - - area.innerText = counter + "/" + max_count; - - if (counter == max_count) { - state = states.RESULT; - - counter = 0; - - let time = parseInt((max_count / ((Date.now() - time_start) / 1000) * 60 / 4)); - times.push(time); - - let average = times.reduce((a, b) => a + b) / times.length; - - document.getElementById("average").innerText = "Average: " + average + "bpm"; - - var li = document.createElement("li"); - li.appendChild(document.createTextNode(time + "bpm")); - document.getElementById("times").appendChild(li); - - area.style["background-color"] = colors.RESULT; - area.innerText = time + "bpm" + '\n(press space to try again)'; - } - } break; - - case states.RESULT: { - if (e.code == "Space") { - state = states.READY; - - area.style["background-color"] = colors.READY; - area.innerText = "Test will start on first keypress."; - } - } break; - - default: - break - } -} - -window.onload = function () { - create_prologue(); - - area = document.getElementById("area"); - area.innerText = "Press 1st key!"; - - document.addEventListener('keydown', handle_press); -} + +const states = { + GETKEYS: 'getkeys', + TRILLING: 'trilling', + READY: 'ready', + RESULT: 'result' +} + +const colors = { + GETKEYS: 'gray', + TRILLING: 'crimson', + READY: 'darkgray', + RESULT: 'dodgerblue' +} + +var state = states.GETKEYS; +var times = [] + +const max_count = 20; + +var key1, key2; +var time_start; +var counter = 0; + +function handle_press(e) { + switch (state) { + case states.GETKEYS: { + if (!key1) { + key1 = e.code; + + area.style["background-color"] = colors.GETKEYS; + area.innerText = "Press 2nd key!"; + } else { + key2 = e.code; + state = states.READY; + + area.style["background-color"] = colors.READY; + area.innerText = "Test will start on first keypress."; + } + } break; + + case states.READY: { + if (e.code == key1 || e.code == key2) { + state = states.TRILLING; + + time_start = Date.now(); + + area.style["background-color"] = colors.TRILLING; + handle_press(e); // update couter/text for first keypress + } + } break; + + case states.TRILLING: { + if (e.code == key1 || e.code == key2) { + counter++; + } + + area.innerText = counter + "/" + max_count; + + if (counter == max_count) { + state = states.RESULT; + + counter = 0; + + let time = parseInt((max_count / ((Date.now() - time_start) / 1000) * 60 / 4)); + times.push(time); + + let average = times.reduce((a, b) => a + b) / times.length; + + document.getElementById("average").innerText = "Average: " + average + "bpm"; + + var li = document.createElement("li"); + li.appendChild(document.createTextNode(time + "bpm")); + document.getElementById("times").appendChild(li); + + area.style["background-color"] = colors.RESULT; + area.innerText = time + "bpm" + '\n(press space to try again)'; + } + } break; + + case states.RESULT: { + if (e.code == "Space") { + state = states.READY; + + area.style["background-color"] = colors.READY; + area.innerText = "Test will start on first keypress."; + } + } break; + + default: + break + } +} + +window.onload = function () { + create_prologue(); + + area = document.getElementById("area"); + area.innerText = "Press 1st key!"; + + document.addEventListener('keydown', handle_press); +}