示例#1
0
文件: api.py 项目: vbabiy/videolog
def api_video_unsubscribe(channel=None, video=None):
    """API video unsubscribe route handler.

    Handles unsubscribing from video's channel.

    Args:
        channel (Optional[str]): YouTube channel ID.
        video (Optional[str]): YouTube video ID.

    Returns:
        flask.Response: Whether operation has succeeded (bool in JSON).
    """

    try:
        auth_check()
    except Exception as e:
        return flask.jsonify(False)

    if channel is not None and video is not None:
        for subscription in yt_get_subscriptions():
            if subscription['snippet']['resourceId']['channelId'] == channel:
                if yt_remove_subscription(subscription['id']):
                    return flask.jsonify(True)
                else:
                    return flask.jsonify(False)

    return flask.jsonify(False)
示例#2
0
文件: api.py 项目: vbabiy/videolog
def api_video_comments(channel=None, video=None):
    """API video comments route handler.

    Returns JSON file containing video's comments.

    Args:
        channel (Optional[str]): YouTube channel ID.
        video (Optional[str]): YouTube video ID.

    Returns:
        flask.Response: Comments (JSON file).
    """

    try:
        auth_check()
    except Exception as e:
        return flask.jsonify(False)

    if channel is not None and video is not None:
        comments = yt_get_comments(video)

        return flask.Response(json.dumps(comments, indent=2, sort_keys=True),
                              mimetype='application/json',
                              headers={
                                  'Content-Disposition':
                                  'attachment;filename=' + video +
                                  '.comments.json'
                              })
示例#3
0
文件: api.py 项目: vbabiy/videolog
def api_video_play(channel=None, video=None):
    """API video play route handler.

    Handles marking video as played.

    Args:
        channel (Optional[str]): YouTube channel ID.
        video (Optional[str]): YouTube video ID.

    Returns:
        flask.Response: Whether operation has succeeded (bool in JSON).
    """

    try:
        auth_check()
    except Exception as e:
        return flask.jsonify(False)

    if channel is not None and video is not None:
        db = get_db()
        user_id = flask.session['user']['id']

        if video not in db[user_id][channel]['played']:
            db[user_id][channel]['played'][video] = (
                datetime.datetime.utcnow().replace(
                    microsecond=0, tzinfo=datetime.timezone.utc).isoformat())
            update_db(db)

        return flask.jsonify(True)
示例#4
0
文件: api.py 项目: vbabiy/videolog
def api_video_subscribe(channel=None, video=None):
    """API video subscribe route handler.

    Handles subscribing to video's channel.

    Args:
        channel (Optional[str]): YouTube channel ID.
        video (Optional[str]): YouTube video ID.

    Returns:
        flask.Response: Whether operation has succeeded (bool in JSON).
    """

    try:
        auth_check()
    except Exception as e:
        return flask.jsonify(False)

    if channel is not None and video is not None:
        if yt_create_subscription(channel):
            return flask.jsonify(True)
        else:
            return flask.jsonify(False)

    return flask.jsonify(False)
示例#5
0
文件: api.py 项目: vbabiy/videolog
def api_video_playlists(channel=None, video=None):
    """API video playlists route handler.

    Handles updating video's playlists.

    Args:
        channel (Optional[str]): YouTube channel ID.
        video (Optional[str]): YouTube video ID.

    Returns:
        flask.Response: Whether operation has succeeded (bool in JSON).
    """

    try:
        auth_check()
    except Exception as e:
        return flask.jsonify(False)

    data = flask.request.args.get('data', None)

    if channel is not None and video is not None and data is not None:
        for playlist_id, include in json.loads(
                urllib.parse.unquote(data)).items():
            if include:
                yt_insert_to_playlist(video, playlist_id)
            else:
                yt_remove_from_playlist(video, playlist_id)

        return flask.jsonify(True)
