diff --git a/.gitignore b/.gitignore index 9f30eba..2de334c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ _test*.py *.txt *.json /SourceHanSans-Regular.otf -/_old \ No newline at end of file +/_old +*.mpd \ No newline at end of file diff --git a/README.md b/README.md index 3004895..f5976bc 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,12 @@ Some small Python utilities. +To use them, make sure you also downloaded `util.py` file from the same directory (or better, just clone the whole repo). + + ## `instalive.py` -Instagram live downloader. Feed in MPD URL (including all the query parameters!) within 24 hour and it can download the full live stream from the beginning. +Instagram live downloader. Feed in MPD URL (including all the query parameters!) within ~~24~~ (a few) hours and it can download the full live stream from the beginning. Usage: @@ -12,13 +15,13 @@ Usage: usage: instalive.py [-h] [--action ACTION] [--dir DIR] [--debug] [--quality QUALITY] [--time TIME] [--range RANGE] url Available actions: - all - Download both video and audio, and merge them (default) - live - Download the live stream only + all - Download both video and audio (including live and backtracking), and then merge them (default) + live - Download the live stream only (no backtracking) video - Download video only audio - Download audio only merge - Merge downloaded video and audio - check - Check the downloaded segments to make sure there is no missing segments - manual - Manually process a specified range (used together with --range) + check - Check the downloaded segments to make sure there are no missing segments + manual - Manually check missing segments at the largest gap (or use --range to assign a range) (for debugging only) info - Display downloader object info import: - Import segments downloaded via N_m3u8DL-RE from a given path @@ -29,16 +32,21 @@ options: -h, --help show this help message and exit --action ACTION, -a ACTION action to perform (default: all) - --dir DIR, -d DIR save path (default: CWD) + --dir DIR, -d DIR save path (default: instalive_{mpd_id}) --debug debug mode --quality QUALITY, -q QUALITY - manually assign video quality (default: auto) - --time TIME, -t TIME manually assign last t (default: auto) - --range RANGE manually assign range (start,end) for quick iterate test mode + manually assign video quality by quality name. + (default: "pst": which is the "original" (?) and has the best bitrate, + but not necessarily the highest resolution. + Pass empty string or "highest" to use the highest resolution one.) + --time TIME, -t TIME for debugging only; manually assign last t (default: auto) + --range RANGE for debugging only; manually assign iteration range (start,end) for manual action ``` ## `oricon.py` +**Deprecated: You can no longer get original size of images from Oricon website AFAIK. The random quality toggling because of CDN is also gone (?). This can still be used to download the images in the size shown on the webpage, though.** + Quickly download all the highest quality pictures from any Oricon news, special or photo article. Usage: @@ -53,8 +61,6 @@ oricon.py https://www.oricon.co.jp/news/2236438/ Usage: -Note: make sure you also downloaded `util.py` file from the same directory. - CLI: ``` @@ -105,6 +111,8 @@ downloader.getPostPhotos(12345) ## `scraper_radiko.py` +**Deprecated: just use yt-dlp or even better, with [yt-dlp-rajiko](https://github.com/garret1317/yt-dlp-rajiko) plugin.** + Note: you need to prepare the JP proxy yourself. Usage: @@ -128,17 +136,21 @@ e.parse() Nico Timeshift downloader. Download both video and comments. Also can download thumbnail from normal video. Downloading for normal video isn't supported (yet). -Install these first: ``` pip install browser-cookie3 websocket-client rich python-dateutil pytz requests beautifulsoup4 lxml -npm i minyami -g ``` +The actual downloading is delegated to [minyami](https://github.com/Last-Order/Minyami) and/or [N_m3u8DL-RE](https://github.com/nilaoda/N_m3u8DL-RE), so make sure you have them installed first and available in your PATH. + +It also can reads the arguments from a `nico.txt` file in the CWD or the same directory as the script. Syntax: just put all the arguments in one line. + CLI: ``` -usage: nico.py [-h] [--verbose] [--info] [--dump] [--thumb] [--cookies COOKIES] [--comments {yes,no,only}] [--proxy PROXY] [--save-dir SAVE_DIR] [--reserve] url +usage: nico.py [-h] [--verbose] [--info] [--dump] [--thumb] [--cookies COOKIES] [--comments {yes,no,only}] [--proxy PROXY] [--save-dir SAVE_DIR] [--reserve] + [--simulate] + url positional arguments: url URL or ID of nicovideo webpage @@ -150,17 +162,18 @@ options: --dump Dump all the metadata to json files. --thumb Download thumbnail only. Only works for video type (not live type). --cookies COOKIES, -c COOKIES - Cookie source. [Default: chrome] + Cookie source. Provide either: - A browser name to fetch from; - The value of "user_session"; - A Netscape-style cookie file. --comments {yes,no,only}, -d {yes,no,only} - Control if comments (danmaku) are downloaded. [Default: yes] + Control if comments (danmaku) are downloaded. [Default: no] --proxy PROXY Specify a proxy, "none", or "auto" (automatically detects system proxy settings). [Default: auto] --save-dir SAVE_DIR, -o SAVE_DIR Specify the directory to save the downloaded files. [Default: current directory] --reserve Automatically reserve timeshift ticket if not reserved yet. [Default: no] + --simulate Simulate the download process without actually downloading. ``` ## `util.py` @@ -169,8 +182,23 @@ Some utility functions mainly for myself. Read the code to get the idea. Some highlights: -* download - a generic downloader -* format_str - format a string to certain width and alignment. Supports wide characters like Chinese and Japanese. -* compare_obj - recursively compare two objects. -* Table - a simple table class. -* parse_to_shortdate - parse a date string to a short date string ('%y%m%d'). Supports more East Asian language formats than `dateutil.parser`. \ No newline at end of file +**Network & Web Scraping:** +* **download** - a comprehensive file downloader with retry logic, duplicate handling (skip/overwrite/rename), referer support, and automatic filename detection from URLs or response headers +* **get** - a convenience wrapper around requests that returns a BeautifulSoup object with retry logic built-in +* **requests_retry_session** - create a requests session with automatic retry on network failures +* **load_cookie** - load cookies from browser (Chrome/Firefox/Edge), cookie files (Netscape format), or cookie strings + +**File Operations:** +* **safeify** - sanitize filenames by replacing illegal characters with full-width equivalents (yes I know safeify isn't a real word. It was blindly copied from another project and It was too much effort to change it) +* **move_or_delete_duplicate** - move files with smart duplicate detection via hash comparison +* **dump_json/load_json** - convenient JSON file I/O with proper encoding and formatting + +**Data Structures & Formatting:** +* **Table** - a simple table class with sorting, searching, filtering, and pretty printing capabilities +* **format_str** - format a string to certain width and alignment. Supports wide characters like Chinese and Japanese +* **compare_obj** - recursively compare two objects (dicts, lists, etc.) and print differences with rich formatting +* **rprint** - print function using the rich library for colored and styled terminal output + +**Date & Time:** +* **MyTime** - a datetime wrapper that makes timezone conversions easy (local, JST, naive) +* **parse_to_shortdate** - parse a date string to a short date string ('%y%m%d'). Supports more East Asian language formats than `dateutil.parser` \ No newline at end of file diff --git a/instalive.py b/instalive.py index 59e02e9..c164def 100644 --- a/instalive.py +++ b/instalive.py @@ -5,12 +5,15 @@ import xml.etree.ElementTree as ET from datetime import datetime from pathlib import Path -from shutil import copy2, copyfileobj +from shutil import copy2, copyfileobj, get_terminal_size from subprocess import run -from urllib.parse import unquote, urljoin +from urllib.parse import urljoin -import requests from tqdm import tqdm +from util import requests_retry_session, get_webname, td_format + + +TOLERANCE = 0.2 def parse_iso8601_duration(duration): @@ -30,6 +33,13 @@ def parse_iso8601_duration(duration): total_seconds = (days * 86400) + (hours * 3600) + (minutes * 60) + seconds return total_seconds + +def print_full_width(s): + '''Print a string to full width of the terminal.''' + width = get_terminal_size().columns + print('\r' + s.ljust(width), end='') + + def concat(files, output, verbose=False): '''Concatenate files into one file.''' output = Path(output) @@ -45,40 +55,18 @@ def concat(files, output, verbose=False): fi.close() out.close() -def get_webname(url): - return unquote(url.split('?')[0].split('/')[-1]) - -def td_format(td): - seconds = int(td.total_seconds()) - in_future = seconds > 0 - seconds = abs(seconds) - periods = [ - ('year', 60*60*24*365), - ('month', 60*60*24*30), - ('day', 60*60*24), - ('hour', 60*60), - ('minute', 60), - ('second', 1) - ] - strings=[] - for period_name, period_seconds in periods: - if seconds > period_seconds: - period_value , seconds = divmod(seconds, period_seconds) - has_s = 's' if period_value > 1 else '' - strings.append(f"{period_value} {period_name}{has_s}") - if in_future: - return 'in ' + ', '.join(strings) - else: - return ', '.join(strings) + ' ago' - class InstaliveDownloader: def __init__(self, url, save_path, debug=False, quality=None): - self.session = requests.Session() + self.session = requests_retry_session() self.url = url + if save_path is None: + mpd_name = get_webname(url).split('.')[0] + save_path = f'instalive_{mpd_name}' self.save_path = Path(save_path) if not self.save_path.exists(): self.save_path.mkdir(parents=True) + assert self.save_path.is_dir(), f'{self.save_path} is not a directory!' self.debug = debug if debug: print('Debug mode enabled. Only download 30 segments.') @@ -101,7 +89,13 @@ def _download(self, url, save_path=None, filename=None, skip_existing=True): def save_mpd(self): print('Save mpd to local file...') - (self.save_path/'mpd.mpd').write_text(self.mpd_text, encoding='utf-8') + mpd_lines = self.mpd_text.splitlines(keepends=True) + comment = f'\n' + if mpd_lines and mpd_lines[0].strip().startswith(' end_id else 1)) - ids.sort(key=lambda x: abs(x - x)) + ids.sort(key=lambda k: abs(k - x)) return ids while True: valid_id = None # firstly, we try to find if existing local file that is close to the guesses[0]. - # which is defined as +/- 10% of the interval. + # which is defined as +/- TOLERANCE (default: 20%) of the interval. + # this is a "narrower" window than surrounding() function. # notice that we don't need to try all the guesses, they will be checked in the # next step. - for candidate in range(id_guesses[0] - int(self.interval*0.1), id_guesses[0] + int(self.interval*0.1) + 1): + ids = list(range(id_guesses[0] - int(self.interval*TOLERANCE), id_guesses[0] + int(self.interval*TOLERANCE) + 1)) + ids.sort(key=lambda k: abs(k - id_guesses[0])) + # only try ids that are before the prev_id (if backward) or after the prev_id (if forward). + if prev_id: + ids = [id for id in ids if (id - prev_id) * sign > 0] + for candidate in ids: url = self.video_url_template.format(candidate) - f = self.save_path/'video'/ get_webname(url) + f = self.save_path_video / get_webname(url) if f.exists() and f.stat().st_size > 0: valid_id = candidate - print(f'\rSegment {valid_id}: {f} already exists. Skip. ', end='') + print_full_width(f'Segment {valid_id}: {f} already exists. Skip.') break # if not found locally, we try to fetch the segment by id from the all # (last_valid_id - potential_interval) pools. # The order matters: we try the most common interval first. - # this is single-threaded, because it is more costly to close all the threads - # if we initiate them altogether.. + # this is single-threaded, because it is more time-consuming trying to close all the threads. if not valid_id: for candidate in id_guesses: status = self.fetch_video_by_id(candidate) - print(f'\rSegment {candidate}: HTTP {status} ', end='') + print_full_width(f'Segment {candidate}: HTTP {status}') if status in [200, 'Exists']: valid_id = candidate break # If still not found, we iterate around the guesses[0] to find the next segment. - # the range is defined as 60% behind of x and 110% ahead of x, - # e.g. for 2000 interval, it would be last_id - 800 to last_id - 4200, sorted by - # distance to last_id - 2000. + # the range is defined as 50%+TOLERANCE behind of x and 100%+TOLERANCE ahead of x, + # where x is the guesses[0]. + # e.g. for 2000 interval, x = last_id - 2000, the range would be + # last_id - 800 to last_id - 4400, sorted by distance to last_id - 2000. # notice that it covers up to the range of next next id, this way we ensure we still # continue the downloading instead of stopping too early (despite missing a segment). if not valid_id: ids = [id for id in surrounding(id_guesses[0]) if id not in id_guesses] + if prev_id: + ids = [id for id in ids if (id - prev_id) * sign > 0] valid_id, status = self.quick_iterate(ids) # if still not, we assume we downloaded them all and stop. if not valid_id: print("\nFailed to find next segment. Assume we downloaded all. Stop.") break - print(f'\rSegment {valid_id}: HTTP {status} ', end='') + print_full_width(f'Segment {valid_id}: HTTP {status}') # at this point, we should have a valid_id. assert valid_id # add new interval to known_intervals if prev_id: new_interval = abs(valid_id - prev_id) + assert new_interval > 0 # this should not happen. # do not add new interval if it is too different from the current interval. # for example, if the nominal interval is 2000, we should only add ones that are # less than 3000. Otherwise we enables the possibility of skipping segments. @@ -355,9 +388,8 @@ def surrounding(x): id_guesses = [valid_id + interval * sign for interval in known_intervals] def check(self): - print('Check if there is any missing segment...') - p = self.save_path / 'video' - files = list(p.iterdir()) + print('Check if there is any missing video segment...') + files = list(self.save_path_video.iterdir()) # filename format: 17981336783244063_0-1297029.m4v ids = [int(m[1]) for f in files if (m := re.search(r'\d+_0-(\d+)', f.name))] ids.sort() @@ -368,22 +400,22 @@ def check(self): for diff in diffs: diff_count[diff] = diff_count.get(diff, 0) + 1 print(f'Count of diff values between each segment:', diff_count) + max_idx = diffs.index(max(diffs)) + print(f'Largest ID diff between each segment: {max(diffs)}, at {ids[max_idx]} -> {ids[max_idx+1]}') + min_idx = diffs.index(min(diffs)) + print(f'Smallest ID diff between each segment: {min(diffs)}, at {ids[min_idx]} -> {ids[min_idx+1]}') - if max(diffs) > self.interval * 1.1: - idx = diffs.index(max(diffs)) - worst = (ids[idx], ids[idx+1]) - print(f'Largest ID diff between each segment: {max(diffs)}, at {ids[idx]} -> {ids[idx+1]}') - idx = diffs.index(min(diffs)) - print(f'Smallest ID diff between each segment: {min(diffs)}, at {ids[idx]} -> {ids[idx+1]}') + # Check if there are any missing segments, defined as gaps larger than 1.2 times the interval. + if max(diffs) > self.interval * (1+TOLERANCE): print(f'It is likely that the video is not fully downloaded!!') - return worst + return (ids[max_idx], ids[max_idx+1]) else: print('No missing segment found.') + def download_audio(self): print('Downloading audio segments...') - audio_save_path = self.save_path / 'audio' - files = list((self.save_path / 'video').iterdir()) + files = list(self.save_path_video.iterdir()) with concurrent.futures.ThreadPoolExecutor(max_workers=20) as ex: futures = [] count = 0 @@ -394,7 +426,7 @@ def download_audio(self): continue id = re.search(r'_0-(init|\d+)\.m4v', f.name)[1] url = self.audio_url_template.format(id) - futures.append(ex.submit(self._download, url, save_path=audio_save_path)) + futures.append(ex.submit(self._download, url, save_path=self.save_path_audio)) for _ in concurrent.futures.as_completed(futures): count += 1 print(f'Finished {count}/{len(futures)} ', end='\r') @@ -407,16 +439,17 @@ def get_key(f): return int(re.search(r'\d+_0-(\d+)', f.name)[1]) video_file = self.save_path / 'video.m4v' - files = list((self.save_path / 'video').iterdir()) + files = list(self.save_path_video.iterdir()) print(f'Find {len(files)} video segments. Merging...') files.sort(key=get_key) concat(files, video_file) audio_file = self.save_path / 'audio.m4a' - files2 = list((self.save_path / 'audio').iterdir()) + files2 = list(self.save_path_audio.iterdir()) print(f'Find {len(files2)} audio segments. Merging...') files2.sort(key=get_key) concat(files2, audio_file) + print(f'Merging video and audio using FFMPEG...') run(['ffmpeg', '-loglevel', 'error', '-stats', '-i', video_file, '-i', audio_file, '-c', 'copy', self.save_path/'merged.mp4']) @@ -427,15 +460,15 @@ def import_segments(self, path): if p.is_dir() and (p / '_init.mp4').exists(): if 'avc' in p.name: template = self.video_url_template - type_ = 'video' + save_path = self.save_path_video else: template = self.audio_url_template - type_ = 'audio' + save_path = self.save_path_audio for f in p.iterdir(): if f.stem.isdigit(): id = int(f.stem) new_filename = get_webname(template.format(id)) - newf = self.save_path / type_ / new_filename + newf = save_path / new_filename if newf.exists(): if newf.stat().st_size == f.stat().st_size: print(f'{id}: {newf} already exists. Skip.') @@ -456,6 +489,8 @@ def main(url, save_path, time, debug, action, quality): return if action == 'info': for k, v in downloader.__dict__.items(): + if k == 'mpd_text': # too long. + v = v[:10] + '...' + v[-10:] print(f'{k}: {v}') return try: @@ -512,31 +547,30 @@ def task1(): if __name__ == '__main__': import argparse - class CustomHelpFormatter(argparse.RawTextHelpFormatter): - pass - parser = argparse.ArgumentParser( description='Available actions:\n' - ' all - Download both video and audio, and merge them (default)\n' - ' live - Download the live stream only\n' + ' all - Download both video and audio (including live and backtracking), and then merge them (default)\n' + ' live - Download the live stream only (no backtracking)\n' ' video - Download video only\n' ' audio - Download audio only\n' ' merge - Merge downloaded video and audio\n' - ' check - Check the downloaded segments to make sure there is no missing segments\n' - ' manual - Manually process a specified range (used together with --range)\n' + ' check - Check the downloaded segments to make sure there are no missing segments\n' + ' manual - Manually check missing segments at the largest gap (or use --range to assign a range) (for debugging only)\n' ' info - Display downloader object info\n' ' import: - Import segments downloaded via N_m3u8DL-RE from a given path', - formatter_class=CustomHelpFormatter + formatter_class=argparse.RawTextHelpFormatter ) - parser.add_argument("url", help="url of mpd") parser.add_argument("--action", '-a', default='all', help="action to perform (default: all)") - parser.add_argument("--dir", "-d", default='.', help="save path (default: CWD)") + parser.add_argument("--dir", "-d", help="save path (default: instalive_{mpd_id})") parser.add_argument("--debug", action='store_true', help="debug mode") - parser.add_argument("--quality", "-q", help="manually assign video quality (default: auto)") - parser.add_argument("--time", "-t", help="manually assign last t (default: auto)") - parser.add_argument('--range', help='manually assign range (start,end) for quick iterate test mode') + parser.add_argument("--quality", "-q", default='pst', help="manually assign video quality by quality name.\n" + "(default: \"pst\": which is the \"original\" (?) and has the best bitrate,\n" + "but not necessarily the highest resolution.\n" + "Pass empty string or \"highest\" to use the highest resolution one.)") + parser.add_argument("--time", "-t", help="for debugging only; manually assign last t (default: auto)") + parser.add_argument('--range', help='for debugging only; manually assign iteration range (start,end) for manual action') args = parser.parse_args() diff --git a/nico.py b/nico.py index f21cac9..544ac0c 100644 --- a/nico.py +++ b/nico.py @@ -6,40 +6,52 @@ from urllib.parse import urljoin, urlparse from urllib.request import getproxies from pathlib import Path - -import browser_cookie3 import websocket from rich.console import Console from rich.table import Table + from util import download, dump_json, get, MyTime, requests_retry_session, safeify, to_jp_time, load_cookie +from proto.dwango.nicolive.chat.service.edge import payload_pb2 as chat +import google.protobuf.json_format console = Console() print = console.print +# based on https://github.com/rinsuki-lab/ndgr-reader/blob/main/src/protobuf-stream-reader.ts +def read_protobuf_message(data): + offset = 0 + result = 0 + i = 0 + while True: + if offset >= len(data): + return None + current = data[offset] + result |= (current & 0x7F) << i + offset += 1 + i += 7 + if not (current & 0x80): + break + if offset + result > len(data): + return None + return data[offset:offset + result] + + class NicoDownloader(): HEADERS = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36'} def __init__(self, cookies, proxy=None, save_dir=None): def validate_cookie(cookies): for cookie in cookies: - if cookie.name == 'user_session': + if cookie.name == 'user_session' and cookie.value: print('Find user_session in cookie:', cookie.value) break else: print(f'WARN: Cannot find user_session in cookie. You\'re probably not logged in.') # exit(1) # make it non-fatal - - if cookies.lower() in ['chrome', 'firefox', 'edge']: - print(f'Fetching cookies from browser {cookies}...') - cookies = cookies.lower() - if cookies == 'chrome': - cookies = browser_cookie3.chrome(domain_name='.nicovideo.jp') - elif cookies == 'firefox': - cookies = browser_cookie3.firefox(domain_name='.nicovideo.jp') - elif cookies == 'edge': - cookies = browser_cookie3.edge(domain_name='.nicovideo.jp') + if isinstance(cookies, str) and cookies.lower() in ['chrome', 'firefox', 'edge']: + cookies = load_cookie(cookies + '/nicovideo.jp') validate_cookie(cookies) - elif cookies.startswith('user_session_'): + elif isinstance(cookies, str) and cookies.startswith('user_session_'): cookies = {'user_session': cookies} elif Path(cookies).exists(): print(f'Loading cookies from file {cookies}...') @@ -54,20 +66,28 @@ def validate_cookie(cookies): self.session.headers.update(self.HEADERS) # proxy settings - if proxy is None or proxy.lower() == 'none': - proxy = None + if not proxy or proxy.lower() == 'none': + # it's recommended to set proxy to '' instead of None, otherwise + # some redirected requests may not actually use the proxy settings. + # see https://github.com/psf/requests/issues/6153 + proxy = '' elif proxy == 'auto': - proxies = getproxies() - if proxy := proxies.get('http'): - print(f'INFO: automatically use system proxy {proxy}') - - # I don't think system proxy would be missing scheme, but just in case + sys_proxies = getproxies() + if proxy := sys_proxies.get('http', ''): # if failed, proxy would be '' + print(f'INFO: Automatically use system proxy {proxy}') + # I don't think system proxy would be missing scheme ever, but just in case if proxy and '://' not in proxy: + print('WARN: Proxy is missing scheme. Assuming http://') proxy = f'http://{proxy}' - + # setting self.session.proxies alone isn't enough; because requests will + # prioritize system proxy over session.proxies. + # you have to set proxy at request level to override system proxy. + # see https://github.com/psf/requests/issues/2018 + # so, we have to disable system proxy by setting trust_env to False first. + self.session.trust_env = False self.session.proxies = {'http': proxy, 'https': proxy} + # this is for websocket connection/minyami download self.proxy = proxy - self.save_dir = Path(save_dir) if save_dir else Path.cwd() def _parse_url_or_video_id(self, url_or_video_id): @@ -95,65 +115,45 @@ def fetch_page(self, url): live_data = json.loads(soup.select_one('#embedded-data')['data-props']) return live_data - def download_comments(self, room_info, when, filename): - chats_all = [] - url = room_info["data"]["messageServer"]["uri"] - thread_id = room_info["data"]["threadId"] - - ws = self.create_ws(url) - print(f'Start download comments from thread {thread_id}') - sending = True - while sending: - message = [ - {"ping": {"content": "rs:0"}}, - {"ping": {"content": "ps:0"}}, - { - "thread": { - "thread": thread_id, - "version": "20090904", - "res_from": -1000, - "when": when + 10 - } - }, - {"ping": {"content": "pf:0"}}, - {"ping": {"content": "rf:0"}} - ] - ws.send(json.dumps(message)) - - first_chat = True - chats = [] - while True: - result = ws.recv() - data = json.loads(result) - if "chat" in data: - if first_chat: - first_chat = False - # if the date of chat does not change from last batch, - # we assume we have fetched all the comments. - if data["chat"]["date"] == when: - sending = False - break - when = data["chat"]["date"] - chats.append(data) - else: - # reach the end of this batch - if "ping" in data and 'rf' in data["ping"]["content"]: - break - chats_all.extend(chats) - ws.close() + def download_comments_native(self, message_server_info, output): + view_uri = message_server_info['data']['viewUri'] + #"vposBaseTime": "2024-09-25T21:50:00+09:00", + vpos_base_time_dt = datetime.strptime(message_server_info['data']['vposBaseTime'], '%Y-%m-%dT%H:%M:%S%z') + vpos_base_time_epoch = int(vpos_base_time_dt.timestamp()) + print(f'vpos Base time: {vpos_base_time_dt} ({vpos_base_time_epoch})') - # remove duplicate from chats_all - _ = [] - for d in chats_all: - if d not in _: - _.append(d) - chats_all = _ - chats_all.sort(key=lambda x: (x['chat']['date'], x['chat'].get('vpos', 0))) + at = 'now' + backward_api_uri = None + while True: + url = f'{view_uri}?&at={at}' + print(f'Fetch {url}') + r = self.session.get(url, timeout=30) + message = read_protobuf_message(r.content) + chunked_entry = chat.ChunkedEntry() + chunked_entry.ParseFromString(message) + if chunked_entry.HasField('next'): + at = chunked_entry.next.at + elif chunked_entry.HasField('backward'): + backward_api_uri = chunked_entry.backward.segment.uri + break + messages = [] + while True: + print(f'Fetch {backward_api_uri}') + r2 = self.session.get(backward_api_uri, timeout=30) + packed_segment = chat.PackedSegment() + packed_segment.ParseFromString(r2.content) + # prepend to messages + messages = [message for message in packed_segment.messages] + messages + if packed_segment.HasField('next'): + backward_api_uri = packed_segment.next.uri + else: + break + print(f'Find {len(messages)} messages.') + dump_json([google.protobuf.json_format.MessageToDict(message) for message in messages], output) + # TODO: convert the json to a format that is compatible with nicoxml2ass - dump_json(chats_all, self.save_dir / f'{filename}.json') - print(f'Total unique comments: {len(chats_all)}. Saved to "{filename}.json".') - def download_timeshift(self, url_or_video_id, info_only=False, comments='yes', verbose=False, dump=False, auto_reserve=False): + def download_timeshift(self, url_or_video_id, info_only=False, comments='no', verbose=False, dump=False, auto_reserve=False, simulate=False): video_id, url, video_type = self._parse_url_or_video_id(url_or_video_id) return_value = { @@ -169,8 +169,8 @@ def download_timeshift(self, url_or_video_id, info_only=False, comments='yes', v live_data = self.fetch_page(url) title = live_data['program']['title'] - end_time_epoch = live_data["program"]["endTime"] begin_time_epoch = live_data["program"]["beginTime"] + end_time_epoch = live_data["program"]["endTime"] begin_time_dt = to_jp_time(datetime.fromtimestamp(begin_time_epoch)) end_time_dt = to_jp_time(datetime.fromtimestamp(end_time_epoch)) @@ -207,29 +207,32 @@ def download_timeshift(self, url_or_video_id, info_only=False, comments='yes', v # to see if there is any other errors while not live_data['site']['relive'].get('webSocketUrl', None): assert live_data['userProgramWatch']['canWatch'] == False - # 'notHaveTimeshiftTicket': not reserved yet - # 'notUseTimeshiftTicket': reserved but not activated - if 'notHaveTimeshiftTicket' in live_data['userProgramWatch']['rejectedReasons'] or \ - 'notUseTimeshiftTicket' in live_data['userProgramWatch']['rejectedReasons']: - if auto_reserve or input('WARN: You do not have timeshift ticket. Do you want to reserve/activate it now? Y/[N] ').lower() == 'y': - print('Reserving...') - # POST = reserve, PATCH = activate/use - r = self.session.post(f'https://live2.nicovideo.jp/api/v2/programs/{video_id}/timeshift/reservation') - print('Tried POST, response:', r.status_code) - r = self.session.patch(f'https://live2.nicovideo.jp/api/v2/programs/{video_id}/timeshift/reservation') - print('Tried PATCH, response:', r.status_code) - if not r.status_code == 200: - print('Reserving or activating failed. Please try reserving it manually in the webpage.') - return return_value - # refetch live_data - live_data = self.fetch_page(url) - # back to the beginning of the loop - else: - print("Aborted.") + # return if isCountryRestrictionTarget is true + if live_data['userProgramWatch'].get('isCountryRestrictionTarget', False): + print('ERROR: This video is not available in your country.') + return return_value + print(f'WARN: You don\'t have or have not activated the timeshift ticket. Reason:\n{live_data["userProgramWatch"]}') + if auto_reserve or input('Do you want to reserve/activate it now? Y/[N] ').lower() == 'y': + print('Reserving...') + # POST = reserve, PATCH = activate/use + reservation_url = f'https://live2.nicovideo.jp/api/v2/programs/{video_id}/timeshift/reservation' + r = self.session.post(reservation_url) + print('Tried POST, response:', r.status_code) + r = self.session.patch(reservation_url) + print('Tried PATCH, response:', r.status_code) + if r.status_code != 200: + print('Reserving or activating failed. Please try reserving it manually on the webpage.') return return_value + # refetch live_data + old_user_program_watch = live_data['userProgramWatch'] + live_data = self.fetch_page(url) + if live_data['userProgramWatch'] == old_user_program_watch: + print('WARN: live_data did not change after reserving/activating. Something must be wrong.') + print('Aborted.') + return return_value + # back to the beginning of the loop else: - reasons = ', '.join(live_data['userProgramWatch']['rejectedReasons']) - print(f'ERROR: You cannot watch this video because of: {reasons}.') + print("Aborted.") return return_value # Add warning if it's trial only @@ -249,76 +252,116 @@ def download_timeshift(self, url_or_video_id, info_only=False, comments='yes', v # websocket.enableTrace(True) ws = self.create_ws(ws_url) verbose and print("Sent startWatching") - ws.send('{"type":"startWatching","data":{"stream":{"quality":"' + max_quality + '","protocol":"hls","latency":"low","chasePlay":false},"room":{"protocol":"webSocket","commentable":true},"reconnect":false}}') - room_info = None + start_watching_payload = { + "type": "startWatching", + "data": { + "stream": { + "quality": max_quality, + "protocol": "hls", + "latency": "low", + "chasePlay": False, + 'accessRightMethod': 'single_cookie' + }, + "room": { + "protocol": "webSocket", + "commentable": True + }, + "reconnect": False + } + } + verbose and print('Payload:', start_watching_payload) + ws.send(json.dumps(start_watching_payload)) stream_info = None - - ex = concurrent.futures.ThreadPoolExecutor(max_workers=1) + message_server_info = None while True: verbose and print("Receiving...") result = ws.recv() verbose and print("Received '%s'" % result) data = json.loads(result) - if data['type'] == 'room': - room_info = data - if comments in ['yes', 'only']: - ex.submit(self.download_comments, room_info, end_time_epoch, filename) - elif data['type'] == 'stream': + if data['type'] == 'stream': stream_info = data - # just grab all the info even if we don't need it, it doesn't save any time anyway. - if room_info and stream_info: - print('Got all the info we needed. Close WS.') + elif data['type'] == 'messageServer': + message_server_info = data + if stream_info and message_server_info: + print('Got all the info we needed. Close WS connection.') break ws.close() if dump: - dump_json(room_info, self.save_dir / f'{filename}.roominfo.json') dump_json(stream_info, self.save_dir / f'{filename}.streaminfo.json') - + dump_json(message_server_info, self.save_dir / f'{filename}.msgserverinfo.json') return_value.update({ - 'room_info': room_info, - 'stream_info': stream_info + 'stream_info': stream_info, + 'message_server_info': message_server_info }) - if comments == 'only': - ex.shutdown(wait=True) - return_value['danmaku'] = self.save_dir / f'{filename}.json' - return return_value + ex = concurrent.futures.ThreadPoolExecutor(max_workers=1) + if not simulate and comments in ['yes', 'only']: + print('Downloading comments...') + danmaku_output = self.save_dir / f'{filename}.json' + return_value['danmaku'] = danmaku_output + if comments == 'yes': + ex.submit(self.download_comments_native, message_server_info, danmaku_output) + elif comments == 'only': + self.download_comments_native(message_server_info, danmaku_output) + return return_value + else: + return_value['danmaku'] = None master_m3u8_url = stream_info['data']['uri'] - text = self.session.get(master_m3u8_url).text - playlist_url = re.search(r'^.+playlist\.m3u8.*$', text, re.MULTILINE)[0] - playlist_url = urljoin(master_m3u8_url, playlist_url) #+ '&start=682.251' - if verbose: - print('master m3u8 URL:', master_m3u8_url) - print('================== content ==================') - print(text) - print('==================== end ====================') - print('playlist m3u8 URL:', playlist_url) - print('================== content ==================') - print(self.session.get(playlist_url).text) - print('==================== end ====================') - - # do not use arrays. the way python quotes & is not compatible with cmd/bat which minyami uses. - # See: https://stackoverflow.com/questions/74700723/ - # Make sure to also use shell=True for *nix systems - output = self.save_dir / f'{filename}.ts' - cmd = f'minyami -d "{playlist_url}" --key {audience_token},{max_quality} -o "{output}"' - if self.proxy: - cmd += f' --proxy "{self.proxy}"' + + if 'assetdelivery.dlive' in master_m3u8_url: + print('WARN: This is a DLive stream. Will use yt-dlp to download.') + assert 'cookies' in stream_info['data'] + for c in stream_info['data']['cookies']: + self.session.cookies.set(c['name'], c['value']) + playlist_url = None + if verbose: + print('master m3u8 URL:', master_m3u8_url) + print('================== content ==================') + print(self.session.get(master_m3u8_url).text) + print('==================== end ====================') + output = self.save_dir / f'{filename}.mp4' + dlive_bid = self.session.cookies.get("dlive_bid") + # cmd = f'yt-dlp "{master_m3u8_url}" --ignore-config -N 10 -o "{output}" --add-headers "Cookie:dlive_bid={dlive_bid}"' + save_dir_str = str(self.save_dir).rstrip('\\') # remove trailing backslash otherwise it will escape quotes in cmd + cmd = f'N_m3u8DL-RE "{master_m3u8_url}" --save-name "{filename}" --save-dir "{save_dir_str}" --auto-select -H "Cookie:dlive_bid={dlive_bid}" -mt -M format=mp4 --no-date-info' + else: + master_m3u8_text = self.session.get(master_m3u8_url).text + playlist_url = re.search(r'^.+playlist\.m3u8.*$', master_m3u8_text, re.MULTILINE)[0] + playlist_url = urljoin(master_m3u8_url, playlist_url) #+ '&start=682.251' + if verbose: + print('master m3u8 URL:', master_m3u8_url) + print('================== content ==================') + print(master_m3u8_text) + print('==================== end ====================') + print('playlist m3u8 URL:', playlist_url) + print('================== content ==================') + print(self.session.get(playlist_url).text) + print('==================== end ====================') + output = self.save_dir / f'{filename}.ts' + # do not use arrays. the way python quotes & is not compatible with cmd/bat which minyami uses. + # See: https://stackoverflow.com/questions/74700723/ + # Make sure to also use shell=True for *nix systems + cmd = f'minyami -d "{playlist_url}" --key {audience_token},{max_quality} -o "{output}"' + if self.proxy: + cmd += f' --proxy "{self.proxy}"' + if verbose: cmd += ' --verbose' print('CMD is:') print(cmd) - run(cmd, shell=True) - ex.shutdown(wait=True) # ensure download_comments is finished + if simulate: + output = None + else: + run(cmd, shell=True) + ex.shutdown(wait=True) # ensure download_comments is finished return_value.update({ 'master_m3u8_url': master_m3u8_url, 'playlist_m3u8_url': playlist_url, 'output': output, - 'danmaku': self.save_dir / f'{filename}.json' if comments in ['yes', 'only'] else None }) return return_value @@ -344,8 +387,19 @@ def download_thumbnail(self, url_or_video_id, info_only=False, dump=False): download(thumbnail_url, filename=self.save_dir / video_id) if __name__ == "__main__": + import shlex + import sys import argparse + # auto load arguments from nico.txt + for f in [Path(__file__).parent / 'nico.txt', Path('nico.txt')]: + if f.exists(): + with open(f, encoding='utf8') as f: + # insert in front so it can be overridden + commands = shlex.split(f.read().replace('\\', '\\\\')) + sys.argv[1:1] = commands + break + # https://stackoverflow.com/questions/3853722/how-to-insert-newlines-on-argparse-help-text class SmartFormatter(argparse.HelpFormatter): def _split_lines(self, text, width): @@ -359,11 +413,12 @@ def _split_lines(self, text, width): parser.add_argument('--info', '-i', action='store_true', help='Print info only.') parser.add_argument('--dump', action='store_true', help='Dump all the metadata to json files.') parser.add_argument('--thumb', action='store_true', help='Download thumbnail only. Only works for video type (not live type).') - parser.add_argument('--cookies', '-c', default='chrome', help='R|Cookie source. [Default: chrome]\nProvide either:\n - A browser name to fetch from;\n - The value of "user_session";\n - A Netscape-style cookie file.') - parser.add_argument('--comments', '-d', default='yes', choices=['yes', 'no', 'only'], help='Control if comments (danmaku) are downloaded. [Default: yes]') + parser.add_argument('--cookies', '-c', help='R|Cookie source.\nProvide either:\n - A browser name to fetch from;\n - The value of "user_session";\n - A Netscape-style cookie file.') + parser.add_argument('--comments', '-d', default='no', choices=['yes', 'no', 'only'], help='Control if comments (danmaku) are downloaded. [Default: no]') parser.add_argument('--proxy', default='auto', help='Specify a proxy, "none", or "auto" (automatically detects system proxy settings). [Default: auto]') parser.add_argument('--save-dir', '-o', help='Specify the directory to save the downloaded files. [Default: current directory]') parser.add_argument('--reserve', action='store_true', help='Automatically reserve timeshift ticket if not reserved yet. [Default: no]') + parser.add_argument('--simulate', action='store_true', help='Simulate the download process without actually downloading.') args = parser.parse_args() @@ -372,6 +427,6 @@ def _split_lines(self, text, width): if args.thumb: nico_downloader.download_thumbnail(args.url, info_only=args.info, dump=args.dump) else: - nico_downloader.download_timeshift(args.url, info_only=args.info, verbose=args.verbose, comments=args.comments, dump=args.dump, auto_reserve=args.reserve) + nico_downloader.download_timeshift(args.url, info_only=args.info, verbose=args.verbose, comments=args.comments, dump=args.dump, auto_reserve=args.reserve, simulate=args.simulate) diff --git a/proto/__init__.py b/proto/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/proto/dwango/nicolive/chat/data/atoms/moderator_pb2.py b/proto/dwango/nicolive/chat/data/atoms/moderator_pb2.py new file mode 100644 index 0000000..866e905 --- /dev/null +++ b/proto/dwango/nicolive/chat/data/atoms/moderator_pb2.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: dwango/nicolive/chat/data/atoms/moderator.proto +# Protobuf Python Version: 5.27.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 27, + 0, + '', + 'dwango/nicolive/chat/data/atoms/moderator.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n/dwango/nicolive/chat/data/atoms/moderator.proto\x12\x1f\x64wango.nicolive.chat.data.atoms\x1a\x1fgoogle/protobuf/timestamp.proto\"j\n\x11ModeratorUserInfo\x12\x0f\n\x07user_id\x18\x01 \x01(\x03\x12\x15\n\x08nickname\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x07iconUrl\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x0b\n\t_nicknameB\n\n\x08_iconUrl\"\x8b\x02\n\x10ModeratorUpdated\x12W\n\toperation\x18\x01 \x01(\x0e\x32\x44.dwango.nicolive.chat.data.atoms.ModeratorUpdated.ModeratorOperation\x12\x44\n\x08operator\x18\x02 \x01(\x0b\x32\x32.dwango.nicolive.chat.data.atoms.ModeratorUserInfo\x12-\n\tupdatedAt\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\")\n\x12ModeratorOperation\x12\x07\n\x03\x41\x44\x44\x10\x00\x12\n\n\x06\x44\x45LETE\x10\x01\"\xbb\x03\n\x0bSSNGUpdated\x12M\n\toperation\x18\x01 \x01(\x0e\x32:.dwango.nicolive.chat.data.atoms.SSNGUpdated.SSNGOperation\x12\x0f\n\x07ssng_id\x18\x02 \x01(\x03\x12\x44\n\x08operator\x18\x03 \x01(\x0b\x32\x32.dwango.nicolive.chat.data.atoms.ModeratorUserInfo\x12H\n\x04type\x18\x04 \x01(\x0e\x32\x35.dwango.nicolive.chat.data.atoms.SSNGUpdated.SSNGTypeH\x00\x88\x01\x01\x12\x13\n\x06source\x18\x05 \x01(\tH\x01\x88\x01\x01\x12\x32\n\tupdatedAt\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x02\x88\x01\x01\"$\n\rSSNGOperation\x12\x07\n\x03\x41\x44\x44\x10\x00\x12\n\n\x06\x44\x45LETE\x10\x01\"+\n\x08SSNGType\x12\x08\n\x04USER\x10\x00\x12\x08\n\x04WORD\x10\x01\x12\x0b\n\x07\x43OMMAND\x10\x02\x42\x07\n\x05_typeB\t\n\x07_sourceB\x0c\n\n_updatedAt\"\xa3\x02\n\x16ModerationAnnouncement\x12\x14\n\x07message\x18\x01 \x01(\tH\x00\x88\x01\x01\x12]\n\x0eguidelineItems\x18\x02 \x03(\x0e\x32\x45.dwango.nicolive.chat.data.atoms.ModerationAnnouncement.GuidelineItem\x12-\n\tupdatedAt\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"Y\n\rGuidelineItem\x12\x0b\n\x07UNKNOWN\x10\x00\x12\n\n\x06SEXUAL\x10\x01\x12\x08\n\x04SPAM\x10\x02\x12\x0b\n\x07SLANDER\x10\x03\x12\x18\n\x14PERSONAL_INFORMATION\x10\x04\x42\n\n\x08_messageb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'dwango.nicolive.chat.data.atoms.moderator_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_MODERATORUSERINFO']._serialized_start=117 + _globals['_MODERATORUSERINFO']._serialized_end=223 + _globals['_MODERATORUPDATED']._serialized_start=226 + _globals['_MODERATORUPDATED']._serialized_end=493 + _globals['_MODERATORUPDATED_MODERATOROPERATION']._serialized_start=452 + _globals['_MODERATORUPDATED_MODERATOROPERATION']._serialized_end=493 + _globals['_SSNGUPDATED']._serialized_start=496 + _globals['_SSNGUPDATED']._serialized_end=939 + _globals['_SSNGUPDATED_SSNGOPERATION']._serialized_start=824 + _globals['_SSNGUPDATED_SSNGOPERATION']._serialized_end=860 + _globals['_SSNGUPDATED_SSNGTYPE']._serialized_start=862 + _globals['_SSNGUPDATED_SSNGTYPE']._serialized_end=905 + _globals['_MODERATIONANNOUNCEMENT']._serialized_start=942 + _globals['_MODERATIONANNOUNCEMENT']._serialized_end=1233 + _globals['_MODERATIONANNOUNCEMENT_GUIDELINEITEM']._serialized_start=1132 + _globals['_MODERATIONANNOUNCEMENT_GUIDELINEITEM']._serialized_end=1221 +# @@protoc_insertion_point(module_scope) diff --git a/proto/dwango/nicolive/chat/data/atoms/moderator_pb2.pyi b/proto/dwango/nicolive/chat/data/atoms/moderator_pb2.pyi new file mode 100644 index 0000000..46df076 --- /dev/null +++ b/proto/dwango/nicolive/chat/data/atoms/moderator_pb2.pyi @@ -0,0 +1,195 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" + +import builtins +import collections.abc +import google.protobuf.descriptor +import google.protobuf.internal.containers +import google.protobuf.internal.enum_type_wrapper +import google.protobuf.message +import google.protobuf.timestamp_pb2 +import sys +import typing + +if sys.version_info >= (3, 10): + import typing as typing_extensions +else: + import typing_extensions + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +@typing.final +class ModeratorUserInfo(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + USER_ID_FIELD_NUMBER: builtins.int + NICKNAME_FIELD_NUMBER: builtins.int + ICONURL_FIELD_NUMBER: builtins.int + user_id: builtins.int + nickname: builtins.str + iconUrl: builtins.str + def __init__( + self, + *, + user_id: builtins.int = ..., + nickname: builtins.str | None = ..., + iconUrl: builtins.str | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["_iconUrl", b"_iconUrl", "_nickname", b"_nickname", "iconUrl", b"iconUrl", "nickname", b"nickname"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["_iconUrl", b"_iconUrl", "_nickname", b"_nickname", "iconUrl", b"iconUrl", "nickname", b"nickname", "user_id", b"user_id"]) -> None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_iconUrl", b"_iconUrl"]) -> typing.Literal["iconUrl"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_nickname", b"_nickname"]) -> typing.Literal["nickname"] | None: ... + +global___ModeratorUserInfo = ModeratorUserInfo + +@typing.final +class ModeratorUpdated(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + class _ModeratorOperation: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _ModeratorOperationEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[ModeratorUpdated._ModeratorOperation.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + ADD: ModeratorUpdated._ModeratorOperation.ValueType # 0 + DELETE: ModeratorUpdated._ModeratorOperation.ValueType # 1 + + class ModeratorOperation(_ModeratorOperation, metaclass=_ModeratorOperationEnumTypeWrapper): ... + ADD: ModeratorUpdated.ModeratorOperation.ValueType # 0 + DELETE: ModeratorUpdated.ModeratorOperation.ValueType # 1 + + OPERATION_FIELD_NUMBER: builtins.int + OPERATOR_FIELD_NUMBER: builtins.int + UPDATEDAT_FIELD_NUMBER: builtins.int + operation: global___ModeratorUpdated.ModeratorOperation.ValueType + @property + def operator(self) -> global___ModeratorUserInfo: ... + @property + def updatedAt(self) -> google.protobuf.timestamp_pb2.Timestamp: ... + def __init__( + self, + *, + operation: global___ModeratorUpdated.ModeratorOperation.ValueType = ..., + operator: global___ModeratorUserInfo | None = ..., + updatedAt: google.protobuf.timestamp_pb2.Timestamp | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["operator", b"operator", "updatedAt", b"updatedAt"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["operation", b"operation", "operator", b"operator", "updatedAt", b"updatedAt"]) -> None: ... + +global___ModeratorUpdated = ModeratorUpdated + +@typing.final +class SSNGUpdated(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + class _SSNGOperation: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _SSNGOperationEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[SSNGUpdated._SSNGOperation.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + ADD: SSNGUpdated._SSNGOperation.ValueType # 0 + DELETE: SSNGUpdated._SSNGOperation.ValueType # 1 + + class SSNGOperation(_SSNGOperation, metaclass=_SSNGOperationEnumTypeWrapper): ... + ADD: SSNGUpdated.SSNGOperation.ValueType # 0 + DELETE: SSNGUpdated.SSNGOperation.ValueType # 1 + + class _SSNGType: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _SSNGTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[SSNGUpdated._SSNGType.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + USER: SSNGUpdated._SSNGType.ValueType # 0 + WORD: SSNGUpdated._SSNGType.ValueType # 1 + COMMAND: SSNGUpdated._SSNGType.ValueType # 2 + + class SSNGType(_SSNGType, metaclass=_SSNGTypeEnumTypeWrapper): ... + USER: SSNGUpdated.SSNGType.ValueType # 0 + WORD: SSNGUpdated.SSNGType.ValueType # 1 + COMMAND: SSNGUpdated.SSNGType.ValueType # 2 + + OPERATION_FIELD_NUMBER: builtins.int + SSNG_ID_FIELD_NUMBER: builtins.int + OPERATOR_FIELD_NUMBER: builtins.int + TYPE_FIELD_NUMBER: builtins.int + SOURCE_FIELD_NUMBER: builtins.int + UPDATEDAT_FIELD_NUMBER: builtins.int + operation: global___SSNGUpdated.SSNGOperation.ValueType + ssng_id: builtins.int + type: global___SSNGUpdated.SSNGType.ValueType + source: builtins.str + @property + def operator(self) -> global___ModeratorUserInfo: ... + @property + def updatedAt(self) -> google.protobuf.timestamp_pb2.Timestamp: ... + def __init__( + self, + *, + operation: global___SSNGUpdated.SSNGOperation.ValueType = ..., + ssng_id: builtins.int = ..., + operator: global___ModeratorUserInfo | None = ..., + type: global___SSNGUpdated.SSNGType.ValueType | None = ..., + source: builtins.str | None = ..., + updatedAt: google.protobuf.timestamp_pb2.Timestamp | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["_source", b"_source", "_type", b"_type", "_updatedAt", b"_updatedAt", "operator", b"operator", "source", b"source", "type", b"type", "updatedAt", b"updatedAt"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["_source", b"_source", "_type", b"_type", "_updatedAt", b"_updatedAt", "operation", b"operation", "operator", b"operator", "source", b"source", "ssng_id", b"ssng_id", "type", b"type", "updatedAt", b"updatedAt"]) -> None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_source", b"_source"]) -> typing.Literal["source"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_type", b"_type"]) -> typing.Literal["type"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_updatedAt", b"_updatedAt"]) -> typing.Literal["updatedAt"] | None: ... + +global___SSNGUpdated = SSNGUpdated + +@typing.final +class ModerationAnnouncement(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + class _GuidelineItem: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _GuidelineItemEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[ModerationAnnouncement._GuidelineItem.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + UNKNOWN: ModerationAnnouncement._GuidelineItem.ValueType # 0 + SEXUAL: ModerationAnnouncement._GuidelineItem.ValueType # 1 + SPAM: ModerationAnnouncement._GuidelineItem.ValueType # 2 + SLANDER: ModerationAnnouncement._GuidelineItem.ValueType # 3 + PERSONAL_INFORMATION: ModerationAnnouncement._GuidelineItem.ValueType # 4 + + class GuidelineItem(_GuidelineItem, metaclass=_GuidelineItemEnumTypeWrapper): ... + UNKNOWN: ModerationAnnouncement.GuidelineItem.ValueType # 0 + SEXUAL: ModerationAnnouncement.GuidelineItem.ValueType # 1 + SPAM: ModerationAnnouncement.GuidelineItem.ValueType # 2 + SLANDER: ModerationAnnouncement.GuidelineItem.ValueType # 3 + PERSONAL_INFORMATION: ModerationAnnouncement.GuidelineItem.ValueType # 4 + + MESSAGE_FIELD_NUMBER: builtins.int + GUIDELINEITEMS_FIELD_NUMBER: builtins.int + UPDATEDAT_FIELD_NUMBER: builtins.int + message: builtins.str + @property + def guidelineItems(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[global___ModerationAnnouncement.GuidelineItem.ValueType]: ... + @property + def updatedAt(self) -> google.protobuf.timestamp_pb2.Timestamp: ... + def __init__( + self, + *, + message: builtins.str | None = ..., + guidelineItems: collections.abc.Iterable[global___ModerationAnnouncement.GuidelineItem.ValueType] | None = ..., + updatedAt: google.protobuf.timestamp_pb2.Timestamp | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["_message", b"_message", "message", b"message", "updatedAt", b"updatedAt"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["_message", b"_message", "guidelineItems", b"guidelineItems", "message", b"message", "updatedAt", b"updatedAt"]) -> None: ... + def WhichOneof(self, oneof_group: typing.Literal["_message", b"_message"]) -> typing.Literal["message"] | None: ... + +global___ModerationAnnouncement = ModerationAnnouncement diff --git a/proto/dwango/nicolive/chat/data/atoms/sensitive_pb2.py b/proto/dwango/nicolive/chat/data/atoms/sensitive_pb2.py new file mode 100644 index 0000000..973c011 --- /dev/null +++ b/proto/dwango/nicolive/chat/data/atoms/sensitive_pb2.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: dwango/nicolive/chat/data/atoms/sensitive.proto +# Protobuf Python Version: 5.27.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 27, + 0, + '', + 'dwango/nicolive/chat/data/atoms/sensitive.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n/dwango/nicolive/chat/data/atoms/sensitive.proto\x12\x1f\x64wango.nicolive.chat.data.atomsb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'dwango.nicolive.chat.data.atoms.sensitive_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None +# @@protoc_insertion_point(module_scope) diff --git a/proto/dwango/nicolive/chat/data/atoms/sensitive_pb2.pyi b/proto/dwango/nicolive/chat/data/atoms/sensitive_pb2.pyi new file mode 100644 index 0000000..dd854e2 --- /dev/null +++ b/proto/dwango/nicolive/chat/data/atoms/sensitive_pb2.pyi @@ -0,0 +1,8 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" + +import google.protobuf.descriptor + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor diff --git a/proto/dwango/nicolive/chat/data/atoms_pb2.py b/proto/dwango/nicolive/chat/data/atoms_pb2.py new file mode 100644 index 0000000..4d031be --- /dev/null +++ b/proto/dwango/nicolive/chat/data/atoms_pb2.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: dwango/nicolive/chat/data/atoms.proto +# Protobuf Python Version: 5.27.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 27, + 0, + '', + 'dwango/nicolive/chat/data/atoms.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import duration_pb2 as google_dot_protobuf_dot_duration__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%dwango/nicolive/chat/data/atoms.proto\x12\x19\x64wango.nicolive.chat.data\x1a\x1egoogle/protobuf/duration.proto\"\xbc\t\n\x04\x43hat\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\t\x12\x0c\n\x04vpos\x18\x03 \x01(\x05\x12\x45\n\x0e\x61\x63\x63ount_status\x18\x04 \x01(\x0e\x32-.dwango.nicolive.chat.data.Chat.AccountStatus\x12\x11\n\x04name\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0braw_user_id\x18\x05 \x01(\x03H\x01\x88\x01\x01\x12\x1b\n\x0ehashed_user_id\x18\x06 \x01(\tH\x02\x88\x01\x01\x12:\n\x08modifier\x18\x07 \x01(\x0b\x32(.dwango.nicolive.chat.data.Chat.Modifier\x12\n\n\x02no\x18\x08 \x01(\x05\x1a\xe3\x06\n\x08Modifier\x12>\n\x08position\x18\x01 \x01(\x0e\x32,.dwango.nicolive.chat.data.Chat.Modifier.Pos\x12;\n\x04size\x18\x02 \x01(\x0e\x32-.dwango.nicolive.chat.data.Chat.Modifier.Size\x12I\n\x0bnamed_color\x18\x03 \x01(\x0e\x32\x32.dwango.nicolive.chat.data.Chat.Modifier.ColorNameH\x00\x12H\n\nfull_color\x18\x04 \x01(\x0b\x32\x32.dwango.nicolive.chat.data.Chat.Modifier.FullColorH\x00\x12;\n\x04\x66ont\x18\x05 \x01(\x0e\x32-.dwango.nicolive.chat.data.Chat.Modifier.Font\x12\x41\n\x07opacity\x18\x06 \x01(\x0e\x32\x30.dwango.nicolive.chat.data.Chat.Modifier.Opacity\x1a,\n\tFullColor\x12\t\n\x01r\x18\x01 \x01(\x05\x12\t\n\x01g\x18\x02 \x01(\x05\x12\t\n\x01\x62\x18\x03 \x01(\x05\"\"\n\x03Pos\x12\x08\n\x04naka\x10\x00\x12\t\n\x05shita\x10\x01\x12\x06\n\x02ue\x10\x02\"&\n\x04Size\x12\n\n\x06medium\x10\x00\x12\t\n\x05small\x10\x01\x12\x07\n\x03\x62ig\x10\x02\"\xed\x01\n\tColorName\x12\t\n\x05white\x10\x00\x12\x07\n\x03red\x10\x01\x12\x08\n\x04pink\x10\x02\x12\n\n\x06orange\x10\x03\x12\n\n\x06yellow\x10\x04\x12\t\n\x05green\x10\x05\x12\x08\n\x04\x63yan\x10\x06\x12\x08\n\x04\x62lue\x10\x07\x12\n\n\x06purple\x10\x08\x12\t\n\x05\x62lack\x10\t\x12\n\n\x06white2\x10\n\x12\x08\n\x04red2\x10\x0b\x12\t\n\x05pink2\x10\x0c\x12\x0b\n\x07orange2\x10\r\x12\x0b\n\x07yellow2\x10\x0e\x12\n\n\x06green2\x10\x0f\x12\t\n\x05\x63yan2\x10\x10\x12\t\n\x05\x62lue2\x10\x11\x12\x0b\n\x07purple2\x10\x12\x12\n\n\x06\x62lack2\x10\x13\"*\n\x04\x46ont\x12\n\n\x06\x64\x65\x66ont\x10\x00\x12\n\n\x06mincho\x10\x01\x12\n\n\x06gothic\x10\x02\"&\n\x07Opacity\x12\n\n\x06Normal\x10\x00\x12\x0f\n\x0bTranslucent\x10\x01\x42\x07\n\x05\x63olor\"*\n\rAccountStatus\x12\x0c\n\x08Standard\x10\x00\x12\x0b\n\x07Premium\x10\x01\x42\x07\n\x05_nameB\x0e\n\x0c_raw_user_idB\x11\n\x0f_hashed_user_id\"\x96\x01\n\x0fOperatorComment\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\t\x12\x11\n\x04name\x18\x02 \x01(\tH\x00\x88\x01\x01\x12:\n\x08modifier\x18\x03 \x01(\x0b\x32(.dwango.nicolive.chat.data.Chat.Modifier\x12\x11\n\x04link\x18\x04 \x01(\tH\x01\x88\x01\x01\x42\x07\n\x05_nameB\x07\n\x05_link\"Q\n\x04Jump\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\t\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\'\n\x04wait\x18\x04 \x01(\x0b\x32\x19.google.protobuf.Duration\"Q\n\x08Redirect\x12\x0b\n\x03uri\x18\x01 \x01(\t\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\'\n\x04wait\x18\x04 \x01(\x0b\x32\x19.google.protobuf.Duration\"\xc7\x01\n\x12SimpleNotification\x12\x10\n\x06ichiba\x18\x01 \x01(\tH\x00\x12\x0f\n\x05quote\x18\x02 \x01(\tH\x00\x12\x11\n\x07\x65motion\x18\x03 \x01(\tH\x00\x12\x10\n\x06\x63ruise\x18\x04 \x01(\tH\x00\x12\x1a\n\x10program_extended\x18\x05 \x01(\tH\x00\x12\x14\n\nranking_in\x18\x06 \x01(\tH\x00\x12\x19\n\x0franking_updated\x18\x08 \x01(\tH\x00\x12\x11\n\x07visited\x18\x07 \x01(\tH\x00\x42\t\n\x07message\"\xd1\x01\n\x04Gift\x12\x0f\n\x07item_id\x18\x01 \x01(\t\x12\x1f\n\x12\x61\x64vertiser_user_id\x18\x02 \x01(\x03H\x00\x88\x01\x01\x12\x17\n\x0f\x61\x64vertiser_name\x18\x03 \x01(\t\x12\r\n\x05point\x18\x04 \x01(\x03\x12\x0f\n\x07message\x18\x05 \x01(\t\x12\x11\n\titem_name\x18\x06 \x01(\t\x12\x1e\n\x11\x63ontribution_rank\x18\x07 \x01(\x05H\x01\x88\x01\x01\x42\x15\n\x13_advertiser_user_idB\x14\n\x12_contribution_rank\"\x87\x04\n\x06Nicoad\x12\x32\n\x02v0\x18\x01 \x01(\x0b\x32$.dwango.nicolive.chat.data.Nicoad.V0H\x00\x12\x32\n\x02v1\x18\x02 \x01(\x0b\x32$.dwango.nicolive.chat.data.Nicoad.V1H\x00\x1a\xd9\x02\n\x02V0\x12;\n\x06latest\x18\x01 \x01(\x0b\x32+.dwango.nicolive.chat.data.Nicoad.V0.Latest\x12=\n\x07ranking\x18\x02 \x03(\x0b\x32,.dwango.nicolive.chat.data.Nicoad.V0.Ranking\x12\x13\n\x0btotal_point\x18\x03 \x01(\x05\x1aM\n\x06Latest\x12\x12\n\nadvertiser\x18\x01 \x01(\t\x12\r\n\x05point\x18\x02 \x01(\x05\x12\x14\n\x07message\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\n\n\x08_message\x1as\n\x07Ranking\x12\x12\n\nadvertiser\x18\x01 \x01(\t\x12\x0c\n\x04rank\x18\x02 \x01(\x05\x12\x14\n\x07message\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x16\n\tuser_rank\x18\x04 \x01(\x05H\x01\x88\x01\x01\x42\n\n\x08_messageB\x0c\n\n_user_rank\x1a-\n\x02V1\x12\x16\n\x0etotal_ad_point\x18\x01 \x01(\x05\x12\x0f\n\x07message\x18\x02 \x01(\tB\n\n\x08versions\"t\n\x0b\x43ommentLock\x12=\n\x06status\x18\x01 \x01(\x0e\x32-.dwango.nicolive.chat.data.CommentLock.Status\"&\n\x06Status\x12\x10\n\x0cUnrestricted\x10\x00\x12\n\n\x06Locked\x10\x01\"\x80\x01\n\x0b\x43ommentMode\x12=\n\x06layout\x18\x01 \x01(\x0e\x32-.dwango.nicolive.chat.data.CommentMode.Layout\"2\n\x06Layout\x12\n\n\x06Normal\x10\x00\x12\x0c\n\x08SplitTop\x10\x01\x12\x0e\n\nBackground\x10\x02\"\x0c\n\nGameUpdate\"\xe4\x01\n\nTrialPanel\x12:\n\x05panel\x18\x01 \x01(\x0e\x32+.dwango.nicolive.chat.data.TrialPanel.Panel\x12\x44\n\x10unqualified_user\x18\x02 \x01(\x0e\x32*.dwango.nicolive.chat.data.TrialPanel.Mode\" \n\x05Panel\x12\n\n\x06Hidden\x10\x00\x12\x0b\n\x07\x44isplay\x10\x01\"2\n\x04Mode\x12\x0b\n\x07\x41llowed\x10\x00\x12\x0e\n\nRestricted\x10\x01\x12\r\n\tForbidden\x10\x02\"o\n\rProgramStatus\x12=\n\x05state\x18\x01 \x01(\x0e\x32..dwango.nicolive.chat.data.ProgramStatus.State\"\x1f\n\x05State\x12\x0b\n\x07Unknown\x10\x00\x12\t\n\x05\x45nded\x10\x01\"\xa9\x01\n\nTagUpdated\x12\x37\n\x04tags\x18\x01 \x03(\x0b\x32).dwango.nicolive.chat.data.TagUpdated.Tag\x12\x14\n\x0cowner_locked\x18\x02 \x01(\x08\x1aL\n\x03Tag\x12\x0c\n\x04text\x18\x01 \x01(\t\x12\x0e\n\x06locked\x18\x02 \x01(\x08\x12\x10\n\x08reserved\x18\x03 \x01(\x08\x12\x15\n\rnicopedia_uri\x18\x04 \x01(\t\"\xa8\x01\n\nStatistics\x12\x14\n\x07viewers\x18\x01 \x01(\x03H\x00\x88\x01\x01\x12\x15\n\x08\x63omments\x18\x02 \x01(\x03H\x01\x88\x01\x01\x12\x16\n\tad_points\x18\x03 \x01(\x03H\x02\x88\x01\x01\x12\x18\n\x0bgift_points\x18\x04 \x01(\x03H\x03\x88\x01\x01\x42\n\n\x08_viewersB\x0b\n\t_commentsB\x0c\n\n_ad_pointsB\x0e\n\x0c_gift_pointsJ\x04\x08\x05\x10\x06\"\xe8\x01\n\x07Marquee\x12@\n\x07\x64isplay\x18\x01 \x01(\x0b\x32*.dwango.nicolive.chat.data.Marquee.DisplayH\x00\x88\x01\x01\x1a\x8e\x01\n\x07\x44isplay\x12\x44\n\x10operator_comment\x18\x01 \x01(\x0b\x32*.dwango.nicolive.chat.data.OperatorComment\x12\x30\n\x08\x64uration\x18\x03 \x01(\x0b\x32\x19.google.protobuf.DurationH\x00\x88\x01\x01\x42\x0b\n\t_durationB\n\n\x08_display\"\x83\x02\n\x07\x45nquete\x12\x10\n\x08question\x18\x01 \x01(\t\x12:\n\x07\x63hoices\x18\x02 \x03(\x0b\x32).dwango.nicolive.chat.data.Enquete.Choice\x12\x39\n\x06status\x18\x03 \x01(\x0e\x32).dwango.nicolive.chat.data.Enquete.Status\x1a\x43\n\x06\x43hoice\x12\x13\n\x0b\x64\x65scription\x18\x01 \x01(\t\x12\x16\n\tper_mille\x18\x03 \x01(\x05H\x00\x88\x01\x01\x42\x0c\n\n_per_mille\"*\n\x06Status\x12\n\n\x06\x43losed\x10\x00\x12\x08\n\x04Poll\x10\x01\x12\n\n\x06Result\x10\x02\"{\n\tMoveOrder\x12/\n\x04jump\x18\x01 \x01(\x0b\x32\x1f.dwango.nicolive.chat.data.JumpH\x00\x12\x37\n\x08redirect\x18\x02 \x01(\x0b\x32#.dwango.nicolive.chat.data.RedirectH\x00\x42\x04\n\x02tob\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'dwango.nicolive.chat.data.atoms_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_CHAT']._serialized_start=101 + _globals['_CHAT']._serialized_end=1313 + _globals['_CHAT_MODIFIER']._serialized_start=358 + _globals['_CHAT_MODIFIER']._serialized_end=1225 + _globals['_CHAT_MODIFIER_FULLCOLOR']._serialized_start=772 + _globals['_CHAT_MODIFIER_FULLCOLOR']._serialized_end=816 + _globals['_CHAT_MODIFIER_POS']._serialized_start=818 + _globals['_CHAT_MODIFIER_POS']._serialized_end=852 + _globals['_CHAT_MODIFIER_SIZE']._serialized_start=854 + _globals['_CHAT_MODIFIER_SIZE']._serialized_end=892 + _globals['_CHAT_MODIFIER_COLORNAME']._serialized_start=895 + _globals['_CHAT_MODIFIER_COLORNAME']._serialized_end=1132 + _globals['_CHAT_MODIFIER_FONT']._serialized_start=1134 + _globals['_CHAT_MODIFIER_FONT']._serialized_end=1176 + _globals['_CHAT_MODIFIER_OPACITY']._serialized_start=1178 + _globals['_CHAT_MODIFIER_OPACITY']._serialized_end=1216 + _globals['_CHAT_ACCOUNTSTATUS']._serialized_start=1227 + _globals['_CHAT_ACCOUNTSTATUS']._serialized_end=1269 + _globals['_OPERATORCOMMENT']._serialized_start=1316 + _globals['_OPERATORCOMMENT']._serialized_end=1466 + _globals['_JUMP']._serialized_start=1468 + _globals['_JUMP']._serialized_end=1549 + _globals['_REDIRECT']._serialized_start=1551 + _globals['_REDIRECT']._serialized_end=1632 + _globals['_SIMPLENOTIFICATION']._serialized_start=1635 + _globals['_SIMPLENOTIFICATION']._serialized_end=1834 + _globals['_GIFT']._serialized_start=1837 + _globals['_GIFT']._serialized_end=2046 + _globals['_NICOAD']._serialized_start=2049 + _globals['_NICOAD']._serialized_end=2568 + _globals['_NICOAD_V0']._serialized_start=2164 + _globals['_NICOAD_V0']._serialized_end=2509 + _globals['_NICOAD_V0_LATEST']._serialized_start=2315 + _globals['_NICOAD_V0_LATEST']._serialized_end=2392 + _globals['_NICOAD_V0_RANKING']._serialized_start=2394 + _globals['_NICOAD_V0_RANKING']._serialized_end=2509 + _globals['_NICOAD_V1']._serialized_start=2511 + _globals['_NICOAD_V1']._serialized_end=2556 + _globals['_COMMENTLOCK']._serialized_start=2570 + _globals['_COMMENTLOCK']._serialized_end=2686 + _globals['_COMMENTLOCK_STATUS']._serialized_start=2648 + _globals['_COMMENTLOCK_STATUS']._serialized_end=2686 + _globals['_COMMENTMODE']._serialized_start=2689 + _globals['_COMMENTMODE']._serialized_end=2817 + _globals['_COMMENTMODE_LAYOUT']._serialized_start=2767 + _globals['_COMMENTMODE_LAYOUT']._serialized_end=2817 + _globals['_GAMEUPDATE']._serialized_start=2819 + _globals['_GAMEUPDATE']._serialized_end=2831 + _globals['_TRIALPANEL']._serialized_start=2834 + _globals['_TRIALPANEL']._serialized_end=3062 + _globals['_TRIALPANEL_PANEL']._serialized_start=2978 + _globals['_TRIALPANEL_PANEL']._serialized_end=3010 + _globals['_TRIALPANEL_MODE']._serialized_start=3012 + _globals['_TRIALPANEL_MODE']._serialized_end=3062 + _globals['_PROGRAMSTATUS']._serialized_start=3064 + _globals['_PROGRAMSTATUS']._serialized_end=3175 + _globals['_PROGRAMSTATUS_STATE']._serialized_start=3144 + _globals['_PROGRAMSTATUS_STATE']._serialized_end=3175 + _globals['_TAGUPDATED']._serialized_start=3178 + _globals['_TAGUPDATED']._serialized_end=3347 + _globals['_TAGUPDATED_TAG']._serialized_start=3271 + _globals['_TAGUPDATED_TAG']._serialized_end=3347 + _globals['_STATISTICS']._serialized_start=3350 + _globals['_STATISTICS']._serialized_end=3518 + _globals['_MARQUEE']._serialized_start=3521 + _globals['_MARQUEE']._serialized_end=3753 + _globals['_MARQUEE_DISPLAY']._serialized_start=3599 + _globals['_MARQUEE_DISPLAY']._serialized_end=3741 + _globals['_ENQUETE']._serialized_start=3756 + _globals['_ENQUETE']._serialized_end=4015 + _globals['_ENQUETE_CHOICE']._serialized_start=3904 + _globals['_ENQUETE_CHOICE']._serialized_end=3971 + _globals['_ENQUETE_STATUS']._serialized_start=3973 + _globals['_ENQUETE_STATUS']._serialized_end=4015 + _globals['_MOVEORDER']._serialized_start=4017 + _globals['_MOVEORDER']._serialized_end=4140 +# @@protoc_insertion_point(module_scope) diff --git a/proto/dwango/nicolive/chat/data/atoms_pb2.pyi b/proto/dwango/nicolive/chat/data/atoms_pb2.pyi new file mode 100644 index 0000000..c29b24e --- /dev/null +++ b/proto/dwango/nicolive/chat/data/atoms_pb2.pyi @@ -0,0 +1,826 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" + +import builtins +import collections.abc +import google.protobuf.descriptor +import google.protobuf.duration_pb2 +import google.protobuf.internal.containers +import google.protobuf.internal.enum_type_wrapper +import google.protobuf.message +import sys +import typing + +if sys.version_info >= (3, 10): + import typing as typing_extensions +else: + import typing_extensions + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +@typing.final +class Chat(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + class _AccountStatus: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _AccountStatusEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Chat._AccountStatus.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + Standard: Chat._AccountStatus.ValueType # 0 + Premium: Chat._AccountStatus.ValueType # 1 + + class AccountStatus(_AccountStatus, metaclass=_AccountStatusEnumTypeWrapper): ... + Standard: Chat.AccountStatus.ValueType # 0 + Premium: Chat.AccountStatus.ValueType # 1 + + @typing.final + class Modifier(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + class _Pos: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _PosEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Chat.Modifier._Pos.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + naka: Chat.Modifier._Pos.ValueType # 0 + shita: Chat.Modifier._Pos.ValueType # 1 + ue: Chat.Modifier._Pos.ValueType # 2 + + class Pos(_Pos, metaclass=_PosEnumTypeWrapper): ... + naka: Chat.Modifier.Pos.ValueType # 0 + shita: Chat.Modifier.Pos.ValueType # 1 + ue: Chat.Modifier.Pos.ValueType # 2 + + class _Size: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _SizeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Chat.Modifier._Size.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + medium: Chat.Modifier._Size.ValueType # 0 + small: Chat.Modifier._Size.ValueType # 1 + big: Chat.Modifier._Size.ValueType # 2 + + class Size(_Size, metaclass=_SizeEnumTypeWrapper): ... + medium: Chat.Modifier.Size.ValueType # 0 + small: Chat.Modifier.Size.ValueType # 1 + big: Chat.Modifier.Size.ValueType # 2 + + class _ColorName: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _ColorNameEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Chat.Modifier._ColorName.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + white: Chat.Modifier._ColorName.ValueType # 0 + red: Chat.Modifier._ColorName.ValueType # 1 + pink: Chat.Modifier._ColorName.ValueType # 2 + orange: Chat.Modifier._ColorName.ValueType # 3 + yellow: Chat.Modifier._ColorName.ValueType # 4 + green: Chat.Modifier._ColorName.ValueType # 5 + cyan: Chat.Modifier._ColorName.ValueType # 6 + blue: Chat.Modifier._ColorName.ValueType # 7 + purple: Chat.Modifier._ColorName.ValueType # 8 + black: Chat.Modifier._ColorName.ValueType # 9 + white2: Chat.Modifier._ColorName.ValueType # 10 + red2: Chat.Modifier._ColorName.ValueType # 11 + pink2: Chat.Modifier._ColorName.ValueType # 12 + orange2: Chat.Modifier._ColorName.ValueType # 13 + yellow2: Chat.Modifier._ColorName.ValueType # 14 + green2: Chat.Modifier._ColorName.ValueType # 15 + cyan2: Chat.Modifier._ColorName.ValueType # 16 + blue2: Chat.Modifier._ColorName.ValueType # 17 + purple2: Chat.Modifier._ColorName.ValueType # 18 + black2: Chat.Modifier._ColorName.ValueType # 19 + + class ColorName(_ColorName, metaclass=_ColorNameEnumTypeWrapper): ... + white: Chat.Modifier.ColorName.ValueType # 0 + red: Chat.Modifier.ColorName.ValueType # 1 + pink: Chat.Modifier.ColorName.ValueType # 2 + orange: Chat.Modifier.ColorName.ValueType # 3 + yellow: Chat.Modifier.ColorName.ValueType # 4 + green: Chat.Modifier.ColorName.ValueType # 5 + cyan: Chat.Modifier.ColorName.ValueType # 6 + blue: Chat.Modifier.ColorName.ValueType # 7 + purple: Chat.Modifier.ColorName.ValueType # 8 + black: Chat.Modifier.ColorName.ValueType # 9 + white2: Chat.Modifier.ColorName.ValueType # 10 + red2: Chat.Modifier.ColorName.ValueType # 11 + pink2: Chat.Modifier.ColorName.ValueType # 12 + orange2: Chat.Modifier.ColorName.ValueType # 13 + yellow2: Chat.Modifier.ColorName.ValueType # 14 + green2: Chat.Modifier.ColorName.ValueType # 15 + cyan2: Chat.Modifier.ColorName.ValueType # 16 + blue2: Chat.Modifier.ColorName.ValueType # 17 + purple2: Chat.Modifier.ColorName.ValueType # 18 + black2: Chat.Modifier.ColorName.ValueType # 19 + + class _Font: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _FontEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Chat.Modifier._Font.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + defont: Chat.Modifier._Font.ValueType # 0 + mincho: Chat.Modifier._Font.ValueType # 1 + gothic: Chat.Modifier._Font.ValueType # 2 + + class Font(_Font, metaclass=_FontEnumTypeWrapper): ... + defont: Chat.Modifier.Font.ValueType # 0 + mincho: Chat.Modifier.Font.ValueType # 1 + gothic: Chat.Modifier.Font.ValueType # 2 + + class _Opacity: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _OpacityEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Chat.Modifier._Opacity.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + Normal: Chat.Modifier._Opacity.ValueType # 0 + Translucent: Chat.Modifier._Opacity.ValueType # 1 + + class Opacity(_Opacity, metaclass=_OpacityEnumTypeWrapper): ... + Normal: Chat.Modifier.Opacity.ValueType # 0 + Translucent: Chat.Modifier.Opacity.ValueType # 1 + + @typing.final + class FullColor(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + R_FIELD_NUMBER: builtins.int + G_FIELD_NUMBER: builtins.int + B_FIELD_NUMBER: builtins.int + r: builtins.int + g: builtins.int + b: builtins.int + def __init__( + self, + *, + r: builtins.int = ..., + g: builtins.int = ..., + b: builtins.int = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["b", b"b", "g", b"g", "r", b"r"]) -> None: ... + + POSITION_FIELD_NUMBER: builtins.int + SIZE_FIELD_NUMBER: builtins.int + NAMED_COLOR_FIELD_NUMBER: builtins.int + FULL_COLOR_FIELD_NUMBER: builtins.int + FONT_FIELD_NUMBER: builtins.int + OPACITY_FIELD_NUMBER: builtins.int + position: global___Chat.Modifier.Pos.ValueType + size: global___Chat.Modifier.Size.ValueType + named_color: global___Chat.Modifier.ColorName.ValueType + font: global___Chat.Modifier.Font.ValueType + opacity: global___Chat.Modifier.Opacity.ValueType + @property + def full_color(self) -> global___Chat.Modifier.FullColor: ... + def __init__( + self, + *, + position: global___Chat.Modifier.Pos.ValueType = ..., + size: global___Chat.Modifier.Size.ValueType = ..., + named_color: global___Chat.Modifier.ColorName.ValueType = ..., + full_color: global___Chat.Modifier.FullColor | None = ..., + font: global___Chat.Modifier.Font.ValueType = ..., + opacity: global___Chat.Modifier.Opacity.ValueType = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["color", b"color", "full_color", b"full_color", "named_color", b"named_color"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["color", b"color", "font", b"font", "full_color", b"full_color", "named_color", b"named_color", "opacity", b"opacity", "position", b"position", "size", b"size"]) -> None: ... + def WhichOneof(self, oneof_group: typing.Literal["color", b"color"]) -> typing.Literal["named_color", "full_color"] | None: ... + + CONTENT_FIELD_NUMBER: builtins.int + VPOS_FIELD_NUMBER: builtins.int + ACCOUNT_STATUS_FIELD_NUMBER: builtins.int + NAME_FIELD_NUMBER: builtins.int + RAW_USER_ID_FIELD_NUMBER: builtins.int + HASHED_USER_ID_FIELD_NUMBER: builtins.int + MODIFIER_FIELD_NUMBER: builtins.int + NO_FIELD_NUMBER: builtins.int + content: builtins.str + vpos: builtins.int + account_status: global___Chat.AccountStatus.ValueType + name: builtins.str + raw_user_id: builtins.int + hashed_user_id: builtins.str + no: builtins.int + @property + def modifier(self) -> global___Chat.Modifier: ... + def __init__( + self, + *, + content: builtins.str = ..., + vpos: builtins.int = ..., + account_status: global___Chat.AccountStatus.ValueType = ..., + name: builtins.str | None = ..., + raw_user_id: builtins.int | None = ..., + hashed_user_id: builtins.str | None = ..., + modifier: global___Chat.Modifier | None = ..., + no: builtins.int = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["_hashed_user_id", b"_hashed_user_id", "_name", b"_name", "_raw_user_id", b"_raw_user_id", "hashed_user_id", b"hashed_user_id", "modifier", b"modifier", "name", b"name", "raw_user_id", b"raw_user_id"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["_hashed_user_id", b"_hashed_user_id", "_name", b"_name", "_raw_user_id", b"_raw_user_id", "account_status", b"account_status", "content", b"content", "hashed_user_id", b"hashed_user_id", "modifier", b"modifier", "name", b"name", "no", b"no", "raw_user_id", b"raw_user_id", "vpos", b"vpos"]) -> None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_hashed_user_id", b"_hashed_user_id"]) -> typing.Literal["hashed_user_id"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_name", b"_name"]) -> typing.Literal["name"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_raw_user_id", b"_raw_user_id"]) -> typing.Literal["raw_user_id"] | None: ... + +global___Chat = Chat + +@typing.final +class OperatorComment(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + CONTENT_FIELD_NUMBER: builtins.int + NAME_FIELD_NUMBER: builtins.int + MODIFIER_FIELD_NUMBER: builtins.int + LINK_FIELD_NUMBER: builtins.int + content: builtins.str + name: builtins.str + link: builtins.str + @property + def modifier(self) -> global___Chat.Modifier: ... + def __init__( + self, + *, + content: builtins.str = ..., + name: builtins.str | None = ..., + modifier: global___Chat.Modifier | None = ..., + link: builtins.str | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["_link", b"_link", "_name", b"_name", "link", b"link", "modifier", b"modifier", "name", b"name"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["_link", b"_link", "_name", b"_name", "content", b"content", "link", b"link", "modifier", b"modifier", "name", b"name"]) -> None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_link", b"_link"]) -> typing.Literal["link"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_name", b"_name"]) -> typing.Literal["name"] | None: ... + +global___OperatorComment = OperatorComment + +@typing.final +class Jump(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + CONTENT_FIELD_NUMBER: builtins.int + MESSAGE_FIELD_NUMBER: builtins.int + WAIT_FIELD_NUMBER: builtins.int + content: builtins.str + message: builtins.str + @property + def wait(self) -> google.protobuf.duration_pb2.Duration: ... + def __init__( + self, + *, + content: builtins.str = ..., + message: builtins.str = ..., + wait: google.protobuf.duration_pb2.Duration | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["wait", b"wait"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["content", b"content", "message", b"message", "wait", b"wait"]) -> None: ... + +global___Jump = Jump + +@typing.final +class Redirect(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + URI_FIELD_NUMBER: builtins.int + MESSAGE_FIELD_NUMBER: builtins.int + WAIT_FIELD_NUMBER: builtins.int + uri: builtins.str + message: builtins.str + @property + def wait(self) -> google.protobuf.duration_pb2.Duration: ... + def __init__( + self, + *, + uri: builtins.str = ..., + message: builtins.str = ..., + wait: google.protobuf.duration_pb2.Duration | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["wait", b"wait"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["message", b"message", "uri", b"uri", "wait", b"wait"]) -> None: ... + +global___Redirect = Redirect + +@typing.final +class SimpleNotification(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ICHIBA_FIELD_NUMBER: builtins.int + QUOTE_FIELD_NUMBER: builtins.int + EMOTION_FIELD_NUMBER: builtins.int + CRUISE_FIELD_NUMBER: builtins.int + PROGRAM_EXTENDED_FIELD_NUMBER: builtins.int + RANKING_IN_FIELD_NUMBER: builtins.int + RANKING_UPDATED_FIELD_NUMBER: builtins.int + VISITED_FIELD_NUMBER: builtins.int + ichiba: builtins.str + quote: builtins.str + emotion: builtins.str + cruise: builtins.str + program_extended: builtins.str + ranking_in: builtins.str + ranking_updated: builtins.str + visited: builtins.str + def __init__( + self, + *, + ichiba: builtins.str = ..., + quote: builtins.str = ..., + emotion: builtins.str = ..., + cruise: builtins.str = ..., + program_extended: builtins.str = ..., + ranking_in: builtins.str = ..., + ranking_updated: builtins.str = ..., + visited: builtins.str = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["cruise", b"cruise", "emotion", b"emotion", "ichiba", b"ichiba", "message", b"message", "program_extended", b"program_extended", "quote", b"quote", "ranking_in", b"ranking_in", "ranking_updated", b"ranking_updated", "visited", b"visited"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["cruise", b"cruise", "emotion", b"emotion", "ichiba", b"ichiba", "message", b"message", "program_extended", b"program_extended", "quote", b"quote", "ranking_in", b"ranking_in", "ranking_updated", b"ranking_updated", "visited", b"visited"]) -> None: ... + def WhichOneof(self, oneof_group: typing.Literal["message", b"message"]) -> typing.Literal["ichiba", "quote", "emotion", "cruise", "program_extended", "ranking_in", "ranking_updated", "visited"] | None: ... + +global___SimpleNotification = SimpleNotification + +@typing.final +class Gift(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ITEM_ID_FIELD_NUMBER: builtins.int + ADVERTISER_USER_ID_FIELD_NUMBER: builtins.int + ADVERTISER_NAME_FIELD_NUMBER: builtins.int + POINT_FIELD_NUMBER: builtins.int + MESSAGE_FIELD_NUMBER: builtins.int + ITEM_NAME_FIELD_NUMBER: builtins.int + CONTRIBUTION_RANK_FIELD_NUMBER: builtins.int + item_id: builtins.str + advertiser_user_id: builtins.int + advertiser_name: builtins.str + point: builtins.int + message: builtins.str + item_name: builtins.str + contribution_rank: builtins.int + def __init__( + self, + *, + item_id: builtins.str = ..., + advertiser_user_id: builtins.int | None = ..., + advertiser_name: builtins.str = ..., + point: builtins.int = ..., + message: builtins.str = ..., + item_name: builtins.str = ..., + contribution_rank: builtins.int | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["_advertiser_user_id", b"_advertiser_user_id", "_contribution_rank", b"_contribution_rank", "advertiser_user_id", b"advertiser_user_id", "contribution_rank", b"contribution_rank"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["_advertiser_user_id", b"_advertiser_user_id", "_contribution_rank", b"_contribution_rank", "advertiser_name", b"advertiser_name", "advertiser_user_id", b"advertiser_user_id", "contribution_rank", b"contribution_rank", "item_id", b"item_id", "item_name", b"item_name", "message", b"message", "point", b"point"]) -> None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_advertiser_user_id", b"_advertiser_user_id"]) -> typing.Literal["advertiser_user_id"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_contribution_rank", b"_contribution_rank"]) -> typing.Literal["contribution_rank"] | None: ... + +global___Gift = Gift + +@typing.final +class Nicoad(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing.final + class V0(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing.final + class Latest(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ADVERTISER_FIELD_NUMBER: builtins.int + POINT_FIELD_NUMBER: builtins.int + MESSAGE_FIELD_NUMBER: builtins.int + advertiser: builtins.str + point: builtins.int + message: builtins.str + def __init__( + self, + *, + advertiser: builtins.str = ..., + point: builtins.int = ..., + message: builtins.str | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["_message", b"_message", "message", b"message"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["_message", b"_message", "advertiser", b"advertiser", "message", b"message", "point", b"point"]) -> None: ... + def WhichOneof(self, oneof_group: typing.Literal["_message", b"_message"]) -> typing.Literal["message"] | None: ... + + @typing.final + class Ranking(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ADVERTISER_FIELD_NUMBER: builtins.int + RANK_FIELD_NUMBER: builtins.int + MESSAGE_FIELD_NUMBER: builtins.int + USER_RANK_FIELD_NUMBER: builtins.int + advertiser: builtins.str + rank: builtins.int + message: builtins.str + user_rank: builtins.int + def __init__( + self, + *, + advertiser: builtins.str = ..., + rank: builtins.int = ..., + message: builtins.str | None = ..., + user_rank: builtins.int | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["_message", b"_message", "_user_rank", b"_user_rank", "message", b"message", "user_rank", b"user_rank"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["_message", b"_message", "_user_rank", b"_user_rank", "advertiser", b"advertiser", "message", b"message", "rank", b"rank", "user_rank", b"user_rank"]) -> None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_message", b"_message"]) -> typing.Literal["message"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_user_rank", b"_user_rank"]) -> typing.Literal["user_rank"] | None: ... + + LATEST_FIELD_NUMBER: builtins.int + RANKING_FIELD_NUMBER: builtins.int + TOTAL_POINT_FIELD_NUMBER: builtins.int + total_point: builtins.int + @property + def latest(self) -> global___Nicoad.V0.Latest: ... + @property + def ranking(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Nicoad.V0.Ranking]: ... + def __init__( + self, + *, + latest: global___Nicoad.V0.Latest | None = ..., + ranking: collections.abc.Iterable[global___Nicoad.V0.Ranking] | None = ..., + total_point: builtins.int = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["latest", b"latest"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["latest", b"latest", "ranking", b"ranking", "total_point", b"total_point"]) -> None: ... + + @typing.final + class V1(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TOTAL_AD_POINT_FIELD_NUMBER: builtins.int + MESSAGE_FIELD_NUMBER: builtins.int + total_ad_point: builtins.int + message: builtins.str + def __init__( + self, + *, + total_ad_point: builtins.int = ..., + message: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["message", b"message", "total_ad_point", b"total_ad_point"]) -> None: ... + + V0_FIELD_NUMBER: builtins.int + V1_FIELD_NUMBER: builtins.int + @property + def v0(self) -> global___Nicoad.V0: ... + @property + def v1(self) -> global___Nicoad.V1: ... + def __init__( + self, + *, + v0: global___Nicoad.V0 | None = ..., + v1: global___Nicoad.V1 | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["v0", b"v0", "v1", b"v1", "versions", b"versions"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["v0", b"v0", "v1", b"v1", "versions", b"versions"]) -> None: ... + def WhichOneof(self, oneof_group: typing.Literal["versions", b"versions"]) -> typing.Literal["v0", "v1"] | None: ... + +global___Nicoad = Nicoad + +@typing.final +class CommentLock(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + class _Status: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _StatusEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[CommentLock._Status.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + Unrestricted: CommentLock._Status.ValueType # 0 + Locked: CommentLock._Status.ValueType # 1 + + class Status(_Status, metaclass=_StatusEnumTypeWrapper): ... + Unrestricted: CommentLock.Status.ValueType # 0 + Locked: CommentLock.Status.ValueType # 1 + + STATUS_FIELD_NUMBER: builtins.int + status: global___CommentLock.Status.ValueType + def __init__( + self, + *, + status: global___CommentLock.Status.ValueType = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["status", b"status"]) -> None: ... + +global___CommentLock = CommentLock + +@typing.final +class CommentMode(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + class _Layout: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _LayoutEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[CommentMode._Layout.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + Normal: CommentMode._Layout.ValueType # 0 + SplitTop: CommentMode._Layout.ValueType # 1 + Background: CommentMode._Layout.ValueType # 2 + + class Layout(_Layout, metaclass=_LayoutEnumTypeWrapper): ... + Normal: CommentMode.Layout.ValueType # 0 + SplitTop: CommentMode.Layout.ValueType # 1 + Background: CommentMode.Layout.ValueType # 2 + + LAYOUT_FIELD_NUMBER: builtins.int + layout: global___CommentMode.Layout.ValueType + def __init__( + self, + *, + layout: global___CommentMode.Layout.ValueType = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["layout", b"layout"]) -> None: ... + +global___CommentMode = CommentMode + +@typing.final +class GameUpdate(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + def __init__( + self, + ) -> None: ... + +global___GameUpdate = GameUpdate + +@typing.final +class TrialPanel(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + class _Panel: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _PanelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[TrialPanel._Panel.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + Hidden: TrialPanel._Panel.ValueType # 0 + Display: TrialPanel._Panel.ValueType # 1 + + class Panel(_Panel, metaclass=_PanelEnumTypeWrapper): ... + Hidden: TrialPanel.Panel.ValueType # 0 + Display: TrialPanel.Panel.ValueType # 1 + + class _Mode: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _ModeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[TrialPanel._Mode.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + Allowed: TrialPanel._Mode.ValueType # 0 + Restricted: TrialPanel._Mode.ValueType # 1 + Forbidden: TrialPanel._Mode.ValueType # 2 + + class Mode(_Mode, metaclass=_ModeEnumTypeWrapper): ... + Allowed: TrialPanel.Mode.ValueType # 0 + Restricted: TrialPanel.Mode.ValueType # 1 + Forbidden: TrialPanel.Mode.ValueType # 2 + + PANEL_FIELD_NUMBER: builtins.int + UNQUALIFIED_USER_FIELD_NUMBER: builtins.int + panel: global___TrialPanel.Panel.ValueType + unqualified_user: global___TrialPanel.Mode.ValueType + def __init__( + self, + *, + panel: global___TrialPanel.Panel.ValueType = ..., + unqualified_user: global___TrialPanel.Mode.ValueType = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["panel", b"panel", "unqualified_user", b"unqualified_user"]) -> None: ... + +global___TrialPanel = TrialPanel + +@typing.final +class ProgramStatus(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + class _State: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _StateEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[ProgramStatus._State.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + Unknown: ProgramStatus._State.ValueType # 0 + Ended: ProgramStatus._State.ValueType # 1 + + class State(_State, metaclass=_StateEnumTypeWrapper): ... + Unknown: ProgramStatus.State.ValueType # 0 + Ended: ProgramStatus.State.ValueType # 1 + + STATE_FIELD_NUMBER: builtins.int + state: global___ProgramStatus.State.ValueType + def __init__( + self, + *, + state: global___ProgramStatus.State.ValueType = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["state", b"state"]) -> None: ... + +global___ProgramStatus = ProgramStatus + +@typing.final +class TagUpdated(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing.final + class Tag(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TEXT_FIELD_NUMBER: builtins.int + LOCKED_FIELD_NUMBER: builtins.int + RESERVED_FIELD_NUMBER: builtins.int + NICOPEDIA_URI_FIELD_NUMBER: builtins.int + text: builtins.str + locked: builtins.bool + reserved: builtins.bool + nicopedia_uri: builtins.str + def __init__( + self, + *, + text: builtins.str = ..., + locked: builtins.bool = ..., + reserved: builtins.bool = ..., + nicopedia_uri: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["locked", b"locked", "nicopedia_uri", b"nicopedia_uri", "reserved", b"reserved", "text", b"text"]) -> None: ... + + TAGS_FIELD_NUMBER: builtins.int + OWNER_LOCKED_FIELD_NUMBER: builtins.int + owner_locked: builtins.bool + @property + def tags(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___TagUpdated.Tag]: ... + def __init__( + self, + *, + tags: collections.abc.Iterable[global___TagUpdated.Tag] | None = ..., + owner_locked: builtins.bool = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["owner_locked", b"owner_locked", "tags", b"tags"]) -> None: ... + +global___TagUpdated = TagUpdated + +@typing.final +class Statistics(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + VIEWERS_FIELD_NUMBER: builtins.int + COMMENTS_FIELD_NUMBER: builtins.int + AD_POINTS_FIELD_NUMBER: builtins.int + GIFT_POINTS_FIELD_NUMBER: builtins.int + viewers: builtins.int + comments: builtins.int + ad_points: builtins.int + gift_points: builtins.int + def __init__( + self, + *, + viewers: builtins.int | None = ..., + comments: builtins.int | None = ..., + ad_points: builtins.int | None = ..., + gift_points: builtins.int | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["_ad_points", b"_ad_points", "_comments", b"_comments", "_gift_points", b"_gift_points", "_viewers", b"_viewers", "ad_points", b"ad_points", "comments", b"comments", "gift_points", b"gift_points", "viewers", b"viewers"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["_ad_points", b"_ad_points", "_comments", b"_comments", "_gift_points", b"_gift_points", "_viewers", b"_viewers", "ad_points", b"ad_points", "comments", b"comments", "gift_points", b"gift_points", "viewers", b"viewers"]) -> None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_ad_points", b"_ad_points"]) -> typing.Literal["ad_points"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_comments", b"_comments"]) -> typing.Literal["comments"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_gift_points", b"_gift_points"]) -> typing.Literal["gift_points"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_viewers", b"_viewers"]) -> typing.Literal["viewers"] | None: ... + +global___Statistics = Statistics + +@typing.final +class Marquee(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing.final + class Display(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + OPERATOR_COMMENT_FIELD_NUMBER: builtins.int + DURATION_FIELD_NUMBER: builtins.int + @property + def operator_comment(self) -> global___OperatorComment: ... + @property + def duration(self) -> google.protobuf.duration_pb2.Duration: ... + def __init__( + self, + *, + operator_comment: global___OperatorComment | None = ..., + duration: google.protobuf.duration_pb2.Duration | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["_duration", b"_duration", "duration", b"duration", "operator_comment", b"operator_comment"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["_duration", b"_duration", "duration", b"duration", "operator_comment", b"operator_comment"]) -> None: ... + def WhichOneof(self, oneof_group: typing.Literal["_duration", b"_duration"]) -> typing.Literal["duration"] | None: ... + + DISPLAY_FIELD_NUMBER: builtins.int + @property + def display(self) -> global___Marquee.Display: ... + def __init__( + self, + *, + display: global___Marquee.Display | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["_display", b"_display", "display", b"display"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["_display", b"_display", "display", b"display"]) -> None: ... + def WhichOneof(self, oneof_group: typing.Literal["_display", b"_display"]) -> typing.Literal["display"] | None: ... + +global___Marquee = Marquee + +@typing.final +class Enquete(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + class _Status: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _StatusEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Enquete._Status.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + Closed: Enquete._Status.ValueType # 0 + Poll: Enquete._Status.ValueType # 1 + Result: Enquete._Status.ValueType # 2 + + class Status(_Status, metaclass=_StatusEnumTypeWrapper): ... + Closed: Enquete.Status.ValueType # 0 + Poll: Enquete.Status.ValueType # 1 + Result: Enquete.Status.ValueType # 2 + + @typing.final + class Choice(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + DESCRIPTION_FIELD_NUMBER: builtins.int + PER_MILLE_FIELD_NUMBER: builtins.int + description: builtins.str + per_mille: builtins.int + def __init__( + self, + *, + description: builtins.str = ..., + per_mille: builtins.int | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["_per_mille", b"_per_mille", "per_mille", b"per_mille"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["_per_mille", b"_per_mille", "description", b"description", "per_mille", b"per_mille"]) -> None: ... + def WhichOneof(self, oneof_group: typing.Literal["_per_mille", b"_per_mille"]) -> typing.Literal["per_mille"] | None: ... + + QUESTION_FIELD_NUMBER: builtins.int + CHOICES_FIELD_NUMBER: builtins.int + STATUS_FIELD_NUMBER: builtins.int + question: builtins.str + status: global___Enquete.Status.ValueType + @property + def choices(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Enquete.Choice]: ... + def __init__( + self, + *, + question: builtins.str = ..., + choices: collections.abc.Iterable[global___Enquete.Choice] | None = ..., + status: global___Enquete.Status.ValueType = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["choices", b"choices", "question", b"question", "status", b"status"]) -> None: ... + +global___Enquete = Enquete + +@typing.final +class MoveOrder(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + JUMP_FIELD_NUMBER: builtins.int + REDIRECT_FIELD_NUMBER: builtins.int + @property + def jump(self) -> global___Jump: ... + @property + def redirect(self) -> global___Redirect: ... + def __init__( + self, + *, + jump: global___Jump | None = ..., + redirect: global___Redirect | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["jump", b"jump", "redirect", b"redirect", "to", b"to"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["jump", b"jump", "redirect", b"redirect", "to", b"to"]) -> None: ... + def WhichOneof(self, oneof_group: typing.Literal["to", b"to"]) -> typing.Literal["jump", "redirect"] | None: ... + +global___MoveOrder = MoveOrder diff --git a/proto/dwango/nicolive/chat/data/message_pb2.py b/proto/dwango/nicolive/chat/data/message_pb2.py new file mode 100644 index 0000000..340fad5 --- /dev/null +++ b/proto/dwango/nicolive/chat/data/message_pb2.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: dwango/nicolive/chat/data/message.proto +# Protobuf Python Version: 5.27.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 27, + 0, + '', + 'dwango/nicolive/chat/data/message.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from . import atoms_pb2 as dwango_dot_nicolive_dot_chat_dot_data_dot_atoms__pb2 +from .atoms import moderator_pb2 as dwango_dot_nicolive_dot_chat_dot_data_dot_atoms_dot_moderator__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\'dwango/nicolive/chat/data/message.proto\x12\x19\x64wango.nicolive.chat.data\x1a%dwango/nicolive/chat/data/atoms.proto\x1a/dwango/nicolive/chat/data/atoms/moderator.proto\"\xde\x04\n\x0fNicoliveMessage\x12/\n\x04\x63hat\x18\x01 \x01(\x0b\x32\x1f.dwango.nicolive.chat.data.ChatH\x00\x12L\n\x13simple_notification\x18\x07 \x01(\x0b\x32-.dwango.nicolive.chat.data.SimpleNotificationH\x00\x12/\n\x04gift\x18\x08 \x01(\x0b\x32\x1f.dwango.nicolive.chat.data.GiftH\x00\x12\x33\n\x06nicoad\x18\t \x01(\x0b\x32!.dwango.nicolive.chat.data.NicoadH\x00\x12<\n\x0bgame_update\x18\r \x01(\x0b\x32%.dwango.nicolive.chat.data.GameUpdateH\x00\x12<\n\x0btag_updated\x18\x11 \x01(\x0b\x32%.dwango.nicolive.chat.data.TagUpdatedH\x00\x12N\n\x11moderator_updated\x18\x12 \x01(\x0b\x32\x31.dwango.nicolive.chat.data.atoms.ModeratorUpdatedH\x00\x12\x44\n\x0cssng_updated\x18\x13 \x01(\x0b\x32,.dwango.nicolive.chat.data.atoms.SSNGUpdatedH\x00\x12:\n\x0foverflowed_chat\x18\x14 \x01(\x0b\x32\x1f.dwango.nicolive.chat.data.ChatH\x00\x42\x06\n\x04\x64\x61taJ\x04\x08\x02\x10\x07J\x04\x08\n\x10\rJ\x04\x08\x0e\x10\x11\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'dwango.nicolive.chat.data.message_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_NICOLIVEMESSAGE']._serialized_start=159 + _globals['_NICOLIVEMESSAGE']._serialized_end=765 +# @@protoc_insertion_point(module_scope) diff --git a/proto/dwango/nicolive/chat/data/message_pb2.pyi b/proto/dwango/nicolive/chat/data/message_pb2.pyi new file mode 100644 index 0000000..c9e629c --- /dev/null +++ b/proto/dwango/nicolive/chat/data/message_pb2.pyi @@ -0,0 +1,63 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" + +import builtins +import ndgr_client.proto.dwango.nicolive.chat.data.atoms.moderator_pb2 +import ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2 +import google.protobuf.descriptor +import google.protobuf.message +import typing + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +@typing.final +class NicoliveMessage(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + CHAT_FIELD_NUMBER: builtins.int + SIMPLE_NOTIFICATION_FIELD_NUMBER: builtins.int + GIFT_FIELD_NUMBER: builtins.int + NICOAD_FIELD_NUMBER: builtins.int + GAME_UPDATE_FIELD_NUMBER: builtins.int + TAG_UPDATED_FIELD_NUMBER: builtins.int + MODERATOR_UPDATED_FIELD_NUMBER: builtins.int + SSNG_UPDATED_FIELD_NUMBER: builtins.int + OVERFLOWED_CHAT_FIELD_NUMBER: builtins.int + @property + def chat(self) -> ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.Chat: ... + @property + def simple_notification(self) -> ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.SimpleNotification: ... + @property + def gift(self) -> ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.Gift: ... + @property + def nicoad(self) -> ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.Nicoad: ... + @property + def game_update(self) -> ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.GameUpdate: ... + @property + def tag_updated(self) -> ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.TagUpdated: ... + @property + def moderator_updated(self) -> ndgr_client.proto.dwango.nicolive.chat.data.atoms.moderator_pb2.ModeratorUpdated: ... + @property + def ssng_updated(self) -> ndgr_client.proto.dwango.nicolive.chat.data.atoms.moderator_pb2.SSNGUpdated: ... + @property + def overflowed_chat(self) -> ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.Chat: ... + def __init__( + self, + *, + chat: ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.Chat | None = ..., + simple_notification: ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.SimpleNotification | None = ..., + gift: ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.Gift | None = ..., + nicoad: ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.Nicoad | None = ..., + game_update: ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.GameUpdate | None = ..., + tag_updated: ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.TagUpdated | None = ..., + moderator_updated: ndgr_client.proto.dwango.nicolive.chat.data.atoms.moderator_pb2.ModeratorUpdated | None = ..., + ssng_updated: ndgr_client.proto.dwango.nicolive.chat.data.atoms.moderator_pb2.SSNGUpdated | None = ..., + overflowed_chat: ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.Chat | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["chat", b"chat", "data", b"data", "game_update", b"game_update", "gift", b"gift", "moderator_updated", b"moderator_updated", "nicoad", b"nicoad", "overflowed_chat", b"overflowed_chat", "simple_notification", b"simple_notification", "ssng_updated", b"ssng_updated", "tag_updated", b"tag_updated"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["chat", b"chat", "data", b"data", "game_update", b"game_update", "gift", b"gift", "moderator_updated", b"moderator_updated", "nicoad", b"nicoad", "overflowed_chat", b"overflowed_chat", "simple_notification", b"simple_notification", "ssng_updated", b"ssng_updated", "tag_updated", b"tag_updated"]) -> None: ... + def WhichOneof(self, oneof_group: typing.Literal["data", b"data"]) -> typing.Literal["chat", "simple_notification", "gift", "nicoad", "game_update", "tag_updated", "moderator_updated", "ssng_updated", "overflowed_chat"] | None: ... + +global___NicoliveMessage = NicoliveMessage diff --git a/proto/dwango/nicolive/chat/data/origin_pb2.py b/proto/dwango/nicolive/chat/data/origin_pb2.py new file mode 100644 index 0000000..0648a92 --- /dev/null +++ b/proto/dwango/nicolive/chat/data/origin_pb2.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: dwango/nicolive/chat/data/origin.proto +# Protobuf Python Version: 5.27.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 27, + 0, + '', + 'dwango/nicolive/chat/data/origin.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n&dwango/nicolive/chat/data/origin.proto\x12\x19\x64wango.nicolive.chat.data\"s\n\x0eNicoliveOrigin\x12>\n\x04\x63hat\x18\x01 \x01(\x0b\x32..dwango.nicolive.chat.data.NicoliveOrigin.ChatH\x00\x1a\x17\n\x04\x43hat\x12\x0f\n\x07live_id\x18\x01 \x01(\x03\x42\x08\n\x06originb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'dwango.nicolive.chat.data.origin_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_NICOLIVEORIGIN']._serialized_start=69 + _globals['_NICOLIVEORIGIN']._serialized_end=184 + _globals['_NICOLIVEORIGIN_CHAT']._serialized_start=151 + _globals['_NICOLIVEORIGIN_CHAT']._serialized_end=174 +# @@protoc_insertion_point(module_scope) diff --git a/proto/dwango/nicolive/chat/data/origin_pb2.pyi b/proto/dwango/nicolive/chat/data/origin_pb2.pyi new file mode 100644 index 0000000..3c53eff --- /dev/null +++ b/proto/dwango/nicolive/chat/data/origin_pb2.pyi @@ -0,0 +1,42 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" + +import builtins +import google.protobuf.descriptor +import google.protobuf.message +import typing + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +@typing.final +class NicoliveOrigin(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing.final + class Chat(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + LIVE_ID_FIELD_NUMBER: builtins.int + live_id: builtins.int + def __init__( + self, + *, + live_id: builtins.int = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["live_id", b"live_id"]) -> None: ... + + CHAT_FIELD_NUMBER: builtins.int + @property + def chat(self) -> global___NicoliveOrigin.Chat: ... + def __init__( + self, + *, + chat: global___NicoliveOrigin.Chat | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["chat", b"chat", "origin", b"origin"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["chat", b"chat", "origin", b"origin"]) -> None: ... + def WhichOneof(self, oneof_group: typing.Literal["origin", b"origin"]) -> typing.Literal["chat"] | None: ... + +global___NicoliveOrigin = NicoliveOrigin diff --git a/proto/dwango/nicolive/chat/data/state_pb2.py b/proto/dwango/nicolive/chat/data/state_pb2.py new file mode 100644 index 0000000..126c205 --- /dev/null +++ b/proto/dwango/nicolive/chat/data/state_pb2.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: dwango/nicolive/chat/data/state.proto +# Protobuf Python Version: 5.27.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 27, + 0, + '', + 'dwango/nicolive/chat/data/state.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from . import atoms_pb2 as dwango_dot_nicolive_dot_chat_dot_data_dot_atoms__pb2 +from .atoms import moderator_pb2 as dwango_dot_nicolive_dot_chat_dot_data_dot_atoms_dot_moderator__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%dwango/nicolive/chat/data/state.proto\x12\x19\x64wango.nicolive.chat.data\x1a%dwango/nicolive/chat/data/atoms.proto\x1a/dwango/nicolive/chat/data/atoms/moderator.proto\"\x86\x06\n\rNicoliveState\x12>\n\nstatistics\x18\x01 \x01(\x0b\x32%.dwango.nicolive.chat.data.StatisticsH\x00\x88\x01\x01\x12\x38\n\x07\x65nquete\x18\x02 \x01(\x0b\x32\".dwango.nicolive.chat.data.EnqueteH\x01\x88\x01\x01\x12=\n\nmove_order\x18\x03 \x01(\x0b\x32$.dwango.nicolive.chat.data.MoveOrderH\x02\x88\x01\x01\x12\x38\n\x07marquee\x18\x04 \x01(\x0b\x32\".dwango.nicolive.chat.data.MarqueeH\x03\x88\x01\x01\x12\x41\n\x0c\x63omment_lock\x18\x05 \x01(\x0b\x32&.dwango.nicolive.chat.data.CommentLockH\x04\x88\x01\x01\x12\x41\n\x0c\x63omment_mode\x18\x06 \x01(\x0b\x32&.dwango.nicolive.chat.data.CommentModeH\x05\x88\x01\x01\x12?\n\x0btrial_panel\x18\x07 \x01(\x0b\x32%.dwango.nicolive.chat.data.TrialPanelH\x06\x88\x01\x01\x12\x45\n\x0eprogram_status\x18\t \x01(\x0b\x32(.dwango.nicolive.chat.data.ProgramStatusH\x07\x88\x01\x01\x12]\n\x17moderation_announcement\x18\n \x01(\x0b\x32\x37.dwango.nicolive.chat.data.atoms.ModerationAnnouncementH\x08\x88\x01\x01\x42\r\n\x0b_statisticsB\n\n\x08_enqueteB\r\n\x0b_move_orderB\n\n\x08_marqueeB\x0f\n\r_comment_lockB\x0f\n\r_comment_modeB\x0e\n\x0c_trial_panelB\x11\n\x0f_program_statusB\x1a\n\x18_moderation_announcementb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'dwango.nicolive.chat.data.state_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_NICOLIVESTATE']._serialized_start=157 + _globals['_NICOLIVESTATE']._serialized_end=931 +# @@protoc_insertion_point(module_scope) diff --git a/proto/dwango/nicolive/chat/data/state_pb2.pyi b/proto/dwango/nicolive/chat/data/state_pb2.pyi new file mode 100644 index 0000000..9f225eb --- /dev/null +++ b/proto/dwango/nicolive/chat/data/state_pb2.pyi @@ -0,0 +1,80 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" + +import builtins +import ndgr_client.proto.dwango.nicolive.chat.data.atoms.moderator_pb2 +import ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2 +import google.protobuf.descriptor +import google.protobuf.message +import typing + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +@typing.final +class NicoliveState(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + STATISTICS_FIELD_NUMBER: builtins.int + ENQUETE_FIELD_NUMBER: builtins.int + MOVE_ORDER_FIELD_NUMBER: builtins.int + MARQUEE_FIELD_NUMBER: builtins.int + COMMENT_LOCK_FIELD_NUMBER: builtins.int + COMMENT_MODE_FIELD_NUMBER: builtins.int + TRIAL_PANEL_FIELD_NUMBER: builtins.int + PROGRAM_STATUS_FIELD_NUMBER: builtins.int + MODERATION_ANNOUNCEMENT_FIELD_NUMBER: builtins.int + @property + def statistics(self) -> ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.Statistics: ... + @property + def enquete(self) -> ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.Enquete: ... + @property + def move_order(self) -> ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.MoveOrder: ... + @property + def marquee(self) -> ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.Marquee: ... + @property + def comment_lock(self) -> ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.CommentLock: ... + @property + def comment_mode(self) -> ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.CommentMode: ... + @property + def trial_panel(self) -> ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.TrialPanel: ... + @property + def program_status(self) -> ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.ProgramStatus: ... + @property + def moderation_announcement(self) -> ndgr_client.proto.dwango.nicolive.chat.data.atoms.moderator_pb2.ModerationAnnouncement: ... + def __init__( + self, + *, + statistics: ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.Statistics | None = ..., + enquete: ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.Enquete | None = ..., + move_order: ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.MoveOrder | None = ..., + marquee: ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.Marquee | None = ..., + comment_lock: ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.CommentLock | None = ..., + comment_mode: ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.CommentMode | None = ..., + trial_panel: ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.TrialPanel | None = ..., + program_status: ndgr_client.proto.dwango.nicolive.chat.data.atoms_pb2.ProgramStatus | None = ..., + moderation_announcement: ndgr_client.proto.dwango.nicolive.chat.data.atoms.moderator_pb2.ModerationAnnouncement | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["_comment_lock", b"_comment_lock", "_comment_mode", b"_comment_mode", "_enquete", b"_enquete", "_marquee", b"_marquee", "_moderation_announcement", b"_moderation_announcement", "_move_order", b"_move_order", "_program_status", b"_program_status", "_statistics", b"_statistics", "_trial_panel", b"_trial_panel", "comment_lock", b"comment_lock", "comment_mode", b"comment_mode", "enquete", b"enquete", "marquee", b"marquee", "moderation_announcement", b"moderation_announcement", "move_order", b"move_order", "program_status", b"program_status", "statistics", b"statistics", "trial_panel", b"trial_panel"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["_comment_lock", b"_comment_lock", "_comment_mode", b"_comment_mode", "_enquete", b"_enquete", "_marquee", b"_marquee", "_moderation_announcement", b"_moderation_announcement", "_move_order", b"_move_order", "_program_status", b"_program_status", "_statistics", b"_statistics", "_trial_panel", b"_trial_panel", "comment_lock", b"comment_lock", "comment_mode", b"comment_mode", "enquete", b"enquete", "marquee", b"marquee", "moderation_announcement", b"moderation_announcement", "move_order", b"move_order", "program_status", b"program_status", "statistics", b"statistics", "trial_panel", b"trial_panel"]) -> None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_comment_lock", b"_comment_lock"]) -> typing.Literal["comment_lock"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_comment_mode", b"_comment_mode"]) -> typing.Literal["comment_mode"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_enquete", b"_enquete"]) -> typing.Literal["enquete"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_marquee", b"_marquee"]) -> typing.Literal["marquee"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_moderation_announcement", b"_moderation_announcement"]) -> typing.Literal["moderation_announcement"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_move_order", b"_move_order"]) -> typing.Literal["move_order"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_program_status", b"_program_status"]) -> typing.Literal["program_status"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_statistics", b"_statistics"]) -> typing.Literal["statistics"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing.Literal["_trial_panel", b"_trial_panel"]) -> typing.Literal["trial_panel"] | None: ... + +global___NicoliveState = NicoliveState diff --git a/proto/dwango/nicolive/chat/service/edge/payload_pb2.py b/proto/dwango/nicolive/chat/service/edge/payload_pb2.py new file mode 100644 index 0000000..18a02f9 --- /dev/null +++ b/proto/dwango/nicolive/chat/service/edge/payload_pb2.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: dwango/nicolive/chat/service/edge/payload.proto +# Protobuf Python Version: 5.27.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 27, + 0, + '', + 'dwango/nicolive/chat/service/edge/payload.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 +from ...data import message_pb2 as dwango_dot_nicolive_dot_chat_dot_data_dot_message__pb2 +from ...data import state_pb2 as dwango_dot_nicolive_dot_chat_dot_data_dot_state__pb2 +from ...data import origin_pb2 as dwango_dot_nicolive_dot_chat_dot_data_dot_origin__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n/dwango/nicolive/chat/service/edge/payload.proto\x12!dwango.nicolive.chat.service.edge\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\'dwango/nicolive/chat/data/message.proto\x1a%dwango/nicolive/chat/data/state.proto\x1a&dwango/nicolive/chat/data/origin.proto\"\xb5\x03\n\x0e\x43hunkedMessage\x12\x44\n\x04meta\x18\x01 \x01(\x0b\x32\x36.dwango.nicolive.chat.service.edge.ChunkedMessage.Meta\x12=\n\x07message\x18\x02 \x01(\x0b\x32*.dwango.nicolive.chat.data.NicoliveMessageH\x00\x12\x39\n\x05state\x18\x04 \x01(\x0b\x32(.dwango.nicolive.chat.data.NicoliveStateH\x00\x12J\n\x06signal\x18\x05 \x01(\x0e\x32\x38.dwango.nicolive.chat.service.edge.ChunkedMessage.SignalH\x00\x1au\n\x04Meta\x12\n\n\x02id\x18\x01 \x01(\t\x12&\n\x02\x61t\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x39\n\x06origin\x18\x03 \x01(\x0b\x32).dwango.nicolive.chat.data.NicoliveOrigin\"\x15\n\x06Signal\x12\x0b\n\x07\x46lushed\x10\x00\x42\t\n\x07payload\"\x9e\x02\n\rPackedSegment\x12\x43\n\x08messages\x18\x01 \x03(\x0b\x32\x31.dwango.nicolive.chat.service.edge.ChunkedMessage\x12\x43\n\x04next\x18\x02 \x01(\x0b\x32\x35.dwango.nicolive.chat.service.edge.PackedSegment.Next\x12P\n\x08snapshot\x18\x03 \x01(\x0b\x32>.dwango.nicolive.chat.service.edge.PackedSegment.StateSnapshot\x1a\x13\n\x04Next\x12\x0b\n\x03uri\x18\x01 \x01(\t\x1a\x1c\n\rStateSnapshot\x12\x0b\n\x03uri\x18\x01 \x01(\t\"\xd6\x02\n\x0c\x43hunkedEntry\x12\x46\n\x08\x62\x61\x63kward\x18\x02 \x01(\x0b\x32\x32.dwango.nicolive.chat.service.edge.BackwardSegmentH\x00\x12\x45\n\x08previous\x18\x03 \x01(\x0b\x32\x31.dwango.nicolive.chat.service.edge.MessageSegmentH\x00\x12\x44\n\x07segment\x18\x01 \x01(\x0b\x32\x31.dwango.nicolive.chat.service.edge.MessageSegmentH\x00\x12L\n\x04next\x18\x04 \x01(\x0b\x32<.dwango.nicolive.chat.service.edge.ChunkedEntry.ReadyForNextH\x00\x1a\x1a\n\x0cReadyForNext\x12\n\n\x02\x61t\x18\x01 \x01(\x03\x42\x07\n\x05\x65ntry\"s\n\x0eMessageSegment\x12)\n\x05\x66rom_\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12)\n\x05until\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0b\n\x03uri\x18\x03 \x01(\t\"\xd6\x01\n\x0f\x42\x61\x63kwardSegment\x12)\n\x05until\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x46\n\x07segment\x18\x02 \x01(\x0b\x32\x35.dwango.nicolive.chat.service.edge.PackedSegment.Next\x12P\n\x08snapshot\x18\x03 \x01(\x0b\x32>.dwango.nicolive.chat.service.edge.PackedSegment.StateSnapshotb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'dwango.nicolive.chat.service.edge.payload_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_CHUNKEDMESSAGE']._serialized_start=240 + _globals['_CHUNKEDMESSAGE']._serialized_end=677 + _globals['_CHUNKEDMESSAGE_META']._serialized_start=526 + _globals['_CHUNKEDMESSAGE_META']._serialized_end=643 + _globals['_CHUNKEDMESSAGE_SIGNAL']._serialized_start=645 + _globals['_CHUNKEDMESSAGE_SIGNAL']._serialized_end=666 + _globals['_PACKEDSEGMENT']._serialized_start=680 + _globals['_PACKEDSEGMENT']._serialized_end=966 + _globals['_PACKEDSEGMENT_NEXT']._serialized_start=917 + _globals['_PACKEDSEGMENT_NEXT']._serialized_end=936 + _globals['_PACKEDSEGMENT_STATESNAPSHOT']._serialized_start=938 + _globals['_PACKEDSEGMENT_STATESNAPSHOT']._serialized_end=966 + _globals['_CHUNKEDENTRY']._serialized_start=969 + _globals['_CHUNKEDENTRY']._serialized_end=1311 + _globals['_CHUNKEDENTRY_READYFORNEXT']._serialized_start=1276 + _globals['_CHUNKEDENTRY_READYFORNEXT']._serialized_end=1302 + _globals['_MESSAGESEGMENT']._serialized_start=1313 + _globals['_MESSAGESEGMENT']._serialized_end=1428 + _globals['_BACKWARDSEGMENT']._serialized_start=1431 + _globals['_BACKWARDSEGMENT']._serialized_end=1645 +# @@protoc_insertion_point(module_scope) diff --git a/proto/dwango/nicolive/chat/service/edge/payload_pb2.pyi b/proto/dwango/nicolive/chat/service/edge/payload_pb2.pyi new file mode 100644 index 0000000..e675e34 --- /dev/null +++ b/proto/dwango/nicolive/chat/service/edge/payload_pb2.pyi @@ -0,0 +1,231 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" + +import builtins +import collections.abc +import ndgr_client.proto.dwango.nicolive.chat.data.message_pb2 +import ndgr_client.proto.dwango.nicolive.chat.data.origin_pb2 +import ndgr_client.proto.dwango.nicolive.chat.data.state_pb2 +import google.protobuf.descriptor +import google.protobuf.internal.containers +import google.protobuf.internal.enum_type_wrapper +import google.protobuf.message +import google.protobuf.timestamp_pb2 +import sys +import typing + +if sys.version_info >= (3, 10): + import typing as typing_extensions +else: + import typing_extensions + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +@typing.final +class ChunkedMessage(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + class _Signal: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _SignalEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[ChunkedMessage._Signal.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + Flushed: ChunkedMessage._Signal.ValueType # 0 + + class Signal(_Signal, metaclass=_SignalEnumTypeWrapper): ... + Flushed: ChunkedMessage.Signal.ValueType # 0 + + @typing.final + class Meta(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ID_FIELD_NUMBER: builtins.int + AT_FIELD_NUMBER: builtins.int + ORIGIN_FIELD_NUMBER: builtins.int + id: builtins.str + @property + def at(self) -> google.protobuf.timestamp_pb2.Timestamp: ... + @property + def origin(self) -> ndgr_client.proto.dwango.nicolive.chat.data.origin_pb2.NicoliveOrigin: ... + def __init__( + self, + *, + id: builtins.str = ..., + at: google.protobuf.timestamp_pb2.Timestamp | None = ..., + origin: ndgr_client.proto.dwango.nicolive.chat.data.origin_pb2.NicoliveOrigin | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["at", b"at", "origin", b"origin"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["at", b"at", "id", b"id", "origin", b"origin"]) -> None: ... + + META_FIELD_NUMBER: builtins.int + MESSAGE_FIELD_NUMBER: builtins.int + STATE_FIELD_NUMBER: builtins.int + SIGNAL_FIELD_NUMBER: builtins.int + signal: global___ChunkedMessage.Signal.ValueType + @property + def meta(self) -> global___ChunkedMessage.Meta: ... + @property + def message(self) -> ndgr_client.proto.dwango.nicolive.chat.data.message_pb2.NicoliveMessage: ... + @property + def state(self) -> ndgr_client.proto.dwango.nicolive.chat.data.state_pb2.NicoliveState: ... + def __init__( + self, + *, + meta: global___ChunkedMessage.Meta | None = ..., + message: ndgr_client.proto.dwango.nicolive.chat.data.message_pb2.NicoliveMessage | None = ..., + state: ndgr_client.proto.dwango.nicolive.chat.data.state_pb2.NicoliveState | None = ..., + signal: global___ChunkedMessage.Signal.ValueType = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["message", b"message", "meta", b"meta", "payload", b"payload", "signal", b"signal", "state", b"state"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["message", b"message", "meta", b"meta", "payload", b"payload", "signal", b"signal", "state", b"state"]) -> None: ... + def WhichOneof(self, oneof_group: typing.Literal["payload", b"payload"]) -> typing.Literal["message", "state", "signal"] | None: ... + +global___ChunkedMessage = ChunkedMessage + +@typing.final +class PackedSegment(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing.final + class Next(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + URI_FIELD_NUMBER: builtins.int + uri: builtins.str + def __init__( + self, + *, + uri: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["uri", b"uri"]) -> None: ... + + @typing.final + class StateSnapshot(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + URI_FIELD_NUMBER: builtins.int + uri: builtins.str + def __init__( + self, + *, + uri: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["uri", b"uri"]) -> None: ... + + MESSAGES_FIELD_NUMBER: builtins.int + NEXT_FIELD_NUMBER: builtins.int + SNAPSHOT_FIELD_NUMBER: builtins.int + @property + def messages(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ChunkedMessage]: ... + @property + def next(self) -> global___PackedSegment.Next: ... + @property + def snapshot(self) -> global___PackedSegment.StateSnapshot: ... + def __init__( + self, + *, + messages: collections.abc.Iterable[global___ChunkedMessage] | None = ..., + next: global___PackedSegment.Next | None = ..., + snapshot: global___PackedSegment.StateSnapshot | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["next", b"next", "snapshot", b"snapshot"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["messages", b"messages", "next", b"next", "snapshot", b"snapshot"]) -> None: ... + +global___PackedSegment = PackedSegment + +@typing.final +class ChunkedEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing.final + class ReadyForNext(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + AT_FIELD_NUMBER: builtins.int + at: builtins.int + def __init__( + self, + *, + at: builtins.int = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["at", b"at"]) -> None: ... + + BACKWARD_FIELD_NUMBER: builtins.int + PREVIOUS_FIELD_NUMBER: builtins.int + SEGMENT_FIELD_NUMBER: builtins.int + NEXT_FIELD_NUMBER: builtins.int + @property + def backward(self) -> global___BackwardSegment: ... + @property + def previous(self) -> global___MessageSegment: ... + @property + def segment(self) -> global___MessageSegment: ... + @property + def next(self) -> global___ChunkedEntry.ReadyForNext: ... + def __init__( + self, + *, + backward: global___BackwardSegment | None = ..., + previous: global___MessageSegment | None = ..., + segment: global___MessageSegment | None = ..., + next: global___ChunkedEntry.ReadyForNext | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["backward", b"backward", "entry", b"entry", "next", b"next", "previous", b"previous", "segment", b"segment"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["backward", b"backward", "entry", b"entry", "next", b"next", "previous", b"previous", "segment", b"segment"]) -> None: ... + def WhichOneof(self, oneof_group: typing.Literal["entry", b"entry"]) -> typing.Literal["backward", "previous", "segment", "next"] | None: ... + +global___ChunkedEntry = ChunkedEntry + +@typing.final +class MessageSegment(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + FROM__FIELD_NUMBER: builtins.int + UNTIL_FIELD_NUMBER: builtins.int + URI_FIELD_NUMBER: builtins.int + uri: builtins.str + @property + def from_(self) -> google.protobuf.timestamp_pb2.Timestamp: + """Python では from が予約されているため from_ として命名している""" + + @property + def until(self) -> google.protobuf.timestamp_pb2.Timestamp: ... + def __init__( + self, + *, + from_: google.protobuf.timestamp_pb2.Timestamp | None = ..., + until: google.protobuf.timestamp_pb2.Timestamp | None = ..., + uri: builtins.str = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["from_", b"from_", "until", b"until"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["from_", b"from_", "until", b"until", "uri", b"uri"]) -> None: ... + +global___MessageSegment = MessageSegment + +@typing.final +class BackwardSegment(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + UNTIL_FIELD_NUMBER: builtins.int + SEGMENT_FIELD_NUMBER: builtins.int + SNAPSHOT_FIELD_NUMBER: builtins.int + @property + def until(self) -> google.protobuf.timestamp_pb2.Timestamp: ... + @property + def segment(self) -> global___PackedSegment.Next: ... + @property + def snapshot(self) -> global___PackedSegment.StateSnapshot: ... + def __init__( + self, + *, + until: google.protobuf.timestamp_pb2.Timestamp | None = ..., + segment: global___PackedSegment.Next | None = ..., + snapshot: global___PackedSegment.StateSnapshot | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["segment", b"segment", "snapshot", b"snapshot", "until", b"until"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["segment", b"segment", "snapshot", b"snapshot", "until", b"until"]) -> None: ... + +global___BackwardSegment = BackwardSegment diff --git a/scraper_ameblo_api.py b/scraper_ameblo_api.py index debaf17..5e071a6 100644 --- a/scraper_ameblo_api.py +++ b/scraper_ameblo_api.py @@ -37,7 +37,7 @@ def download_image(blog_id, id, save_folder='.'): desc = img['title'] desc_ = f'{desc}_{idx}' if len(data['data']) > 1 else desc img_name = safeify(f'{img_date} ameblo_{blog_id}_{id} {desc_} {file_name}') - ex.submit(download, img_url, Path(save_folder) / img_name, dupe='skip', verbose=1) + ex.submit(download, img_url, Path(save_folder) / img_name, dupe='skip', verbose=1, headers=headers) def download_text(blog_id, id, save_folder='.'): # print(f'Processing {id}...') @@ -60,12 +60,15 @@ def download_text(blog_id, id, save_folder='.'): html = text_folder / safeify(f'{stem}.html') if html.exists(): print(f'{html.name} Already exists! Ignore.') - return - html.write_text(text, encoding='utf8') + else: + html.write_text(text, encoding='utf8') metadata_folder = Path(save_folder) / 'metadata' metadata_folder.mkdir(exist_ok=True, parents=True) metadata = metadata_folder / safeify(f'{stem}.json') - dump_json(b, metadata) + if metadata.exists(): + print(f'{metadata.name} Already exists! Ignore.') + else: + dump_json(b, metadata) else: print(f'[E] cannot fetch data from {url}!') diff --git a/scraper_fantia.py b/scraper_fantia.py index 9aa85c2..76ea698 100644 --- a/scraper_fantia.py +++ b/scraper_fantia.py @@ -1,6 +1,7 @@ import concurrent.futures import re import webbrowser +from datetime import datetime from pathlib import Path from urllib.parse import urljoin @@ -14,17 +15,27 @@ HTML_POSTLIST = "https://fantia.jp/fanclubs/{}/posts?page={}" DEFAULT_UA = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36' +# Default templates for directory and filename formatting +# Directory template variables: +# {fanclub_id}, {fanclub_name}, {fanclub_full_name}, {creator_name} +# {post_id}, {post_title}, {post_date}, {rating} +# Filename template variables (in addition to directory ones): +# {content_id}, {idx}, {stem_cleaned} +# Note: extension (.ext) is always appended automatically and not part of the template +DEFAULT_DIR_TEMPLATE = '{fanclub_full_name} ({fanclub_id})/{post_id} {post_title}' +DEFAULT_FILENAME_TEMPLATE = '{content_id}{idx} {stem_cleaned}' + + class FantiaDownloader: - def __init__(self, key, fanclub=None, output='.', subfolder_template=None, skip_existing=True, quick_stop=True): + def __init__(self, key, fanclub=None, output='.', dir_template=None, filename_template=None, skip_existing=True, quick_stop=True): super().__init__() self.key = key self.fanclub = fanclub if not self.fanclub: print('[W] no fanclub id is given. "downloadAll()" won\'t work.') self.output = Path(output) - self.subfolder_template = subfolder_template or '{fanclub_full_name} ({fanclub_id})' - self.subfolder_name = None - #TODO: self.filename_template = '{id} {title}' + self.dir_template = dir_template or DEFAULT_DIR_TEMPLATE + self.filename_template = filename_template or DEFAULT_FILENAME_TEMPLATE self.skip_existing = skip_existing self.quick_stop = skip_existing and quick_stop self.fanclub_info = None @@ -56,13 +67,32 @@ def download_all(self): with self.fetch(API_FANCLUB.format(self.fanclub)) as r: self.update_fanclub_info(r.json()['fanclub']) - output_full = self.output / self.subfolder_name - print(f'Fanclub {self.fanclub}: download all to {output_full}.') - if output_full.exists(): - existing_ids = [m[1] for f in output_full.iterdir() if (m := re.match(r'(\d+) *', f.name))] + # Get base output dir (fanclub level) for checking existing downloads + # Extract only the fanclub-level part of the template (before any post-level placeholders) + fanclub_subs = self._get_fanclub_substitutes() + # Split dir_template and only format the parts that don't require post info + template_parts = self.dir_template.split('/') + base_parts = [] + for part in template_parts: + if any(p in part for p in ['{post_id}', '{post_title}', '{post_date}', '{rating}']): + break + base_parts.append(part.format(**fanclub_subs)) + base_dir = self.output / '/'.join(base_parts) if base_parts else self.output + + print(f'Fanclub {self.fanclub}: download all to {base_dir}.') + existing_ids = [] + if base_dir.exists(): + # Check for subdirectories starting with post_id (new structure) + for f in base_dir.iterdir(): + if f.is_dir() and (m := re.match(r'^(\d+)\b', f.name)): + existing_ids.append(m.group(1)) + # Also check for files starting with post_id (old flat structure) + for f in base_dir.iterdir(): + if f.is_file() and (m := re.match(r'^(\d+)\b', f.name)): + existing_ids.append(m.group(1)) existing_ids = list(dict.fromkeys(existing_ids)) - else: - existing_ids = [] + existing_ids = set(map(int, existing_ids)) + print(f'{len(existing_ids)} ID(s) have already been downloaded.') def fetch_all(): @@ -106,19 +136,57 @@ def fetch_gallery_page(self, page): r = self.fetch(url) r.encoding = 'utf-8' html = r.text - return re.findall(r'\/posts\/(?P[0-9]{1,8})"', html) + return sorted(map(int, re.findall(r'\/posts\/(?P[0-9]{1,8})"', html)), reverse=True) def update_fanclub_info(self, d): self.fanclub_info = d - substitutes = dict( - fanclub_full_name=self.fanclub_info['fanclub_name_with_creator_name'], - fanclub_name=self.fanclub_info['fanclub_name'], - creater_name=self.fanclub_info['creator_name'], + + def _get_fanclub_substitutes(self): + """Get template substitutes from fanclub info.""" + if not self.fanclub_info: + return {} + return dict( + fanclub_full_name=safeify(str(self.fanclub_info['fanclub_name_with_creator_name'])), + fanclub_name=safeify(str(self.fanclub_info['fanclub_name'])), + creator_name=safeify(str(self.fanclub_info['creator_name'])), fanclub_id=self.fanclub_info['id'] ) - substitutes = {k: safeify(str(v)) for k, v in substitutes.items()} - if not self.subfolder_name: - self.subfolder_name = self.subfolder_template.format(**substitutes) + + def _get_post_substitutes(self, post_data): + """Get template substitutes from post data.""" + # Parse posted_at date (format: "Fri, 16 Aug 2024 07:15:36 +0900") + post_date = '' + if posted_at := post_data.get('posted_at'): + try: + dt = datetime.strptime(posted_at, "%a, %d %b %Y %H:%M:%S %z") + post_date = dt.strftime('%Y%m%d') + except ValueError: + post_date = '' + + return dict( + post_id=post_data.get('id', ''), + post_title=safeify(str(post_data.get('title', ''))), + post_date=post_date, + rating=post_data.get('rating', '') + ) + + def _format_dir(self, post_substitutes): + """Format directory path using template and substitutes.""" + subs = {**self._get_fanclub_substitutes(), **post_substitutes} + return self.dir_template.format(**subs) + + def _format_filename(self, post_substitutes, content_id, idx_string, stem_cleaned, ext): + """Format filename using template and substitutes.""" + subs = { + **self._get_fanclub_substitutes(), + **post_substitutes, + 'content_id': content_id, + 'idx': idx_string, + 'stem_cleaned': stem_cleaned + } + # Format the template, strip to remove trailing spaces, then append extension + filename_without_ext = self.filename_template.format(**subs).strip() + return f'{filename_without_ext}.{ext}' def get_post_photos(self, id): print(f'Fetching post {id}...') @@ -139,12 +207,19 @@ def get_post_photos(self, id): if not self.fanclub_info: self.update_fanclub_info(d['post']['fanclub']) + post_data = d['post'] + post_subs = self._get_post_substitutes(post_data) + output_dir = self.output / self._format_dir(post_subs) + # Ensure multi-level directory exists + output_dir.mkdir(parents=True, exist_ok=True) + with concurrent.futures.ThreadPoolExecutor(max_workers=3) as ex: - if thumb := d['post'].get('thumb', None): + if thumb := post_data.get('thumb', None): img_url = thumb['original'] stem, ext = get_webname(img_url).rsplit('.', 1) - ex.submit(download, img_url, filename= self.output / self.subfolder_name / f'{id} !cover.{ext}', verbose=1) - if post_contents := d['post'].get('post_contents', None): + cover_filename = f'!cover.{ext}' + ex.submit(download, img_url, filename=output_dir / cover_filename, verbose=1) + if post_contents := post_data.get('post_contents', None): for c in post_contents: cid = c['id'] if photos := c.get('post_content_photos', None): @@ -152,14 +227,17 @@ def get_post_photos(self, id): img_url = p['url']['original'] stem, ext = get_webname(img_url).rsplit('.', 1) # Clean up the filename; remove all the UUID-ish garbage - stem = re.sub(r'^[0-9a-fA-F]{8}_(.+)$', r'\1', stem) - stem = re.sub(r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}', '', stem) + stem_cleaned = re.sub(r'^[0-9a-fA-F]{8}_(.+)$', r'\1', stem) + stem_cleaned = re.sub(r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}', '', stem_cleaned) + stem_cleaned = stem_cleaned.strip() idx_string = '_' + str(idx).zfill(len(str(len(photos)))) if len(photos) > 1 else '' - stem = f'{id} {cid}{idx_string} {stem}'.strip() - ex.submit(download, img_url, filename=self.output / self.subfolder_name / f'{stem}.{ext}', verbose=1) + filename = self._format_filename(post_subs, cid, idx_string, stem_cleaned, ext) + ex.submit(download, img_url, filename=output_dir / filename, verbose=1) if 'download_uri' in c: dl_url = urljoin('https://fantia.jp', c['download_uri']) - ex.submit(download, dl_url, filename=self.output / self.subfolder_name / f'{id} {cid} {c["filename"]}', session=self.session, verbose=1) + # For download files, use original filename with content_id prefix + dl_filename = f'{cid} {c["filename"]}' + ex.submit(download, dl_url, filename=output_dir / dl_filename, session=self.session, verbose=1) if __name__ == "__main__": pass diff --git a/util.py b/util.py index 3648919..a141cbe 100644 --- a/util.py +++ b/util.py @@ -6,6 +6,7 @@ import time import hashlib import shutil +import unicodedata from datetime import datetime from pathlib import Path from urllib.parse import unquote @@ -14,7 +15,7 @@ # so we can use this file in other projects without installing them. # or to copy and paste the functions to other public projects directly. # to install them all, you can do: -# pip install requests lxml beautifulsoup4 python-dateutil pytz pyperclip wcwidth rich browser_cookie3 +# pip install requests lxml beautifulsoup4 python-dateutil pytz pyperclip wcwidth rich browsercookie # ==================== CONSTANTS ==================== @@ -27,10 +28,14 @@ TWITTER_FILENAME_RE = re.compile(r'^(?P\w+)-(?P\d+)-(?P\d{8})_(?P