Пример #1
0
    def start(self, glossary, save_dir, chunk_size=1024 * 50):
        """

        :param dict[str, dict[str, int | str]] | list[str] glossary:
        :param str | Path save_dir:
        :param int chunk_size: 一度にサーバーに要求するファイルサイズ
        :rtype: bool
        """
        utils.check_arg(locals())
        self.logger.debug("Directory to save in: %s", save_dir)
        self.logger.debug("Dictionary of Videos: %s", glossary)
        self.save_dir = utils.make_dir(save_dir)
        if isinstance(glossary, list):
            glossary = get_infos(glossary, self.logger)
        self.glossary = glossary
        self.logger.info(Msg.nd_start_dl_video, len(self.glossary),
                         list(self.glossary))

        for index, video_id in enumerate(self.glossary.keys()):
            self.logger.info(Msg.nd_download_video, index + 1, len(glossary),
                             video_id, self.glossary[video_id][KeyGTI.TITLE])
            self._download(video_id, chunk_size)
            if len(glossary) > 1:
                time.sleep(1)
        return True
Пример #2
0
    def fetch_meta(self, with_header=True):
        """
        マイリストのメタ情報を表示する。

        :param bool with_header:
        :rtype: list[list[str]]
        """
        utils.check_arg(locals())
        self.logger.info(Msg.ml_loading_mylists)

        counts = len(
            json.loads(self.session.get(URL.URL_ListDef).text)["mylistitem"])
        if with_header:
            container = [["ID", "名前", "項目数", "状態", "作成日", "説明文"]]
        else:
            container = []
        # とりあえずマイリストのデータ
        container.append(
            [utils.DEFAULT_ID, utils.DEFAULT_NAME, counts, "非公開", "--", ""])

        # その他のマイリストのデータ
        # 作成日順に並び替えてから情報を得る
        for item in sorted(self.mylists.values(),
                           key=lambda this: this["since"]):
            response = self.session.get(URL.URL_ListOne,
                                        params={
                                            "group_id": item["id"]
                                        }).text
            counts = len(json.loads(response)["mylistitem"])

            container.append([
                item[MKey.ID], item[MKey.NAME], counts, item[MKey.PUBLICITY],
                item[MKey.SINCE], item[MKey.DESCRIPTION]
            ])
        return container
Пример #3
0
    def purge_mylist(self, list_id, confident=False):
        """
        指定したマイリストを削除する。

        :param int | str list_id: マイリストの名前またはID
        :param bool confident:
        :rtype: bool
        """
        utils.check_arg(locals())
        if utils.ALL_ITEM == list_id:
            raise utils.MylistError(Err.cant_perform_all)
        list_id, list_name = self._get_list_id(list_id)

        if list_id == utils.DEFAULT_ID:
            raise utils.MylistError(Err.deflist_to_create_or_purge)
        if not confident and not self._confirmation("purge", list_name):
            print(Msg.ml_answer_no)
            return False

        res = self.get_response("purge", list_id=list_id)
        if res["status"] != "ok":
            self.logger.error(Err.failed_to_purge, list_name, res["status"])
            return False
        else:
            self.logger.info(Msg.ml_done_purge, list_name)
            del self.mylists[list_id]
            return True
Пример #4
0
    def _download(self, video_id, chunk_size=1024 * 50):
        """
        :param str video_id: 動画ID (e.g. sm1234)
        :param int chunk_size: 一度にサーバーに要求するファイルサイズ
        :rtype: bool
        """
        utils.check_arg(locals())
        db = self.glossary[video_id]
        if video_id.startswith("so"):
            redirected = self.session.get(URL.URL_Watch +
                                          video_id).url.split("/")[-1]
            db[KeyGTI.V_OR_T_ID] = redirected
        self.logger.debug(
            "Video ID: %s and its Thread ID (of officials):"
            " %s", video_id, db[KeyGTI.V_OR_T_ID])

        response = utils.get_from_getflv(db[KeyGTI.V_OR_T_ID], self.session,
                                         self.logger)

        vid_url = response[KeyGetFlv.VIDEO_URL]
        is_premium = response[KeyGetFlv.IS_PREMIUM]

        # 動画視聴ページに行ってCookieをもらってくる
        self.session.head(URL.URL_Watch + video_id)
        # connect timeoutを10秒, read timeoutを30秒に設定
        # ↓この時点ではダウンロードは始まらず、ヘッダーだけが来ている
        video_data = self.session.get(url=vid_url,
                                      stream=True,
                                      timeout=(10.0, 30.0))
        db[KeyGTI.FILE_SIZE] = int(video_data.headers["content-length"])
        self.logger.debug("File Size: %s (Premium: %s)", db[KeyGTI.FILE_SIZE],
                          [False, True][int(is_premium)])

        return self._saver(video_id, video_data, chunk_size)