示例#6
0
文件: api.py 项目: vbabiy/videolog
def api_video_unplay(channel=None, video=None):
    """API video unplay route handler.

    Handles marking video as unplayed.

    Args:
        channel (Optional[str]): YouTube channel ID.
        video (Optional[str]): YouTube video ID.

    Returns:
        flask.Response: Whether operation has succeeded (bool in JSON).
    """

    try:
        auth_check()
    except Exception as e:
        return flask.jsonify(False)

    if channel is not None and video is not None:
        db = get_db()
        if video in db[flask.session['user']['id']][channel]['played']:
            db[flask.session['user']['id']][channel]['played'].pop(video)
            update_db(db)

        return flask.jsonify(True)
示例#7
0
文件: web.py 项目: vbabiy/videolog
def web_channels_update():
    """Handles channel tracking.

    Tracks or untracks YouTube channels using connected account or by user
        query. Then redirects to channels management page.

    Uses GET query parameters ``tracks`` and ``query``.

    Returns:
        flask.Response: Channels management page.

    See also:
        :func:`~videolog.web.web_channels_update_tracks()`,
        :func:`~videolog.web.web_channels_update_query()`
    """

    try:
        auth_check()
    except Exception as e:
        return flask.redirect(str(e))

    tracks = flask.request.args.get('tracks', None)
    query = flask.request.args.get('query', None)

    if tracks is not None:
        web_channels_update_tracks(tracks)

    if query is not None:
        try:
            web_channels_update_query(query)
        except:
            flask.session['channels_query_error'] = True
            return flask.redirect('channels')

    return flask.redirect('channels')
示例#8
0
文件: api.py 项目: vbabiy/videolog
def api_video_unarchive(channel=None, video=None):
    """API video unarchive route handler.

    Handles video unarchiving.

    Args:
        channel (Optional[str]): YouTube channel ID.
        video (Optional[str]): YouTube video ID.

    Returns:
        flask.Response: Whether operation has succeeded (bool in JSON).
    """

    try:
        auth_check()
    except Exception as e:
        return flask.jsonify(False)

    if channel is not None and video is not None:
        db = get_db()
        user_id = flask.session['user']['id']
        if video in db[user_id][channel]['archived']:
            if yt_remove_from_playlist(
                    video, db[user_id][channel]['archived'][video]):
                db[user_id][channel]['archived'].pop(video)
                update_db(db)
            else:
                return flask.jsonify(False)

        return flask.jsonify(True)
示例#9
0
文件: web.py 项目: vbabiy/videolog
def web_archive_comments():
    """Downloads comments.

    Downloads archived videos' comments. Places them to subdirectories by
        YouTube channel ID and generates ZIP archive.

    Returns:
        flask.Response: ZIP archive of archived videos' comments.
    """

    try:
        auth_check()
    except Exception as e:
        return flask.redirect(str(e))

    archive_comments = io.BytesIO()

    with zipfile.ZipFile(archive_comments, 'w') as zf:
        for video_id in db_get_archived():
            video = yt_get_video(video_id)
            comments = yt_get_comments(video_id)

            zf.writestr(
                video['snippet']['channelId'] + '/' + video_id +
                '.comments.json', json.dumps(comments,
                                             indent=2,
                                             sort_keys=True))

    archive_comments.seek(0)
    return flask.send_file(archive_comments,
                           mimetype='application/zip',
                           as_attachment=True,
                           attachment_filename='archive_comments.zip')
示例#10
0
文件: web.py 项目: vbabiy/videolog
def web_archive_insert_rename(type=None, id=None):
    """Handles archive management.

    Inserts video to archive, imports entire playlist into archive or
        renames given archive.

    Uses GET query parameter ``name`` (new given archive name).

    Args:
        type (Optional[str]): 'video', 'playlist' or 'rename'.
        id (Optional[str]): YouTube video or playlist ID.

    Returns:
        flask.Response: Archive management view.

    See also:
        :func:`~videolog.web.web_archive_insert_video()`,
        :func:`~videolog.web.web_archive_import_playlist()`,
        :func:`~videolog.web.web_archive_rename()`
    """

    try:
        auth_check()
    except Exception as e:
        return flask.redirect(str(e))

    if id is not None:
        if type == 'video':
            web_archive_insert_video(id)
        elif type == 'playlist':
            web_archive_import_playlist(id)
        elif type == 'rename':
            web_archive_rename(id, flask.request.args.get('name', None))

    return flask.redirect(flask.url_for('archive'))
