Add NewPipe subscriptions import and export

Closes #82

Signed-off-by: Jesús <heckyel@hyperbola.info>
This commit is contained in:
James Taylor 2021-09-01 19:13:40 -07:00 committed by Jesús
parent fc0fa9aaba
commit cd3383e6e3
No known key found for this signature in database
GPG Key ID: F6EE7BC59A315766
2 changed files with 49 additions and 11 deletions

View File

@ -684,7 +684,7 @@ def check_specific_channels(channel_ids):
channel_names.update(channel_id_name_list)
check_channels_if_necessary(channel_ids)
CHANNEL_ID_RE = re.compile(r'UC[-_\w]{22}')
@yt_app.route('/import_subscriptions', methods=['POST'])
def import_subscriptions():
@ -702,15 +702,36 @@ def import_subscriptions():
mime_type = file.mimetype
if mime_type == 'application/json':
file = file.read().decode('utf-8')
info = file.read().decode('utf-8')
if info == '':
return '400 Bad Request: File is empty', 400
try:
file = json.loads(file)
info = json.loads(info)
except json.decoder.JSONDecodeError:
traceback.print_exc()
return '400 Bad Request: Invalid json file', 400
channels = []
try:
channels = ((item['snippet']['resourceId']['channelId'], item['snippet']['title']) for item in file)
if 'app_version_int' in info: # NewPipe Format
for item in info['subscriptions']:
# Other service, such as SoundCloud
if item.get('service_id', 0) != 0:
continue
channel_url = item['url']
channel_id_match = CHANNEL_ID_RE.search(channel_url)
if channel_id_match:
channel_id = channel_id_match.group(0)
else:
print('WARNING: Could not find channel id in url',
channel_url)
continue
channels.append((channel_id, item['name']))
else: # Old Google Takeout format
for item in info:
snippet = item['snippet']
channel_id = snippet['resourceId']['channelId']
channels.append((channel_id, snippet['title']))
except (KeyError, IndexError):
traceback.print_exc()
return '400 Bad Request: Unknown json structure', 400
@ -738,8 +759,7 @@ def import_subscriptions():
for row in reader:
if not row or row[0].lower().strip() == 'channel id':
continue
elif len(row) > 1 and re.fullmatch(r'UC[-_\w]{22}',
row[0].strip()):
elif len(row) > 1 and CHANNEL_ID_RE.fullmatch(row[0].strip()):
channels.append( (row[0], row[-1]) )
else:
print('WARNING: Unknown row format:', row)
@ -767,7 +787,7 @@ def export_subscriptions():
_get_subscribed_channels(cursor)):
if muted and not include_muted:
continue
if request.values['export_format'] == 'json':
if request.values['export_format'] == 'json_google_takeout':
sub_list.append({
'kind': 'youtube#subscription',
'snippet': {
@ -780,21 +800,38 @@ def export_subscriptions():
'title': channel_name,
},
})
elif request.values['export_format'] == 'json_newpipe':
sub_list.append({
'service_id': 0,
'url': 'https://www.youtube.com/channel/' + channel_id,
'name': channel_name,
})
elif request.values['export_format'] == 'opml':
sub_list.append({
'channel_name': channel_name,
'channel_id': channel_id,
})
if request.values['export_format'] == 'json':
date_time = time.strftime('%Y%m%d%H%M', time.localtime())
if request.values['export_format'] == 'json_google_takeout':
r = flask.Response(json.dumps(sub_list), mimetype='text/json')
cd = 'attachment; filename="subscriptions.json"'
cd = 'attachment; filename="subscriptions_%s.json"' % date_time
r.headers['Content-Disposition'] = cd
return r
elif request.values['export_format'] == 'json_newpipe':
r = flask.Response(json.dumps({
'app_version': '0.21.9',
'app_version_int': 975,
'subscriptions': sub_list,
}), mimetype='text/json')
file_name = 'newpipe_subscriptions_%s_youtube-local.json' % date_time
cd = 'attachment; filename="%s"' % file_name
r.headers['Content-Disposition'] = cd
return r
elif request.values['export_format'] == 'opml':
r = flask.Response(
flask.render_template('subscriptions.xml', sub_list=sub_list),
mimetype='text/xml')
cd = 'attachment; filename="subscriptions.xml"'
cd = 'attachment; filename="subscriptions_%s.xml"' % date_time
r.headers['Content-Disposition'] = cd
return r
else:

View File

@ -29,7 +29,8 @@
<h2>Export subscriptions</h2>
<div class="subscriptions-export-options">
<select id="export-type" name="export_format" title="Export format">
<option value="json">JSON</option>
<option value="json_newpipe">JSON (NewPipe)</option>
<option value="json_google_takeout">JSON (Old Google Takeout Format)</option>
<option value="opml">OPML (RSS, no tags)</option>
</select>
<label for="include-muted">Include muted</label>