Пример #5
0
    def _get_list_id(self, search_for):
        """
        指定されたIDまたは名前を持つマイリストのIDを得る。

        :param int | str search_for: マイリスト名またはマイリストID
        :rtype: (int, str)
        """
        utils.check_arg(locals())
        result = self.get_list_id(search_for)

        if result.get("error") is True:
            if result.get("err_dic"):
                # 同じ名前のマイリストが複数あったとき
                self.logger.error(result.get("err_msg"))
                for single in result.get("err_dic").values():
                    print(Err.name_ambiguous_detail.format(**single),
                          file=sys.stderr)
                sys.exit()
            else:
                # 存在しなかったとき
                raise MylistNotFoundError(result.get("err_msg"))
        else:
            list_id = result["list_id"]
            list_name = result["list_name"]
            self.logger.debug("List_id: %s, List_name: %s", list_id, list_name)
            return list_id, list_name
Пример #6
0
    def create_mylist(self, mylist_name, is_public=False, description=""):
        """
        mylist_name を名前に持つマイリストを作る。

        :param str mylist_name: マイリストの名前
        :param bool is_public: True なら公開マイリストになる
        :param str description: マイリストの説明文
        :rtype: bool
        """
        utils.check_arg(locals())
        if utils.ALL_ITEM == mylist_name:
            raise utils.MylistError(Err.cant_perform_all)
        if mylist_name == "" or mylist_name == utils.DEFAULT_NAME:
            raise utils.MylistError(Err.cant_create)
        res = self.get_response("create",
                                is_public=is_public,
                                mylist_name=mylist_name,
                                description=description)
        if res["status"] != "ok":
            self.logger.error(Err.failed_to_create, mylist_name, res)
            return False
        else:
            self.mylists = self.get_mylists_info()
            item = self.mylists[res[MKey.ID]]
            self.logger.info(Msg.ml_done_create, res[MKey.ID], item[MKey.NAME],
                             item[MKey.PUBLICITY], item[MKey.DESCRIPTION])
            if mylist_name != item[MKey.NAME]:
                self.logger.info(Err.name_replaced, mylist_name,
                                 item[MKey.NAME])
            return True
Пример #7
0
    def show(self, list_id, file_name=None, table=False, survey=False):
        """
        そのマイリストに登録された動画を一覧する。

        :param int | str list_id: マイリストの名前またはID。0で「とりあえずマイリスト」。
        :param str | Path | None file_name: ファイル名。ここにリストを書き出す。
        :param bool table: Trueで表形式で出力する。
        :param bool survey: Trueで全てのマイリストの情報をまとめて出力する。
        :rtype: str
        """
        utils.check_arg({"list_id": list_id, "table": table, "survey": survey})
        if file_name:
            file_name = utils.make_dir(file_name)
        if table:  # 表形式の場合
            if list_id == utils.ALL_ITEM:
                if survey:
                    cont = self._construct_table(self.fetch_all())
                else:
                    cont = self._construct_table(self.fetch_meta())
            else:
                cont = self._construct_table(self.fetch_one(list_id))
        else:  # タブ区切りテキストの場合
            if list_id == utils.ALL_ITEM:
                if survey:
                    cont = self._construct_tsv(self.fetch_all())
                else:
                    cont = self._construct_tsv(self.fetch_meta())
            else:
                cont = self._construct_tsv(self.fetch_one(list_id))
        return self._writer(cont, file_name)
Пример #8
0
    def _download(self, video_id, xml=False):
        """
        :param str video_id: 動画ID (e.g. sm1234)
        :param bool xml:
        :rtype: bool
        """
        utils.check_arg(locals())
        db = self.glossary[video_id]
        if video_id.startswith("so"):
            redirected = self.session.get(URL.URL_Watch +
                                          video_id).url.split("/")[-1]
            db[KeyGTI.V_OR_T_ID] = redirected
        self.logger.debug(
            "Video ID: %s and its Thread ID (of officials):"
            " %s", video_id, db[KeyGTI.V_OR_T_ID])

        response = utils.get_from_getflv(db[KeyGTI.V_OR_T_ID], self.session,
                                         self.logger)

        if response is None:
            time.sleep(4)
            print(Err.waiting_for_permission)
            time.sleep(4)
            return self._download(video_id, xml)

        thread_id = response[KeyGetFlv.THREAD_ID]
        msg_server = response[KeyGetFlv.MSG_SERVER]
        user_id = response[KeyGetFlv.USER_ID]
        user_key = response[KeyGetFlv.USER_KEY]

        opt_thread_id = response[KeyGetFlv.OPT_THREAD_ID]
        needs_key = response[KeyGetFlv.NEEDS_KEY]

        if xml and video_id.startswith(("sm", "nm")):
            req_param = self.make_param_xml(thread_id, user_id)
            self.logger.debug("Posting Parameters: %s", req_param)

            res_com = self.session.post(url=msg_server, data=req_param)
            comment_data = res_com.text.replace("><", ">\n<")
        else:
            if video_id.startswith(("sm", "nm")):
                req_param = self.make_param_json(False, user_id, user_key,
                                                 thread_id)
            else:
                thread_key, force_184 = self.get_thread_key(
                    db[KeyGTI.V_OR_T_ID], needs_key)
                req_param = self.make_param_json(True, user_id, user_key,
                                                 thread_id, opt_thread_id,
                                                 thread_key, force_184)

            self.logger.debug("Posting Parameters: %s", req_param)
            res_com = self.session.post(url=URL.URL_Msg_JSON, json=req_param)
            comment_data = res_com.text.replace("}, ", "},\n")

        comment_data = comment_data.encode(res_com.encoding).decode("utf-8")
        return self._saver(video_id, comment_data, xml)