示例#11
0
文件: web.py 项目: vbabiy/videolog
def web_channels_subscriptions():
    """Handles channel subscriptions.

    Subscribes user to or unsubscribes user from given YouTube channel,
        then redirects to given page. Time delay is needed, because of
        YouTube Data API delays.

    Uses GET query parameter ``update`` (URL encoded JSON data). Its data
        contain ``id`` (YouTube subscription ID), ``subscribe`` (flag whether
        to subscribe or unsubscribe) and ``redirect`` (where to redirect after).

    Returns:
        flask.Response: Channels management or video detail view page.
    """

    try:
        auth_check()
    except Exception as e:
        return flask.redirect(str(e))

    update = flask.request.args.get('update', None)

    if update is not None:
        update_data = json.loads(urllib.parse.unquote(update))

        if update_data['subscribe']:
            yt_create_subscription(update_data['id'])
            time.sleep(10)
        else:
            yt_remove_subscription(update_data['id'])
            time.sleep(10)

        return flask.redirect(update_data['redirect'])

    return flask.redirect('channels')
示例#12
0
文件: api.py 项目: vbabiy/videolog
def api_video_rate(channel=None, video=None):
    """API video rating route handler.

    Handles video rating.

    Args:
        channel (Optional[str]): YouTube channel ID.
        video (Optional[str]): YouTube video ID.

    Returns:
        flask.Response: Whether operation has succeeded (bool in JSON).
    """

    try:
        auth_check()
    except Exception as e:
        return flask.jsonify(False)

    rating = flask.request.args.get('rating', None)

    if channel is not None and video is not None and (rating == 'like'
                                                      or rating == 'dislike'
                                                      or rating == 'none'):
        yt_get_client().videos().rate(id=video, rating=rating).execute()
        return flask.jsonify(True)
示例#13
0
文件: web.py 项目: vbabiy/videolog
def web_channels_track(user=None,
                       subs=[],
                       tracks=[],
                       tracking=True,
                       error=False):
    """Channels track route handler.

    Renders tracking using connected YouTube account page.

    Args:
        user (Optional[dict]): User object (id, name, thumbnail).
        subs (Optional[list]): List of subscribed channels.
        tracks (Optional[list]): List of tracked channels.
        tracking (Optional[bool]): Whether user is viewing
            'Track using connected account' page.
        error (Optional[bool]): Whether error has occured.

    Returns:
        flask.Response: Channel management page.
    """

    try:
        auth_check()
    except Exception as e:
        return flask.redirect(str(e))

    return flask.render_template('channels.html',
                                 user=flask.session['user'],
                                 subs=yt_get_subscriptions(),
                                 tracks=db_get_tracks(sort_by_played=None),
                                 tracking=tracking,
                                 error=error)