Пример #9
0
 def _download(self, video_id, is_large=True):
     """
     :param str video_id: 動画ID (e.g. sm1234)
     :param bool is_large: 大きいサムネイルを取りに行くかどうか
     :rtype: bool
     """
     utils.check_arg(locals())
     url = self.glossary[video_id][KeyGTI.THUMBNAIL_URL]
     if is_large:
         url += ".L"
     image_data = self._worker(video_id, url, is_large)
     if not image_data:
         return False
     return self._saver(video_id, image_data)
Пример #10
0
    def _construct_id(cls, container):
        """
        IDだけを出力する。

        :param list[list[str]] container: 表示したい動画IDのリスト。
        :rtype: str
        """
        utils.check_arg(locals())
        if len(container) == 0:
            return ""
        else:
            return "\n".join([
                str(item[0]) for item in container
                if item is not None and len(item) > 0
            ])
Пример #11
0
    def _construct_tsv(cls, container):
        """
        TSV形式で出力する。

        :param list[list[str]] container: 表示したい内容を含むリスト。
        :rtype: str
        """
        utils.check_arg(locals())
        if len(container) == 0:
            return ""
        else:
            first = container.pop(0)
            rows = [[str(item) for item in row] for row in container]
            rows.insert(0, first)
            return "\n".join(["\t".join(row) for row in rows])
Пример #12
0
    def _construct_id_name(cls, container):
        """
        動画IDやマイリストIDとその名前だけを出力する。

        :param list[list[str]] container: 表示したいIDの入ったリスト。
        :rtype: str
        """
        utils.check_arg(locals())
        if len(container) == 0:
            return ""
        else:
            return "\n".join([
                "{}\t{}".format(item[0], item[1]) for item in container
                if item is not None and len(item) > 0
            ])
Пример #13
0
    def get_title(self, video_id):
        """
        getthumbinfo APIから、タイトルをもらってくる

        :param str video_id: 動画ID
        :rtype:str
        """
        utils.check_arg(locals())
        document = ElementTree.fromstring(
            self.session.get(URL.URL_Info + video_id).text)
        # 「status="ok"」 なら動画は生存 / 存在しない動画には「status="fail"」が返る
        if not document.get("status").lower() == "ok":
            self.logger.error(Msg.nd_deleted_or_private, video_id)
            return ""
        else:
            return html.unescape(document[0].find("title").text)
Пример #14
0
    def get_item_ids(self, list_id, *videoids):
        """
        そのマイリストに含まれている item_id の一覧を返す。

        全て、あるいは指定した(中での生存している)動画の Item IDを返す。
        item_id は sm1234 などの動画IDとは異なるもので、
        マイリスト間の移動や複製に必要となる。

        :param int | str list_id: マイリストの名前またはID
        :param list[str] | tuple[str] videoids:
        :rtype: dict[str, str]
        """
        utils.check_arg(locals())
        list_id, _ = self._get_list_id(list_id)

        # *videoids が要素数1のタプル ("*") or
        # *videoids が要素数0のタプル(即ち未指定) -> 全体モード
        # 何かしら指定されているなら -> 個別モード
        if len(videoids) == 0 or (len(videoids) == 1
                                  and utils.ALL_ITEM in videoids):
            whole = True
        else:
            whole = False
        self.logger.debug("Is in whole mode?: %s", whole)

        if list_id == utils.DEFAULT_ID:
            jtext = json.loads(self.session.get(URL.URL_ListDef).text)
        else:
            jtext = json.loads(
                self.session.get(URL.URL_ListOne, params={
                    "group_id": list_id
                }).text)
        self.logger.debug("Response: %s", jtext)

        results = {}
        for item in jtext["mylistitem"]:
            data = item["item_data"]
            # 0以外のは削除されているか非公開
            if not whole:
                if not "0" == data["deleted"]:
                    self.logger.debug(Msg.ml_deleted_or_private, data)
                    continue

            if whole or data["video_id"] in videoids:
                results.update({data["video_id"]: item["item_id"]})
        return results
Пример #15
0
    def add(self, list_id, *videoids):
        """
        そのマイリストに、 指定した動画を追加する。

        :param int | str list_id: マイリストの名前またはID
        :param str videoids: 追加する動画ID
        :rtype: bool
        """
        utils.check_arg(locals())
        if utils.ALL_ITEM == list_id or utils.ALL_ITEM in videoids:
            raise utils.MylistError(Err.cant_perform_all)
        list_id, list_name = self._get_list_id(list_id)

        to_def = (list_id == utils.DEFAULT_ID)
        self.logger.info(Msg.ml_will_add, list_name, list(videoids))

        _done = []
        for _counter, vd_id in enumerate(videoids):
            _counter += 1
            res = self.get_response("add",
                                    to_def=to_def,
                                    list_id_to=list_id,
                                    video_id=vd_id)

            try:
                self._should_continue(res,
                                      video_id=vd_id,
                                      list_name=list_name,
                                      count_now=_counter,
                                      count_whole=len(videoids))
                self.logger.info(Msg.ml_done_add, _counter, len(videoids),
                                 vd_id)
                _done.append(vd_id)
                time.sleep(0.5)
            except MylistAPIError as error:
                if error.ok:
                    return True
                else:
                    # エラーが起きた場合
                    self.logger.error(Err.remaining, [
                        i for i in videoids
                        if i not in _done and i != utils.ALL_ITEM
                    ])
                    raise
        return True
Пример #16
0
    def _construct_table(cls, container):
        """
        Asciiテーブル形式でリストの中身を表示する。

        入力の形式は以下の通り:

        [
            ["header1", "header2", "header3"],
            ["row_1_1", "row_1_2", "row_1_3"],
            ["row_2_1", "row_2_2", "row_2_3"],
            ["row_3_1", "row_3_2", "row_3_3"]
        ]

        最後のprintで、ユニコード特有の文字はcp932のコマンドプロンプトでは表示できない。
        この対処として幾つかの方法で別の表現に置き換えることができるのだが、例えば「♥」は

        =================== ==================================================
        メソッド                 変換後
        ------------------- --------------------------------------------------
        backslashreplace    \u2665
        xmlcharrefreplace   &#9829;
        replace             ?
        =================== ==================================================

        と表示される。

        :param list[list[str]] container: 表示したい内容を含むリスト。
        :rtype: str
        """
        utils.check_arg(locals())
        if len(container) == 0:
            return ""
        elif not PrettyTable:
            raise ImportError(Err.not_installed, "PrettyTable")
        else:
            column_names = container.pop(0)
            table = PrettyTable(column_names)
            for column in column_names:
                table.align[column] = "l"
            for row in container:
                table.add_row(row)
            return table.get_string()
Пример #17
0
    def _writer(self, text, file_name=None):
        """
        ファイルまたは標準出力に書き出す。

        :param str text: 内容。
        :param str | Path | None file_name: ファイル名またはそのパス
        :rtype: str
        """
        utils.check_arg({"text": text})
        if file_name:
            file_name = utils.make_dir(file_name)
            _text = "{}\n".format(text)
            with file_name.open(mode="w", encoding="utf-8") as fd:
                fd.write(_text)
            self.logger.info(Msg.ml_exported, file_name)
        else:
            enco = utils.get_encoding()
            _text = text.encode(enco, utils.BACKSLASH).decode(enco) + "\n"
            print(_text)
        return _text
Пример #18
0
    def export(self, list_id, file_name=None, survey=False):
        """
        そのマイリストに登録された動画のIDを一覧する。

        :param int | str list_id: マイリストの名前またはID。0で「とりあえずマイリスト」。
        :param str | Path | None file_name: ファイル名。ここにリストを書き出す。
        :param bool survey: Trueで全てのマイリストの情報をまとめて出力する。
        :rtype: str
        """
        utils.check_arg({"list_id": list_id, "survey": survey})
        if file_name:
            file_name = utils.make_dir(file_name)
        if list_id == utils.ALL_ITEM:
            if survey:
                cont = self._construct_id(self.fetch_all(False))
            else:
                cont = self._construct_id_name(self.fetch_meta(False))
        else:
            cont = self._construct_id(self.fetch_one(list_id, False))
        return self._writer(cont, file_name)
Пример #19
0
    def get_thread_key(self, thread_id, needs_key):
        """
        専用のAPIにアクセスして thread_key を取得する。

        :param str thread_id:
        :param str needs_key:
        :rtype: tuple[str, str]
        """
        utils.check_arg(locals())
        if not needs_key == "1":
            self.logger.debug("Video ID (or Thread ID): %s,"
                              " needs_key: %s", thread_id, needs_key)
            return "", "0"
        response = self.session.get(URL.URL_GetThreadKey,
                                    params={"thread": thread_id})
        self.logger.debug("Response from GetThreadKey API: %s", response.text)
        parameters = parse_qs(response.text)
        threadkey = parameters["threadkey"][0]  # type: str
        force_184 = parameters["force_184"][0]  # type: str
        return threadkey, force_184
Пример #20
0
    def _saver(self, video_id, comment_data, xml):
        """

        :param str video_id:
        :param str comment_data:
        :param bool xml:
        :return:
        """
        utils.check_arg(locals())
        if xml and video_id.startswith(("sm", "nm")):
            extention = "xml"
        else:
            extention = "json"

        file_path = self.make_name(video_id, extention)
        self.logger.debug("File Path: %s", file_path)
        with file_path.open("w", encoding="utf-8") as f:
            f.write(comment_data + "\n")
        self.logger.info(Msg.nd_download_done, file_path)
        return True