示例#14
0
文件: web.py 项目: vbabiy/videolog
def web_archive_config():
    """Generates youtube-dl configuration file.

    Generates configuration file for youtube-dl. Gets values from GET query
        parameters.

    Returns:
        flask.Response: youtube-dl configuration file.
    """

    try:
        auth_check()
    except Exception as e:
        return flask.redirect(str(e))

    socket_timeout = flask.request.args.get('ytdl-socket-timeout', '120')
    retries = flask.request.args.get('ytdl-retries', 'infinite')
    output = flask.request.args.get('ytdl-output',
                                    '%(uploader_id)s/%(id)s.%(ext)s')
    overwrites = flask.request.args.get('ytdl-overwrites', 'false') == 'true'
    info_json = flask.request.args.get('ytdl-info-json', 'true') == 'true'
    thumbnail = flask.request.args.get('ytdl-thumbnail', 'true') == 'true'
    format = flask.request.args.get(
        'ytdl-format', 'bestvideo[vcodec^=vp]' +
        '+bestaudio[acodec=opus]/bestvideo+bestaudio[acodec=opus]' +
        '/bestvideo+bestaudio/best')
    merge_format = flask.request.args.get('ytdl-merge-format', 'mkv')
    all_subs = flask.request.args.get('ytdl-all-subs', 'true') == 'true'
    sub_format = flask.request.args.get('ytdl-sub-format', 'srt/best')
    convert_subs = flask.request.args.get('ytdl-convert-subs', 'srt')

    config = io.BytesIO()

    config.write(('--socket-timeout ' + socket_timeout + '\n').encode('utf-8'))
    config.write(('--retries ' + retries + '\n').encode('utf-8'))
    config.write(('--output ' + output + '\n').encode('utf-8'))
    if not overwrites:
        config.write('--no-overwrites\n'.encode('utf-8'))
    if info_json:
        config.write('--write-info-json\n'.encode('utf-8'))
    if thumbnail:
        config.write('--write-thumbnail\n'.encode('utf-8'))
    config.write(('--format ' + format + '\n').encode('utf-8'))
    config.write(
        ('--merge-output-format ' + merge_format + '\n').encode('utf-8'))
    if all_subs:
        config.write('--all-subs\n'.encode('utf-8'))
    config.write(('--sub-format ' + sub_format + '\n').encode('utf-8'))
    config.write(('--convert-subs ' + convert_subs + '\n').encode('utf-8'))

    config.seek(0)

    return flask.Response(
        config,
        mimetype='text/plain',
        headers={'Content-Disposition': 'attachment;filename=config.txt'})
示例#15
0
def test_auth_check_credentials():
    import videolog.auth
    with app.test_client() as client:
        with client.session_transaction() as session:
            session.clear()
        client.get('/')

        with pytest.raises(Exception) as e:
            auth_check()
        assert str(e.value) == 'authorize'
示例#16
0
def test_auth_check_token():
    import videolog.auth
    with app.test_client() as client:
        with client.session_transaction() as session:
            session['credentials'] = {}
        client.get('/')

        with pytest.raises(Exception) as e:
            auth_check()
        assert str(e.value) == 'logout'
示例#17
0
def test_auth_check_grant():
    import videolog.auth
    flexmock(requests, get=flexmock(status_code=400))
    with app.test_client() as client:
        with client.session_transaction() as session:
            session['credentials'] = {'token': 'test_token'}
        client.get('/')

        with pytest.raises(Exception) as e:
            auth_check()
        assert str(e.value) == 'logout'
示例#18
0
文件: web.py 项目: vbabiy/videolog
def web_archive():
    """Archive route handler.

    Renders archive management view.

    Returns:
        flask.Response: Archive management view.
    """

    try:
        auth_check()
    except Exception as e:
        return flask.redirect(str(e))

    return flask.render_template('archive.html',
                                 user=flask.session['user'],
                                 archives=db_get_archives())
示例#19
0
文件: web.py 项目: vbabiy/videolog
def web_index():
    """Index route handler.

    Requests sync of archives with YouTube and redirects to videos list view.

    Returns:
        flask.Response: Index page.
    """

    try:
        auth_check()
    except Exception as e:
        return flask.redirect(str(e))

    db_update_archives()

    return flask.redirect('videos')