Пример #21
0
    def make_param_xml(self, thread_id, user_id):
        """
        コメント取得用のxmlを構成する。

        fork="1" があると投稿者コメントを取得する。
        0-99999:9999,1000: 「0分~99999分までの範囲で
        一分間あたり9999件、直近の1000件を取得する」の意味。

        :param str thread_id:
        :param str user_id:
        :rtype: str
        """
        utils.check_arg(locals())
        self.logger.debug("Arguments: %s", locals())
        return '<packet>' \
              '<thread thread="{0}" user_id="{1}" version="20090904" scores="1"/>' \
              '<thread thread="{0}" user_id="{1}" version="20090904" scores="1"' \
              ' fork="1" res_from="-1000"/>' \
              '<thread_leaves thread="{0}" user_id="{1}" scores="1">' \
              '0-99999:9999,1000</thread_leaves>' \
              '</packet>'.format(thread_id, user_id)
Пример #22
0
    def fetch_all(self, with_info=True):
        """
        全てのマイリストに登録された動画情報を文字列にする。

        :param bool with_info:
        :rtype: list[list[str]]
        """
        utils.check_arg(locals())
        container = []
        if with_info:
            result_def = self.fetch_one(utils.DEFAULT_ID)
            container.extend(result_def)
            for l_id in self.mylists.keys():
                result = self.fetch_one(l_id, False)
                container.extend(result)
        else:
            result_def = self.fetch_one(utils.DEFAULT_ID, False)
            container.extend(result_def)
            for l_id in self.mylists.keys():
                result = self.fetch_one(l_id, False)
                container.extend([[item[0]] for item in result])
        return container
Пример #23
0
    def start(self, glossary, save_dir, xml=False):
        """

        :param dict[str, dict[str, int | str]] | list[str] glossary:
        :param str | Path save_dir:
        :param bool xml:
        """
        utils.check_arg(locals())
        self.logger.debug("Directory to save in: %s", save_dir)
        self.logger.debug("Dictionary of Videos: %s", glossary)
        self.logger.debug("Download XML? : %s", xml)
        if isinstance(glossary, list):
            glossary = get_infos(glossary, self.logger)
        self.glossary = glossary
        self.save_dir = utils.make_dir(save_dir)
        self.logger.info(Msg.nd_start_dl_comment, len(self.glossary),
                         list(self.glossary))
        for index, video_id in enumerate(self.glossary.keys()):
            self.logger.info(Msg.nd_download_comment, index + 1, len(glossary),
                             video_id, self.glossary[video_id][KeyGTI.TITLE])
            self._download(video_id, xml)
            if len(self.glossary) > 1:
                time.sleep(1.5)
        return True
Пример #24
0
    def _worker(self, video_id, url, is_large=True):
        """
        サムネイル画像をダウンロードしにいく。

        :param str video_id: 動画ID (e.g. sm1234)
        :param str url: 画像のURL
        :param bool is_large: 大きいサムネイルを取りに行くかどうか
        :rtype: bool | requests.Response
        """
        utils.check_arg(locals())
        db = self.glossary[video_id]
        with requests.Session() as session:
            try:
                # connect timeoutを5秒, read timeoutを10秒に設定
                response = session.get(url=url, timeout=(5.0, 10.0))
                if response.ok:
                    return response
                # 大きいサムネイルを求めて404が返ってきたら標準の大きさで試す
                if response.status_code == 404:
                    if is_large and url.endswith(".L"):
                        return self._worker(video_id, url[:-2], is_large=False)
                    else:
                        self.logger.error(Err.connection_404, video_id,
                                          db[KeyGTI.TITLE])
                        return False
            except (TypeError, ConnectionError, socket.timeout, Timeout,
                    urllib3.exceptions.TimeoutError,
                    urllib3.exceptions.RequestError) as e:
                self.logger.debug("An exception occurred: %s", e)
                if is_large and url.endswith(".L"):
                    return self._worker(video_id, url[:-2], is_large=False)
                else:
                    self.logger.error(
                        Err.connection_timeout.format(video_id) +
                        " (タイトル: {})".format(db[KeyGTI.TITLE]))
                    return False
Пример #25
0
    def start(self, glossary, save_dir, is_large=True):
        """

        :param dict[str, dict[str, int | str]] | list[str] glossary:
        :param str | Path save_dir:
        :param bool is_large: 大きいサムネイルを取りに行くかどうか
        :rtype: bool
        """
        utils.check_arg(locals())
        self.logger.debug("Directory to save in: %s", save_dir)
        self.logger.debug("Dictionary of Videos: %s", glossary)
        if isinstance(glossary, list):
            glossary = get_infos(glossary, self.logger)
        self.glossary = glossary
        self.save_dir = utils.make_dir(save_dir)

        self.logger.info(Msg.nd_start_dl_pict, len(self.glossary),
                         list(self.glossary))

        for index, video_id in enumerate(self.glossary.keys()):
            self.logger.info(Msg.nd_download_pict, index + 1, len(glossary),
                             video_id, self.glossary[video_id][KeyGTI.TITLE])
            self._download(video_id, is_large)
        return True
Пример #26
0
    def make_param_json(self,
                        official_video,
                        user_id,
                        user_key,
                        thread_id,
                        optional_thread_id=None,
                        thread_key=None,
                        force_184=None):
        """
        コメント取得用のjsonを構成する。

        fork="1" があると投稿者コメントを取得する。
        0-99999:9999,1000: 「0分~99999分までの範囲で
        一分間あたり9999件、直近の1000件を取得する」の意味。

        :param bool official_video: 公式動画なら True
        :param str user_id:
        :param str user_key:
        :param str thread_id:
        :param str | None optional_thread_id:
        :param str | None thread_key:
        :param str | None force_184:
        :rtype: list[dict]
        """
        utils.check_arg({
            "official_video": official_video,
            "user_id": user_id,
            "user_key": user_key,
            "thread_id": thread_id
        })
        self.logger.debug("Arguments of creating JSON: %s", locals())
        result = [
            {
                "ping": {
                    "content": "rs:0"
                }
            },
            {
                "ping": {
                    "content": "ps:0"
                }
            },
            {
                "thread": {
                    "thread": optional_thread_id or thread_id,
                    "version": "20090904",
                    "language": 0,
                    "user_id": user_id,
                    "with_global": 1,
                    "scores": 1,
                    "nicoru": 0,
                    "userkey": user_key
                }
            },
            {
                "ping": {
                    "content": "pf:0"
                }
            },
            {
                "ping": {
                    "content": "ps:1"
                }
            },
            {
                "thread_leaves": {
                    "thread": optional_thread_id or thread_id,
                    "language": 0,
                    "user_id": user_id,
                    # "content" : "0-4:100,250",  # 公式仕様のデフォルト値
                    "content": "0-99999:9999,1000",
                    "scores": 1,
                    "nicoru": 0,
                    "userkey": user_key
                }
            },
            {
                "ping": {
                    "content": "pf:1"
                }
            }
        ]

        if official_video:
            result += [
                {
                    "ping": {
                        "content": "ps:2"
                    }
                },
                {
                    "thread": {
                        "thread": thread_id,
                        "version": "20090904",
                        "language": 0,
                        "user_id": user_id,
                        "force_184": force_184,
                        "with_global": 1,
                        "scores": 1,
                        "nicoru": 0,
                        "threadkey": thread_key
                    }
                },
                {
                    "ping": {
                        "content": "pf:2"
                    }
                },
                {
                    "ping": {
                        "content": "ps:3"
                    }
                },
                {
                    "thread_leaves": {
                        "thread": thread_id,
                        "language": 0,
                        "user_id": user_id,
                        # "content"  : "0-4:100,250",  # 公式仕様のデフォルト値
                        "content": "0-99999:9999,1000",
                        "scores": 1,
                        "nicoru": 0,
                        "force_184": force_184,
                        "threadkey": thread_key
                    }
                },
                {
                    "ping": {
                        "content": "pf:3"
                    }
                }
            ]
        result += [{"ping": {"content": "rf:0"}}]
        return result