示例#20
0
文件: web.py 项目: vbabiy/videolog
def web_archive_batch():
    """Generates youtube-dl batch file.

    Generates batch file for youtube-dl to download archived videos. Optionally
        uses uploaded youtube-dl ``archive`` file to only include videos not
        yet downloaded.

    Returns:
        flask.Response: youtube-dl batch file.
    """

    try:
        auth_check()
    except Exception as e:
        return flask.redirect(str(e))

    batch = set()

    if 'archiveFile' in flask.request.files:
        file = flask.request.files['archiveFile']
        if file.filename != '':
            if file and allowed_file(file.filename):
                downloaded = [
                    line.decode('utf-8').rstrip('\n').split(' ')[1]
                    for line in file.readlines()
                ]
                archived = db_get_archived()

                for video_id in archived:
                    if video_id not in downloaded:
                        batch.add(video_id)
    else:
        archived = db_get_archived()

        for video_id in archived:
            batch.add(video_id)

    return flask.Response(
        '\n'.join(list(batch)),
        mimetype='text/plain',
        headers={'Content-Disposition': 'attachment;filename=batch.txt'})
示例#21
0
文件: api.py 项目: vbabiy/videolog
def api_video_archive(channel=None, video=None):
    """API video archive route handler.

    Handles video archiving.

    Args:
        channel (Optional[str]): YouTube channel ID.
        video (Optional[str]): YouTube video ID.

    Returns:
        flask.Response: Whether operation has succeeded (bool in JSON).
    """

    try:
        auth_check()
    except Exception as e:
        return flask.jsonify(False)

    if channel is not None and video is not None:
        db = get_db()
        archive = None
        for playlist in db_get_archives():
            if playlist['contentDetails']['itemCount'] < 5000:
                archive = playlist
                break

        if archive is None:
            archive = yt_create_playlist()

        if yt_insert_to_playlist(video, archive['id']):
            db[flask.session['user']
               ['id']][channel]['archived'][video] = archive['id']
            update_db(db)
            return flask.jsonify(True)
        else:
            return flask.jsonify(False)
示例#22
0
文件: web.py 项目: vbabiy/videolog
def web_videos(user=None, tracks=[], subs=[], channel=None, video=None):
    """Videos route handler.

    Renders videos list or video detail view.

    Uses GET query parameters ``archived`` and ``played`` for video list filtering.

    Args:
        user (Optional[dict]): User object (id, name, thumbnail).
        tracks (Optional[list]): List of tracked channels.
        subs (Optional[list]): List of subscribed channels.
        channel (Optional[str]): Viewed channel.
        video (Optional[str]): Viewed video.

    Returns:
        flask.Response: Videos list or video detail view page.

    See also:
        :func:`~videolog.web.web_videos_filter()`,
        :func:`~videolog.web.web_videos_random_unplayed()`,
        :func:`~videolog.web.web_videos_next_unplayed()`,
        :func:`~videolog.web.web_videos_random_archived()`,
        :func:`~videolog.web.web_videos_random_all()`
    """

    try:
        auth_check()
    except Exception as e:
        return flask.redirect(str(e))

    tracks = db_get_tracks(sort_by_played=True)

    if channel is None:
        if tracks:
            return flask.redirect(
                flask.url_for('videos', channel=tracks[0]['id']))
        else:
            return flask.render_template('index.html',
                                         user=flask.session['user'])
    else:
        if video is None:
            archived = flask.request.args.get('archived', 'null')
            played = flask.request.args.get('played', 'null')
            videos = web_videos_filter(channel, tracks, archived, played)

            return flask.render_template('index.html',
                                         user=flask.session['user'],
                                         tracks=tracks,
                                         channel=channel,
                                         videos=videos,
                                         archived=archived,
                                         played=played)
        elif video == 'random-unplayed':
            return web_videos_random_unplayed(channel)
        elif video == 'next-unplayed':
            return web_videos_next_unplayed(channel)
        elif video == 'random-archived':
            return web_videos_random_archived(channel)
        elif video == 'random-all':
            return web_videos_random_all(channel)
        else:
            return flask.render_template(
                'index.html',
                user=flask.session['user'],
                tracks=tracks,
                subs=yt_get_subscriptions(list_only=True),
                channel=channel,
                video=yt_get_video(video))