Пример #27
0
    def get_response(self, mode, **kwargs):
        """
        マイリストAPIにアクセスして結果を受け取る。

        * bool is_def:
            (add, copy, move, delete) 「とりあえずマイリスト」が対象であれば True
        * bool is_public:
            (add) 公開マイリストであれば True
        * int  list_id:
            (purge) マイリストのID
        * int  list_id_to:
            (add, copy, move) マイリストのID
        * int  list_id_from:
            (copy, move, delete) マイリストのID
        * str  video_id:
            (add, copy, move, delete) 動画ID
        * str  item_id:
            (add, copy, move, delete) 動画の item ID
        * str  mylist_name:
            (create) マイリストの名前
        * str  description:
            (add, create) 動画またはマイリストの説明文
        * int default_sort:
            (create) 並び順
        * int icon_id:
            (create) マイリストのアイコンを表す数字

        :param str mode: "add", "copy", "move", "delete", "purge", "create" のいずれか
        :rtype: dict
        """
        utils.check_arg(locals())
        assert mode.lower() in ("add", "delete", "copy", "move", "purge",
                                "create")

        self.logger.debug("Query components: %s", kwargs)
        to_def = kwargs.get("to_def")  # type: bool
        from_def = kwargs.get("from_def")  # type: bool
        is_public = kwargs.get("is_public")  # type: bool
        list_id = kwargs.get("list_id")  # type: int
        list_id_to = kwargs.get("list_id_to")  # type: int
        list_id_from = kwargs.get("list_id_from")  # type: int
        video_id = kwargs.get("video_id")  # type: str
        item_id = kwargs.get("item_id")  # type: str
        mylist_name = kwargs.get("mylist_name")  # type: str
        description = kwargs.get("description", "")  # type: str
        default_sort = kwargs.get("default_sort", 0)  # type: int
        icon_id = kwargs.get("icon_id", 0)  # type: int

        if video_id and not isinstance(video_id, list):
            video_id = [video_id]
        if item_id and not isinstance(item_id, list):
            item_id = [item_id]

        if "move" == mode and to_def:
            # とりあえずマイリストには直接移動できないので、追加と削除を別でやる。
            self.get_response("add",
                              to_def=True,
                              video_id=video_id,
                              description=description)
            return self.get_response("delete", from_def=True, item_id=item_id)

        if "add" == mode or ("copy" == mode and to_def):
            payload = {
                "item_type": 0,
                "item_id": video_id,
                "description": description,
                "token": self.token
            }
            if to_def:
                url = URL.URL_AddDef
            else:
                payload.update({"group_id": str(list_id_to)})
                url = URL.URL_AddItem
        elif "delete" == mode:
            payload = {"id_list[0][]": item_id, "token": self.token}
            if from_def:
                url = URL.URL_DeleteDef
            else:
                payload.update({"group_id": str(list_id_from)})
                url = URL.URL_DeleteItem
        elif "copy" == mode:
            payload = {
                "target_group_id": str(list_id_to),
                "id_list[0][]": item_id,
                "token": self.token
            }
            if from_def:
                url = URL.URL_CopyDef
            else:
                payload.update({"group_id": str(list_id_from)})
                url = URL.URL_CopyItem
        elif "move" == mode:
            payload = {
                "target_group_id": str(list_id_to),
                "id_list[0][]": item_id,
                "token": self.token
            }
            if from_def:
                url = URL.URL_MoveDef
            else:
                payload.update({"group_id": str(list_id_from)})
                url = URL.URL_MoveItem
        elif "purge" == mode:
            payload = {"group_id": str(list_id), "token": self.token}
            url = URL.URL_PurgeList
        else:  # create
            payload = {
                "name": mylist_name,
                "description": description,
                "public": int(is_public),
                "default_sort": default_sort,
                "icon_id": icon_id,
                "token": self.token
            }
            url = URL.URL_AddMyList
        self.logger.debug("URL: %s", url)
        self.logger.debug("Query to post: %s", payload)
        res = self.session.get(url, params=payload).text
        self.logger.debug("Response: %s", res)
        return json.loads(res)
Пример #28
0
    def move(self, list_id_from, list_id_to, *videoids):
        """
        そのマイリストに、 指定した動画を移動する。

        :param int | str list_id_from: 移動元のIDまたは名前
        :param int | str list_id_to: 移動先のIDまたは名前
        :param str videoids: 動画ID
        :rtype: bool
        """
        utils.check_arg(locals())
        if len(videoids) > 1 and utils.ALL_ITEM in videoids:
            raise utils.MylistError(Err.videoids_contain_all)
        list_id_from, list_name_from = self._get_list_id(list_id_from)
        list_id_to, list_name_to = self._get_list_id(list_id_to)

        to_def = (list_id_to == utils.DEFAULT_ID)
        from_def = (list_id_from == utils.DEFAULT_ID)

        item_ids = self.get_item_ids(list_id_from, *videoids)
        if len(item_ids) == 0:
            self.logger.error(Err.no_items)
            return False
        if utils.ALL_ITEM not in videoids:
            item_ids = {
                vd_id: item_ids[vd_id]
                for vd_id in videoids if vd_id in item_ids
            }

            # 指定したものが含まれているかの確認
            excluded = [vd_id for vd_id in videoids if vd_id not in item_ids]
            if len(excluded) > 0:
                self.logger.error(Err.item_not_contained, list_name_from,
                                  excluded)

        self.logger.info(Msg.ml_will_move, list_name_from, list_name_to,
                         sorted(item_ids.keys()))

        _done = []
        for _counter, vd_id in enumerate(item_ids):
            _counter += 1
            if to_def:
                # とりあえずマイリストには直接移動できないので、追加と削除を別でやる。
                res = self.get_response("add",
                                        to_def=True,
                                        video_id=vd_id,
                                        item_id=item_ids[vd_id])
                try:
                    self._should_continue(res,
                                          video_id=vd_id,
                                          list_name=list_name_to,
                                          count_now=_counter,
                                          count_whole=len(item_ids))
                except MylistAPIError as error:
                    if error.ok:
                        return True
                    else:
                        # エラーが起きた場合
                        self.logger.error(Err.remaining, [
                            i for i in videoids
                            if i not in _done and i != utils.ALL_ITEM
                        ])
                        raise
                res = self.get_response("delete",
                                        from_def=True,
                                        video_id=vd_id,
                                        item_id=item_ids[vd_id])
            else:
                res = self.get_response("move",
                                        item_id=item_ids[vd_id],
                                        from_def=from_def,
                                        list_id_to=list_id_to,
                                        list_id_from=list_id_from)

            try:
                self._should_continue(res,
                                      video_id=vd_id,
                                      list_name=list_name_to,
                                      count_now=_counter,
                                      count_whole=len(item_ids))
                self.logger.info(Msg.ml_done_move, _counter, len(item_ids),
                                 vd_id)
                _done.append(vd_id)
            except MylistAPIError as error:
                if error.ok:
                    return True
                else:
                    # エラーが起きた場合
                    self.logger.error(Err.remaining, [
                        i for i in videoids
                        if i not in _done and i != utils.ALL_ITEM
                    ])
                    raise
        return True
Пример #29
0
    def delete(self, list_id, *videoids, confident=False):
        """
        そのマイリストから、指定した動画を削除する。

        :param int | str list_id: 移動元のIDまたは名前
        :param str videoids: 動画ID
        :param bool confident:
        :rtype: bool
        """
        utils.check_arg(locals())
        if len(videoids) > 1 and utils.ALL_ITEM in videoids:
            raise utils.MylistError(Err.videoids_contain_all)
        list_id, list_name = self._get_list_id(list_id)

        from_def = (list_id == utils.DEFAULT_ID)

        item_ids = self.get_item_ids(list_id, *videoids)
        if len(item_ids) == 0:
            self.logger.error(Err.no_items)
            return False

        if len(videoids) == 1 and utils.ALL_ITEM in videoids:
            # 全体モード
            if not confident and not self._confirmation(
                    "delete", list_name, sorted(item_ids.keys())):
                print(Msg.ml_answer_no)
                return False
            self.logger.info(Msg.ml_will_delete, list_name,
                             sorted(item_ids.keys()))
        else:
            # 個別モード
            self.logger.info(Msg.ml_will_delete, list_name, list(videoids))
            item_ids = {
                vd_id: item_ids[vd_id]
                for vd_id in videoids if vd_id in item_ids
            }

            # 指定したIDが含まれているかの確認
            excluded = [vd_id for vd_id in videoids if vd_id not in item_ids]
            if len(excluded) > 0:
                self.logger.error(Err.item_not_contained, list_name, excluded)

        _done = []
        for _counter, vd_id in enumerate(item_ids):
            _counter += 1
            res = self.get_response("delete",
                                    from_def=from_def,
                                    list_id_from=list_id,
                                    item_id=item_ids[vd_id])

            try:
                self._should_continue(res,
                                      video_id=vd_id,
                                      list_name=list_name,
                                      count_now=_counter,
                                      count_whole=len(item_ids))
                self.logger.info(Msg.ml_done_delete, _counter, len(item_ids),
                                 vd_id)
                _done.append(vd_id)
            except MylistAPIError as error:
                if error.ok:
                    return True
                else:
                    # エラーが起きた場合
                    self.logger.error(Err.remaining, [
                        i for i in videoids
                        if i not in _done and i != utils.ALL_ITEM
                    ])
                    raise
        return True
Пример #30
0
    def fetch_one(self, list_id, with_header=True):
        """
        単一のマイリストに登録された動画情報を文字列にする。

        deleted について:
            * 1 = 投稿者による削除
            * 2 = 運営による削除
            * 3 = 権利者による削除
            * 8 = 投稿者による非公開

        :param int | str list_id: マイリストの名前またはID。
        :param bool with_header:
        :rtype: list[list[str]]
        """
        utils.check_arg(locals())
        list_id, list_name = self._get_list_id(list_id)

        self.logger.info(Msg.ml_showing_mylist, list_name)
        if list_id == utils.DEFAULT_ID:
            jtext = json.loads(self.session.get(URL.URL_ListDef).text)
        else:
            jtext = json.loads(
                self.session.get(URL.URL_ListOne, params={
                    "group_id": list_id
                }).text)
        self.logger.debug("Returned: %s", jtext)

        if with_header:
            container = [[
                "動画 ID",
                "タイトル",
                "投稿日",
                "再生数",
                "コメント数",
                "マイリスト数",
                "長さ",
                "状態",
                "メモ",
                "所屬",
                # "最近のコメント",
            ]]
        else:
            container = []

        for item in jtext["mylistitem"]:
            data = item[MKey.ITEM_DATA]
            desc = html.unescape(item[MKey.DESCRIPTION])
            duration = int(data[KeyGTI.LENGTH_SECONDS])
            container.append([
                data[KeyGTI.VIDEO_ID],
                html.unescape(data[KeyGTI.TITLE]).replace(r"\/", "/"),
                self._get_jst_from_utime(data[KeyGTI.FIRST_RETRIEVE]),
                data[KeyGTI.VIEW_COUNTER],
                data[KeyGTI.NUM_RES],
                data[KeyGTI.MYLIST_COUNTER],
                "{}:{}".format(duration // 60, duration % 60),
                self.WHY_DELETED.get(data[KeyGTI.DELETED], "不明"),
                desc.strip().replace("\r",
                                     "").replace("\n",
                                                 " ").replace(r"\/", "/"),
                list_name,
                # data[KeyGTI.LAST_RES_BODY],
            ])
        self.logger.debug("Mylists info: %s", container)
        return container