From 5f388eee9357a67250d23b082a66f247bca2a332 Mon Sep 17 00:00:00 2001 From: amatuerCoder <17560146+bluerthanever@users.noreply.github.com> Date: Mon, 27 Apr 2020 18:02:05 +0800 Subject: [PATCH 01/22] Modification and implementation for downloading FANBOX post 1. Imported `FanboxPost` 2. Moved codes to obtain post jsons to a single method `fanboxGetPost` 3. If `member_id` is passed when calling `fanboxGetPost`, json object is returned 4. If no `member_id` is passed when calling `fanboxGetPost`, member_id and name is retrieved from post json and a new `FanboxArtist` instance would be created via`object().__new__(FanboxArtist)`), and a new `FanboxPost` instance would be created and returned. --- PixivBrowserFactory.py | 66 ++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/PixivBrowserFactory.py b/PixivBrowserFactory.py index 751e9706..629ad63e 100644 --- a/PixivBrowserFactory.py +++ b/PixivBrowserFactory.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # pylint: disable=W0603, C0325 import http.client @@ -20,7 +20,7 @@ from PixivArtist import PixivArtist from PixivException import PixivException from PixivImage import PixivImage -from PixivModelFanbox import Fanbox, FanboxArtist +from PixivModelFanbox import Fanbox, FanboxArtist, FanboxPost from PixivOAuth import PixivOAuth from PixivTags import PixivTags @@ -777,26 +777,56 @@ def fanboxGetPostsFromArtist(self, artist_id, next_url=""): result.artistToken = pixivArtist.artistToken for post in result.posts: - # https://fanbox.pixiv.net/api/post.info?postId=279561 - # https://www.pixiv.net/fanbox/creator/104409/post/279561 - p_url = f"https://fanbox.pixiv.net/api/post.info?postId={post.imageId}" - p_referer = f"https://www.pixiv.net/fanbox/creator/{artist_id}/post/{post.imageId}" - PixivHelper.get_logger().debug('Getting post detail from %s', p_url) - p_req = mechanize.Request(p_url) - p_req.add_header('Accept', 'application/json, text/plain, */*') - p_req.add_header('Referer', p_referer) - p_req.add_header('Origin', 'https://www.pixiv.net') - p_req.add_header('User-Agent', self._config.useragent) - - p_res = self.open_with_retry(p_req) - p_response = p_res.read() - PixivHelper.get_logger().debug(p_response.decode('utf8')) - p_res.close() - js = demjson.decode(p_response) + js = self.fanboxGetPost(post.imageId, artist_id) + + + + + + + + + + + + + + + post.parsePost(js["body"]) return result + def fanboxGetPost(self, post_id, member_id=0): + # https://fanbox.pixiv.net/api/post.info?postId=279561 + # https://www.pixiv.net/fanbox/creator/104409/post/279561 + p_url = f"https://fanbox.pixiv.net/api/post.info?postId={post_id}" + # referer doesn't seeem to be essential + p_referer = f"https://www.pixiv.net/fanbox/creator/{member_id}/post/{post_id}" + PixivHelper.get_logger().debug('Getting post detail from %s', p_url) + p_req = mechanize.Request(p_url) + p_req.add_header('Accept', 'application/json, text/plain, */*') + p_req.add_header('Referer', p_referer) + p_req.add_header('Origin', 'https://www.pixiv.net') + p_req.add_header('User-Agent', self._config.useragent) + + p_res = self.open_with_retry(p_req) + p_response = p_res.read() + PixivHelper.get_logger().debug(p_response.decode('utf8')) + p_res.close() + js = demjson.decode(p_response) + if member_id: + return js + else: + _tzInfo = None + if self._config.useLocalTimezone: + _tzInfo = PixivHelper.LocalUTCOffsetTimezone() + user = object().__new__(FanboxArtist) + user.artistId = js["body"]["user"]["userId"] + user.name = js["body"]["user"]["name"] + post = FanboxPost(post_id, user, js["body"], _tzInfo) + return post + def getBrowser(config=None, cookieJar=None): global defaultCookieJar From 90d64ce2519de4563b0434184d4e712478eb1195 Mon Sep 17 00:00:00 2001 From: amatuerCoder <17560146+bluerthanever@users.noreply.github.com> Date: Mon, 27 Apr 2020 18:09:40 +0800 Subject: [PATCH 02/22] Fanbox post records enhancement 1. New table `fanbox_master_post` 2. Logic to create/drop table 3. Method to export fanbox post infomation to csv files, `exportFanboxPostList` 4. Methods to insert, update, select, delete post infos, CRUD? 5. Entries in `menu` and `main` --- PixivDBManager.py | 131 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 2 deletions(-) diff --git a/PixivDBManager.py b/PixivDBManager.py index 9b8ce0b5..99b8a078 100644 --- a/PixivDBManager.py +++ b/PixivDBManager.py @@ -85,7 +85,20 @@ def createDatabase(self): PRIMARY KEY (image_id, page) )''') self.conn.commit() - + + # just to keep track of posts, not to record the details, so no columns like saved_to or caption or whatsoever + c.execute('''CREATE TABLE IF NOT EXISTS fanbox_master_post ( + member_id INTEGER, + post_id INTEGER PRIMARY KEY ON CONFLICT IGNORE, + title TEXT, + fee_required INTEGER, + published_date DATE, + updated_date DATE, + post_type TEXT, + last_update_date DATE + )''') + self.conn.commit() + print('done.') except BaseException: print('Error at createDatabase():', str(sys.exc_info())) @@ -102,6 +115,13 @@ def dropDatabase(self): c.execute('''DROP TABLE IF EXISTS pixiv_master_image''') self.conn.commit() + + c.execute('''DROP TABLE IF EXISTS pixiv_manga_image''') + self.conn.commit() + + c.execute('''DROP TABLE IF EXISTS fanbox_master_post''') + self.conn.commit() + except BaseException: print('Error at dropDatabase():', str(sys.exc_info())) print('failed.') @@ -211,6 +231,33 @@ def exportDetailedList(self, filename): c.close() print('done.') + def exportFanboxPostList(self, filename): + print('Exporting FANBOX post list...', end=' ') + try: + c = self.conn.cursor() + c.execute('''SELECT * FROM fanbox_master_post + ORDER BY member_id, post_id''') + filename = filename + '.csv' + writer = codecs.open(filename, 'wb', encoding='utf-8') + writer.write( + 'member_id,post_id,title,fee_required,published_date,update_date,post_type,last_update_date\r\n') + for row in c: + for string in row: + # Unicode write!! + data = str(string) + writer.write(data) + writer.write(',') + writer.write('\r\n') + writer.write('###END-OF-FILE###') + writer.close() + except BaseException: + print('Error at exportFanboxPostList(): ' + str(sys.exc_info())) + print('failed') + raise + finally: + c.close() + print('done.') + ########################################## # III. Print DB # ########################################## @@ -519,6 +566,73 @@ def insertMangaImages(self, manga_files): finally: c.close() +def insertPost(self, member_id, post_id, title, fee_required, published_date, post_type): + try: + c = self.conn.cursor() + post_id = int(post_id) + c.execute( + '''INSERT OR IGNORE INTO fanbox_master_post (member_id, post_id, last_update_date) + VALUES(?, ?, datetime('now'))''', (member_id, post_id)) + c.execute( + '''UPDATE fanbox_master_post SET title = ?, fee_required = ?, published_date = ?, post_type = ? + WHERE post_id = ?''', + (title, fee_required, published_date, post_type, post_id)) + self.conn.commit() + except BaseException: + print('Error at insertPost():', str(sys.exc_info())) + print('failed') + raise + finally: + c.close() + + def selectPostByPostId(self, post_id): + try: + c = self.conn.cursor() + post_id = int(post_id) + c.execute( + '''SELECT * FROM fanbox_master_post WHERE post_id = ?''', + (post_id,)) + return c.fetchone() + except BaseException: + print('Error at selectPostByPostId():', str(sys.exc_info())) + print('failed') + raise + finally: + c.close() + + def updatePostLastUpdateDate(self, post_id, updated_date): + try: + c = self.conn.cursor() + post_id = int(post_id) + c.execute( + '''UPDATE fanbox_master_post SET updated_date = ? + WHERE post_id = ?''', + (updated_date, post_id)) + self.conn.commit() + except BaseException: + print('Error at updatePostLastUpdateDate():', str(sys.exc_info())) + print('failed') + raise + finally: + c.close() + + def deleteFanboxPost(self, id, by): + id = int(id) + if by not in ["member_id", "post_id"]: + return + sql = f'''DELETE FROM fanbox_master_post WHERE {by} = ?''' + + try: + c = self.conn.cursor() + c.execute(sql, (id,)) + self.conn.commit() + except BaseException: + print('Error at deleteFanboxPost():', str(sys.exc_info())) + print('failed') + raise + finally: + c.close() + def blacklistImage(self, memberId, ImageId): try: c = self.conn.cursor() @@ -775,12 +889,16 @@ def menu(self): print('12. Blacklist image by image_id') print('13. Show all deleted member') print('===============================================') + print('f1. Export FANBOX post list') + print('f2. Delete FANBOX download history by post_id') + print('f3. Delete FANBOX download history by member_id') + print('===============================================') print('c. Clean Up Database') print('i. Interactive Clean Up Database') print('p. Compact Database') print('r. Replace Root Path') print('x. Exit') - selection = input('Select one?').rstrip("\r") + selection = input('Select one? ').rstrip("\r") return selection def main(self): @@ -859,6 +977,15 @@ def main(self): self.blacklistImage(member_id, image_id) elif selection == '13': self.printMemberList(isDeleted=True) + elif selection == 'f1': + filename = input('Filename? ').rstrip("\r") + self.exportFanboxPostList(filename) + elif selection == 'f2': + member_id = input('member_id? ').rstrip("\r") + self.deleteFanboxPost(member_id, "post_id") + elif selection == 'f3': + post_id = input('post_id? ').rstrip("\r") + self.deleteFanboxPost(post_id, "member_id") elif selection == 'c': self.cleanUp() elif selection == 'i': From 9e33815d9307953951dd76ee7544d9b299812869 Mon Sep 17 00:00:00 2001 From: amatuerCoder <17560146+bluerthanever@users.noreply.github.com> Date: Mon, 27 Apr 2020 18:10:54 +0800 Subject: [PATCH 03/22] Some properties and methods changes 1. Added `updatedDate`, `updatedDateDatetime`, `feeRequired` properties and logic to set them in `parsePost` and `parseBody` 2. Deleted the old `updatedDatetime` that was not used anywhere 3. Extracted some codes to print fanbox post infomation to a new method `printPost` --- PixivModelFanbox.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/PixivModelFanbox.py b/PixivModelFanbox.py index 1bfd8a9c..fa5b222a 100644 --- a/PixivModelFanbox.py +++ b/PixivModelFanbox.py @@ -87,7 +87,8 @@ class FanboxPost(object): coverImageUrl = "" worksDate = "" worksDateDateTime = None - updatedDatetime = "" + updatedDate = "" + updatedDateDatetime = None # image|text|file|article|video|entry _supportedType = ["image", "text", "file", "article", "video", "entry"] type = "" @@ -96,14 +97,14 @@ class FanboxPost(object): likeCount = 0 parent = None is_restricted = False - + feeRequired = 0 # compatibility imageMode = "" imageCount = 0 _tzInfo = None linkToFile = None - + # not implemented worksResolution = "" worksTools = "" @@ -121,9 +122,9 @@ def __init__(self, post_id, parent, page, tzInfo=None): self.imageId = int(post_id) self.parent = parent self._tzInfo = tzInfo - + self.linkToFile = dict() - + self.parsePost(page) if not self.is_restricted: @@ -148,12 +149,14 @@ def parsePost(self, jsPost): self.worksDate = jsPost["publishedDatetime"] self.worksDateDateTime = datetime_z.parse_datetime(self.worksDate) + self.updatedDate = jsPost["updatedDatetime"] + self.updatedDateDatetime = datetime_z.parse_datetime(self.updatedDate) # Issue #420 if self._tzInfo is not None: self.worksDateDateTime = self.worksDateDateTime.astimezone( self._tzInfo) - self.updatedDatetime = jsPost["updatedDatetime"] + self.type = jsPost["type"] if self.type not in FanboxPost._supportedType: raise PixivException("Unsupported post type = {0} for post = {1}".format( @@ -246,6 +249,9 @@ def parseBody(self, jsPost): self.body_text, self.getEmbedData(jsPost["body"]["video"], jsPost)) + if "feeRequired" in jsPost: + self.feeRequired = jsPost["feeRequired"] + def getEmbedData(self, embedData, jsPost): if not os.path.exists("content_provider.json"): raise PixivException("Missing content_provider.json, please redownload application!", @@ -296,6 +302,13 @@ def try_add(self, item, list_data): if item not in list_data: list_data.append(item) + def printPost(self): + print("Post = {0}".format(self.imageId)) + print("Title = {0}".format(self.imageTitle)) + print("Type = {0}".format(self.type)) + print("Created Date = {0}".format(self.worksDate)) + print("Is Restricted = {0}".format(self.is_restricted)) + def WriteInfo(self, filename): info = None try: From 0ad0dbacc8417c43e747f8b1c9d842c900daf703 Mon Sep 17 00:00:00 2001 From: amatuerCoder <17560146+bluerthanever@users.noreply.github.com> Date: Mon, 27 Apr 2020 18:17:59 +0800 Subject: [PATCH 04/22] Fanbox post download enhancement 1. Extracted some codes into `FanboxPost.printPost` 2. Moved codes to download FANBOX cover images from `processFanboxArtist` to `processFanboxImages`, because I don't think cover images should be downloaded if the post is not accessible 3. Post infomation (mainly just artist_id, post_id, title, feeRequired, workDate, updatedDate, type) would be written into database first thing after 'processFanboxImages' is called inside it, before retrieving post data row from database for comparison between the newly retrieved post updated date and that written in the database. Post would not be processed if it is restricted or download history exists. 4. Then after everything is down, images downloaded and image info written to file, update the `updatedDate` in database 5. Added entry for download fanbox post by post ids, and some related logic in option parser thiingy --- PixivUtil2.py | 107 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 69 insertions(+), 38 deletions(-) diff --git a/PixivUtil2.py b/PixivUtil2.py index 464d49f5..b6dc68ff 100755 --- a/PixivUtil2.py +++ b/PixivUtil2.py @@ -1515,6 +1515,7 @@ def menu(): print('------------------------') print('f1. Download from supported artists (FANBOX)') print('f2. Download by artist id (FANBOX)') + print('f3. Download by post id (FANBOX)') print('------------------------') print('d. Manage database') print('e. Export online bookmark') @@ -1937,6 +1938,24 @@ def menu_fanbox_download_supported_artist(op_is_valid, args): PixivHelper.print_and_log("error", "Error processing FANBOX Artist: {0} ==> {1}".format(artist_id, pex.message)) +def menu_fanbox_download_by_post_id(op_is_valid, args): + __log__.info('Download FANBOX by post id mode.') + if op_is_valid and len(args) > 0: + post_ids = args + else: + post_ids = input("Post ids = ").rstrip("\r") or 0 + + post_ids = PixivHelper.get_ids_from_csv(post_ids, sep=" ") + for post_id in post_ids: + post_id = int(post_id) + post = __br__.fanboxGetPost(post_id) + try: + processFanboxImages(post, post.parent) + except PixivException as pex: + PixivHelper.print_and_log("error", "Error processing FANBOX post: {0} ==> {1}".format(post_id, pex.message)) + del post + + def processFanboxArtist(artist_id, end_page): current_page = 1 next_url = None @@ -1951,43 +1970,8 @@ def processFanboxArtist(artist_id, end_page): for post in result_artist.posts: print("#{0}".format(image_count)) - print("Post = {0}".format(post.imageId)) - print("Title = {0}".format(post.imageTitle)) - print("Type = {0}".format(post.type)) - print("Created Date = {0}".format(post.worksDate)) - print("Is Restricted = {0}".format(post.is_restricted)) - # cover image - if post.coverImageUrl is not None: - # fake the image_url for filename compatibility, add post id and pagenum - fake_image_url = post.coverImageUrl.replace("{0}/cover/".format(post.imageId), "{0}_".format(post.imageId)) - filename = PixivHelper.make_filename(__config__.filenameFormat, - post, - artistInfo=result_artist, - tagsSeparator=__config__.tagsSeparator, - tagsLimit=__config__.tagsLimit, - fileUrl=fake_image_url, - bookmark=None, - searchTags='') - filename = PixivHelper.sanitize_filename(filename, __config__.rootDirectory) - - post.linkToFile[post.coverImageUrl] = filename - - print("Downloading cover from {0}".format(post.coverImageUrl)) - print("Saved to {0}".format(filename)) - - referer = "https://www.pixiv.net/fanbox/creator/{0}/post/{1}".format(artist_id, post.imageId) - # don't pass the post id and page number to skip db check - (result, filename) = download_image(post.coverImageUrl, - filename, - referer, - __config__.overwrite, - __config__.retry, - __config__.backupOldFile) - PixivHelper.get_logger().debug("Download %s result: %s", filename, result) - - else: - PixivHelper.print_and_log("info", "No Cover Image for post: {0}.".format(post.imageId)) - + post.printPost() + # images if post.type in PixivModelFanbox.FanboxPost._supportedType: processFanboxImages(post, result_artist) @@ -2007,9 +1991,51 @@ def processFanboxArtist(artist_id, end_page): def processFanboxImages(post, result_artist): + __dbManager__.insertPost(result_artist.artistId, post.imageId, post.imageTitle, + post.feeRequired, post.worksDate, post.type) if post.is_restricted: PixivHelper.print_and_log("info", "Skipping post: {0} due to restricted post.".format(post.imageId)) return + + result = __dbManager__.selectPostByPostId(post.imageId) + if result: + updated_date = result[5] + if updated_date is not None and post.updatedDateDatetime <= datetime_z.parse_datetime(updated_date): + PixivHelper.print_and_log("info", "Skipping post: {0} bacause it was downloaded before.".format(post.imageId)) + return + + # cover image + if post.coverImageUrl is not None: + # fake the image_url for filename compatibility, add post id and pagenum + fake_image_url = post.coverImageUrl.replace("{0}/cover/".format(post.imageId), + "{0}_".format(post.imageId)) + filename = PixivHelper.make_filename(__config__.filenameFormat, + post, + artistInfo=result_artist, + tagsSeparator=__config__.tagsSeparator, + tagsLimit=__config__.tagsLimit, + fileUrl=fake_image_url, + bookmark=None, + searchTags='') + filename = PixivHelper.sanitize_filename(filename, __config__.rootDirectory) + + post.linkToFile[post.coverImageUrl] = filename + + print("Downloading cover from {0}".format(post.coverImageUrl)) + print("Saved to {0}".format(filename)) + + referer = "https://www.pixiv.net/fanbox/creator/{0}/post/{1}".format(result_artist.artistId, post.imageId) + # don't pass the post id and page number to skip db check + (result, filename) = download_image(post.coverImageUrl, + filename, + referer, + __config__.overwrite, + __config__.retry, + __config__.backupOldFile) + PixivHelper.get_logger().debug("Download %s result: %s", filename, result) + else: + PixivHelper.print_and_log("info", "No Cover Image for post: {0}.".format(post.imageId)) + if post.images is None or len(post.images) == 0: PixivHelper.print_and_log("info", "No Image available in post: {0}.".format(post.imageId)) # return @@ -2073,6 +2099,8 @@ def processFanboxImages(post, result_artist): reader.close() post.WriteHtml(html_template, __config__.useAbsolutePathsInHtml, filename + ".html") + __dbManager__.updatePostLastUpdateDate(post.imageId, post.updatedDate) + def menu_fanbox_download_by_artist_id(op_is_valid, args): __log__.info('Download FANBOX by Artist ID mode.') @@ -2109,7 +2137,7 @@ def set_console_title(title=''): def setup_option_parser(): global __valid_options - __valid_options = ('1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', 'f1', 'f2', 'd', 'e', 'm') + __valid_options = ('1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', 'f1', 'f2', 'f3', 'd', 'e', 'm') parser = OptionParser() parser.add_option('-s', '--startaction', dest='startaction', help='''Action you want to load your program with: @@ -2127,6 +2155,7 @@ def setup_option_parser(): 12 - Download images by Group Id f1 - Download from supported artists (FANBOX) f2 - Download by artist id (FANBOX) +f3 - Download by post id (FANBOX) e - Export online bookmark m - Export online user bookmark d - Manage database''') @@ -2207,6 +2236,8 @@ def main_loop(ewd, op_is_valid, selection, np_is_valid_local, args): menu_fanbox_download_supported_artist(op_is_valid, args) elif selection == 'f2': menu_fanbox_download_by_artist_id(op_is_valid, args) + elif selection == 'f3': + menu_fanbox_download_by_post_id(op_is_valid, args) # END PIXIV FANBOX elif selection == '-all': if not np_is_valid_local: From cc784ad15aef4db9a63cc66944030b69f158dcd7 Mon Sep 17 00:00:00 2001 From: amatuerCoder <17560146+bluerthanever@users.noreply.github.com> Date: Mon, 27 Apr 2020 21:20:55 +0800 Subject: [PATCH 05/22] Fixed some indent errors --- PixivDBManager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PixivDBManager.py b/PixivDBManager.py index 99b8a078..d050ab23 100644 --- a/PixivDBManager.py +++ b/PixivDBManager.py @@ -231,7 +231,7 @@ def exportDetailedList(self, filename): c.close() print('done.') - def exportFanboxPostList(self, filename): + def exportFanboxPostList(self, filename): print('Exporting FANBOX post list...', end=' ') try: c = self.conn.cursor() @@ -566,7 +566,7 @@ def insertMangaImages(self, manga_files): finally: c.close() -def insertPost(self, member_id, post_id, title, fee_required, published_date, post_type): + def insertPost(self, member_id, post_id, title, fee_required, published_date, post_type): try: c = self.conn.cursor() post_id = int(post_id) From 2a3a75cad46b8a90d5ebeb973f7ba85dd58e774c Mon Sep 17 00:00:00 2001 From: amatuerCoder <17560146+bluerthanever@users.noreply.github.com> Date: Mon, 27 Apr 2020 21:52:16 +0800 Subject: [PATCH 06/22] Some logical correction to `insertPost` --- PixivDBManager.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PixivDBManager.py b/PixivDBManager.py index d050ab23..901a133b 100644 --- a/PixivDBManager.py +++ b/PixivDBManager.py @@ -571,11 +571,11 @@ def insertPost(self, member_id, post_id, title, fee_required, published_date, po c = self.conn.cursor() post_id = int(post_id) c.execute( - '''INSERT OR IGNORE INTO fanbox_master_post (member_id, post_id, last_update_date) - VALUES(?, ?, datetime('now'))''', (member_id, post_id)) + '''INSERT OR IGNORE INTO fanbox_master_post (member_id, post_id) VALUES(?, ?)''', + (member_id, post_id)) c.execute( - '''UPDATE fanbox_master_post SET title = ?, fee_required = ?, published_date = ?, post_type = ? - WHERE post_id = ?''', + '''UPDATE fanbox_master_post SET title = ?, fee_required = ?, published_date = ?, + post_type = ?, last_update_date = datetime('now') WHERE post_id = ?''', (title, fee_required, published_date, post_type, post_id)) self.conn.commit() except BaseException: From 90a82de22a66a8e0f456b2783300e242c7806c50 Mon Sep 17 00:00:00 2001 From: amatuerCoder <17560146+bluerthanever@users.noreply.github.com> Date: Mon, 27 Apr 2020 23:42:08 +0800 Subject: [PATCH 07/22] Moved codes position Made codes to parse `feeRequired` to be ran into earlier --- PixivModelFanbox.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/PixivModelFanbox.py b/PixivModelFanbox.py index fa5b222a..203b54a7 100644 --- a/PixivModelFanbox.py +++ b/PixivModelFanbox.py @@ -151,6 +151,10 @@ def parsePost(self, jsPost): self.worksDateDateTime = datetime_z.parse_datetime(self.worksDate) self.updatedDate = jsPost["updatedDatetime"] self.updatedDateDatetime = datetime_z.parse_datetime(self.updatedDate) + + if "feeRequired" in jsPost: + self.feeRequired = jsPost["feeRequired"] + # Issue #420 if self._tzInfo is not None: self.worksDateDateTime = self.worksDateDateTime.astimezone( @@ -249,9 +253,6 @@ def parseBody(self, jsPost): self.body_text, self.getEmbedData(jsPost["body"]["video"], jsPost)) - if "feeRequired" in jsPost: - self.feeRequired = jsPost["feeRequired"] - def getEmbedData(self, embedData, jsPost): if not os.path.exists("content_provider.json"): raise PixivException("Missing content_provider.json, please redownload application!", From c79202366ee53206dccd0a975b858d2c797847ab Mon Sep 17 00:00:00 2001 From: amatuerCoder <17560146+bluerthanever@users.noreply.github.com> Date: Tue, 28 Apr 2020 00:05:01 +0800 Subject: [PATCH 08/22] Added sep for `exportFanboxPosts` Allow users to choose between "," and "\t" --- PixivDBManager.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/PixivDBManager.py b/PixivDBManager.py index 901a133b..319eb9bd 100644 --- a/PixivDBManager.py +++ b/PixivDBManager.py @@ -231,7 +231,7 @@ def exportDetailedList(self, filename): c.close() print('done.') - def exportFanboxPostList(self, filename): + def exportFanboxPostList(self, filename, sep=","): print('Exporting FANBOX post list...', end=' ') try: c = self.conn.cursor() @@ -239,14 +239,15 @@ def exportFanboxPostList(self, filename): ORDER BY member_id, post_id''') filename = filename + '.csv' writer = codecs.open(filename, 'wb', encoding='utf-8') - writer.write( - 'member_id,post_id,title,fee_required,published_date,update_date,post_type,last_update_date\r\n') + columns = ['member_id','post_id','title','fee_required','published_date','update_date','post_type','last_update_date'] + writer.write(sep.join(columns)) + writer.write('\r\n') for row in c: for string in row: # Unicode write!! data = str(string) writer.write(data) - writer.write(',') + writer.write(sep) writer.write('\r\n') writer.write('###END-OF-FILE###') writer.close() @@ -979,7 +980,9 @@ def main(self): self.printMemberList(isDeleted=True) elif selection == 'f1': filename = input('Filename? ').rstrip("\r") - self.exportFanboxPostList(filename) + sep = input('Separator? (1(default)=",", 2="\\t") ').rstrip("\r") + sep = "\t" if sep=="2" else "," + self.exportFanboxPostList(filename, sep) elif selection == 'f2': member_id = input('member_id? ').rstrip("\r") self.deleteFanboxPost(member_id, "post_id") From 3b1387dee354794f9347992261e4ea0ff3aa1400 Mon Sep 17 00:00:00 2001 From: amatuerCoder <17560146+bluerthanever@users.noreply.github.com> Date: Tue, 28 Apr 2020 01:13:41 +0800 Subject: [PATCH 09/22] Added method to get followed artists --- PixivBrowserFactory.py | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/PixivBrowserFactory.py b/PixivBrowserFactory.py index 629ad63e..19f7ac09 100644 --- a/PixivBrowserFactory.py +++ b/PixivBrowserFactory.py @@ -725,14 +725,30 @@ def handleDebugTagSearchPage(self, response, url): PixivHelper.toUnicode(response))) def fanboxGetSupportedUsers(self): - ''' get all supported users from the list from https://fanbox.pixiv.net/api/plan.listSupporting''' - url = 'https://fanbox.pixiv.net/api/plan.listSupporting' + url = 'https://api.fanbox.cc/plan.listSupporting' PixivHelper.print_and_log('info', f'Getting supported artists from {url}') - referer = "https://www.pixiv.net/fanbox/support/creators" + referer = "https://www.fanbox.cc/creators/supporting" req = mechanize.Request(url) req.add_header('Accept', 'application/json, text/plain, */*') req.add_header('Referer', referer) - req.add_header('Origin', 'https://www.pixiv.net') + req.add_header('Origin', 'https://www.fanbox.cc') + req.add_header('User-Agent', self._config.useragent) + + res = self.open_with_retry(req) + # read the json response + response = res.read() + res.close() + result = Fanbox(response) + return result + + def fanboxGetFollowedUsers(self): + url = 'https://api.fanbox.cc/creator.listFollowing' + PixivHelper.print_and_log('info', f'Getting supported artists from {url}') + referer = "https://www.fanbox.cc/creators/following" + req = mechanize.Request(url) + req.add_header('Accept', 'application/json, text/plain, */*') + req.add_header('Referer', referer) + req.add_header('Origin', 'https://www.fanbox.cc') req.add_header('User-Agent', self._config.useragent) res = self.open_with_retry(req) @@ -778,21 +794,6 @@ def fanboxGetPostsFromArtist(self, artist_id, next_url=""): for post in result.posts: js = self.fanboxGetPost(post.imageId, artist_id) - - - - - - - - - - - - - - - post.parsePost(js["body"]) return result From ac8abf16acca2c16115062bef43b44973e763ce9 Mon Sep 17 00:00:00 2001 From: amatuerCoder <17560146+bluerthanever@users.noreply.github.com> Date: Tue, 28 Apr 2020 01:17:02 +0800 Subject: [PATCH 10/22] Added method for downloading from following artists --- PixivUtil2.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/PixivUtil2.py b/PixivUtil2.py index b6dc68ff..12c364fd 100755 --- a/PixivUtil2.py +++ b/PixivUtil2.py @@ -1938,6 +1938,31 @@ def menu_fanbox_download_supported_artist(op_is_valid, args): PixivHelper.print_and_log("error", "Error processing FANBOX Artist: {0} ==> {1}".format(artist_id, pex.message)) +def menu_fanbox_download_followed_artists(op_is_valid, args): + __log__.info('Download FANBOX Followed Artists mode.') + end_page = 0 + + if op_is_valid and len(args) > 0: + end_page = int(args[0]) + else: + end_page = input("Max Page = ").rstrip("\r") or 0 + end_page = int(end_page) + + result = __br__.fanboxGetFollowedUsers() + if len(result.supportedArtist) == 0: + PixivHelper.print_and_log("info", "No following artist!") + return + PixivHelper.print_and_log("info", "Found {0} followed artist(s)".format(len(result.supportedArtist))) + print(result.supportedArtist) + + for artist_id in result.supportedArtist: + # Issue #567 + try: + processFanboxArtist(artist_id, end_page) + except PixivException as pex: + PixivHelper.print_and_log("error", "Error processing FANBOX Artist: {0} ==> {1}".format(artist_id, pex.message)) + + def menu_fanbox_download_by_post_id(op_is_valid, args): __log__.info('Download FANBOX by post id mode.') if op_is_valid and len(args) > 0: @@ -2137,7 +2162,7 @@ def set_console_title(title=''): def setup_option_parser(): global __valid_options - __valid_options = ('1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', 'f1', 'f2', 'f3', 'd', 'e', 'm') + __valid_options = ('1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', 'f1', 'f2', 'f3', 'f4', 'd', 'e', 'm') parser = OptionParser() parser.add_option('-s', '--startaction', dest='startaction', help='''Action you want to load your program with: @@ -2156,6 +2181,7 @@ def setup_option_parser(): f1 - Download from supported artists (FANBOX) f2 - Download by artist id (FANBOX) f3 - Download by post id (FANBOX) +f4 - Download from following artists (FANBOX) e - Export online bookmark m - Export online user bookmark d - Manage database''') @@ -2238,6 +2264,8 @@ def main_loop(ewd, op_is_valid, selection, np_is_valid_local, args): menu_fanbox_download_by_artist_id(op_is_valid, args) elif selection == 'f3': menu_fanbox_download_by_post_id(op_is_valid, args) + elif selection == 'f4': + menu_fanbox_download_followed_artists(op_is_valid, args) # END PIXIV FANBOX elif selection == '-all': if not np_is_valid_local: From 09d6041e3b7b4f0cf181a2f536ca60a96cd89cdf Mon Sep 17 00:00:00 2001 From: amatuerCoder <17560146+bluerthanever@users.noreply.github.com> Date: Tue, 28 Apr 2020 01:22:47 +0800 Subject: [PATCH 11/22] Forgot about menu --- PixivUtil2.py | 1 + 1 file changed, 1 insertion(+) diff --git a/PixivUtil2.py b/PixivUtil2.py index 12c364fd..c02d9178 100755 --- a/PixivUtil2.py +++ b/PixivUtil2.py @@ -1516,6 +1516,7 @@ def menu(): print('f1. Download from supported artists (FANBOX)') print('f2. Download by artist id (FANBOX)') print('f3. Download by post id (FANBOX)') + print('f3. Download from followed artists (FANBOX)') print('------------------------') print('d. Manage database') print('e. Export online bookmark') From 39cb68c6809c165a500d090133df4e057bf2f96b Mon Sep 17 00:00:00 2001 From: amatuerCoder <17560146+bluerthanever@users.noreply.github.com> Date: Tue, 28 Apr 2020 01:23:35 +0800 Subject: [PATCH 12/22] Typo. crap.... I should be sleeping now. --- PixivUtil2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PixivUtil2.py b/PixivUtil2.py index c02d9178..5e5acfa3 100755 --- a/PixivUtil2.py +++ b/PixivUtil2.py @@ -1516,7 +1516,7 @@ def menu(): print('f1. Download from supported artists (FANBOX)') print('f2. Download by artist id (FANBOX)') print('f3. Download by post id (FANBOX)') - print('f3. Download from followed artists (FANBOX)') + print('f4. Download from followed artists (FANBOX)') print('------------------------') print('d. Manage database') print('e. Export online bookmark') From fe1e2ec0130adc96a8e3252ff6868fcb54d3ae44 Mon Sep 17 00:00:00 2001 From: amatuerCoder <17560146+bluerthanever@users.noreply.github.com> Date: Tue, 28 Apr 2020 13:11:38 +0800 Subject: [PATCH 13/22] Big changes..... whew 1. Deleted class `Fanbox` which is not too much of a use I guess.... 2. Added property `creatorId` for class `FanboxArtist`, and `SUPPORTED` and `FOLLOWED` as constants(?) to be used when deciding the api for getting artist list (supported or followed) 3. Changed the `init` method of `FanboxArtist`, to just set instance property values instead of parsing jsons 4. Made the `parsePosts` method to return a list of posts instead of saving the list to its property, thus no need to create new `FanboxArtist` instance every time for every page. --- PixivModelFanbox.py | 125 ++++++++++++++++++++++++-------------------- 1 file changed, 67 insertions(+), 58 deletions(-) diff --git a/PixivModelFanbox.py b/PixivModelFanbox.py index 203b54a7..7e2d2630 100644 --- a/PixivModelFanbox.py +++ b/PixivModelFanbox.py @@ -6,46 +6,55 @@ import demjson from bs4 import BeautifulSoup -import datetime_z import PixivHelper +import datetime_z from PixivException import PixivException -class Fanbox(object): - supportedArtist = None - - def __init__(self, page): - js = demjson.decode(page) - - if "error" in js and js["error"]: - raise PixivException("Error when requesting Fanbox", 9999, page) - - if "body" in js and js["body"] is not None: - self.parseSupportedArtists(js["body"]) - - def parseSupportedArtists(self, js_body): - self.supportedArtist = list() - # Fix #495 - if "supportingPlans" in js_body: - js_body = js_body["supportingPlans"] - for creator in js_body: - self.supportedArtist.append(int(creator["user"]["userId"])) - - class FanboxArtist(object): artistId = 0 - posts = None + creatorId = "" nextUrl = None hasNextPage = False _tzInfo = None - # require additional API call artistName = "" artistToken = "" - def __init__(self, artist_id, page, tzInfo=None): + SUPPORTED = 0 + FOLLOWED = 1 + + @classmethod + def parseArtists(cls, page, tzInfo=None): + artists = list() + js = demjson.decode(page) + + if "error" in js and js["error"]: + raise PixivException("Error when requesting Fanbox", 9999, page) + + if "body" in js and js["body"] is not None: + js_body = js["body"] + if "supportingPlans" in js["body"]: + js_body = js_body["supportingPlans"] + for creator in js_body: + artists.append( + FanboxArtist(creator["user"]["userId"], + creator["user"]["name"], + creator["creatorId"], + tzInfo=tzInfo + )) + return artists + + def __init__(self, artist_id, artist_name, creator_id, tzInfo=None): self.artistId = int(artist_id) + self.artistName = artist_name + self.creatorId = creator_id self._tzInfo = tzInfo + + def __str__(self): + return f"({self.artistId}, {self.creatorId}, {self.artistName})" + + def parsePosts(self, page): js = demjson.decode(page) if "error" in js and js["error"]: @@ -53,32 +62,33 @@ def __init__(self, artist_id, page, tzInfo=None): "Error when requesting Fanbox artist: {0}".format(self.artistId), 9999, page) if js["body"] is not None: - self.parsePosts(js["body"]) + js_body = js["body"] - def parsePosts(self, js_body): - self.posts = list() + posts = list() - if "creator" in js_body: - self.artistName = js_body["creator"]["user"]["name"] + if "creator" in js_body: + self.artistName = js_body["creator"]["user"]["name"] - if "post" in js_body: - # new api - post_root = js_body["post"] - else: - # https://www.pixiv.net/ajax/fanbox/post?postId={0} - # or old api - post_root = js_body + if "post" in js_body: + # new api + post_root = js_body["post"] + else: + # https://www.pixiv.net/ajax/fanbox/post?postId={0} + # or old api + post_root = js_body + + for jsPost in post_root["items"]: + post_id = int(jsPost["id"]) + post = FanboxPost(post_id, self, jsPost, tzInfo=self._tzInfo) + posts.append(post) + # sanity check + assert (self.artistId == int(jsPost["user"]["userId"])), "Different user id from constructor!" - for jsPost in post_root["items"]: - post_id = int(jsPost["id"]) - post = FanboxPost(post_id, self, jsPost, tzInfo=self._tzInfo) - self.posts.append(post) - # sanity check - assert (self.artistId == int(jsPost["user"]["userId"])), "Different user id from constructor!" + self.nextUrl = post_root["nextUrl"] + if self.nextUrl is not None and len(self.nextUrl) > 0: + self.hasNextPage = True - self.nextUrl = post_root["nextUrl"] - if self.nextUrl is not None and len(self.nextUrl) > 0: - self.hasNextPage = True + return posts class FanboxPost(object): @@ -154,13 +164,12 @@ def parsePost(self, jsPost): if "feeRequired" in jsPost: self.feeRequired = jsPost["feeRequired"] - + # Issue #420 if self._tzInfo is not None: self.worksDateDateTime = self.worksDateDateTime.astimezone( self._tzInfo) - self.type = jsPost["type"] if self.type not in FanboxPost._supportedType: raise PixivException("Unsupported post type = {0} for post = {1}".format( @@ -228,30 +237,30 @@ def parseBody(self, jsPost): elif block["type"] == "image": imageId = block["imageId"] self.body_text = u"{0}
".format( - self.body_text, - jsPost["body"]["imageMap"][imageId]["originalUrl"], - jsPost["body"]["imageMap"][imageId]["thumbnailUrl"]) + self.body_text, + jsPost["body"]["imageMap"][imageId]["originalUrl"], + jsPost["body"]["imageMap"][imageId]["thumbnailUrl"]) self.try_add(jsPost["body"]["imageMap"][imageId]["originalUrl"], self.images) self.try_add(jsPost["body"]["imageMap"][imageId]["originalUrl"], self.embeddedFiles) elif block["type"] == "file": fileId = block["fileId"] self.body_text = u"{0}
{2}".format( - self.body_text, - jsPost["body"]["fileMap"][fileId]["url"], - jsPost["body"]["fileMap"][fileId]["name"]) + self.body_text, + jsPost["body"]["fileMap"][fileId]["url"], + jsPost["body"]["fileMap"][fileId]["name"]) self.try_add(jsPost["body"]["fileMap"][fileId]["url"], self.images) self.try_add(jsPost["body"]["fileMap"][fileId]["url"], self.embeddedFiles) elif block["type"] == "embed": # Implement #470 embedId = block["embedId"] self.body_text = u"{0}
{1}".format( - self.body_text, - self.getEmbedData(jsPost["body"]["embedMap"][embedId], jsPost)) + self.body_text, + self.getEmbedData(jsPost["body"]["embedMap"][embedId], jsPost)) # Issue #476 if "video" in jsPost["body"]: self.body_text = u"{0}
{1}".format( - self.body_text, - self.getEmbedData(jsPost["body"]["video"], jsPost)) + self.body_text, + self.getEmbedData(jsPost["body"]["video"], jsPost)) def getEmbedData(self, embedData, jsPost): if not os.path.exists("content_provider.json"): From ef80c8da25506f965d2146b23e93b3a0b9be2569 Mon Sep 17 00:00:00 2001 From: amatuerCoder <17560146+bluerthanever@users.noreply.github.com> Date: Tue, 28 Apr 2020 13:22:17 +0800 Subject: [PATCH 14/22] Some big changes 1. Changed method name from `menu_fanbox_download_supported_artist` to `menu_fanbox_download_from_artist` with an extra parameter for deciding whether to get artists from supporting list or following list, which should be passed with `FanboxArtist.SUPPORTED` or `FanboxArtist.FOLLOWED` 2. Inside the new `menu_fanbox_download_from_artist` method, it obtains a list of `FanboxArtist` instances, and then update it's `ArtistToken` with some new methods added in `PixivBrowser` 3. The `FanboxArtist` instances would by passed one by one to `processFanboxArtist`, which uses `FanboxArtist` as a parameter now instead of id which is an integer before 4. Inside the new `processFanboxArtist`, posts are obtained from modified methods in `PixivBrowser` 5. Changed the name of `menu_fanbox_download_by_artist_id` to `menu_fanbox_download_by_artist_or_creator_id`, and allowed it to use the new `creatorId` to get posts --- PixivUtil2.py | 164 ++++++++++++++++++++++++++------------------------ 1 file changed, 87 insertions(+), 77 deletions(-) diff --git a/PixivUtil2.py b/PixivUtil2.py index 5e5acfa3..e02271a6 100755 --- a/PixivUtil2.py +++ b/PixivUtil2.py @@ -1514,7 +1514,7 @@ def menu(): print('12. Download by Group Id') print('------------------------') print('f1. Download from supported artists (FANBOX)') - print('f2. Download by artist id (FANBOX)') + print('f2. Download by artist/creator id (FANBOX)') print('f3. Download by post id (FANBOX)') print('f4. Download from followed artists (FANBOX)') print('------------------------') @@ -1914,8 +1914,15 @@ def menu_export_online_user_bookmark(opisvalid, args): export_bookmark(filename, 'n', 1, 0, member_id) -def menu_fanbox_download_supported_artist(op_is_valid, args): - __log__.info('Download FANBOX Supported Artists mode.') +def menu_fanbox_download_from_artist(op_is_valid, via, args): + via_type = "" + if via == PixivModelFanbox.FanboxArtist.SUPPORTED: + via_type = "supported" + elif via == PixivModelFanbox.FanboxArtist.FOLLOWED: + via_type = "followed" + + + __log__.info(f'Download FANBOX {via_type.capitalize()} Artists mode.') end_page = 0 if op_is_valid and len(args) > 0: @@ -1924,44 +1931,45 @@ def menu_fanbox_download_supported_artist(op_is_valid, args): end_page = input("Max Page = ").rstrip("\r") or 0 end_page = int(end_page) - result = __br__.fanboxGetSupportedUsers() - if len(result.supportedArtist) == 0: - PixivHelper.print_and_log("info", "No supported artist!") + fanbox_login = __br__.fanboxLoginUsingCookie() + if not(fanbox_login): + __log__.info("FANBOX login cookie string invalid, please update in config.ini") return - PixivHelper.print_and_log("info", "Found {0} supported artist(s)".format(len(result.supportedArtist))) - print(result.supportedArtist) + + - for artist_id in result.supportedArtist: - # Issue #567 - try: - processFanboxArtist(artist_id, end_page) - except PixivException as pex: - PixivHelper.print_and_log("error", "Error processing FANBOX Artist: {0} ==> {1}".format(artist_id, pex.message)) + artists = __br__.fanboxGetUsers(via) + + + + + -def menu_fanbox_download_followed_artists(op_is_valid, args): - __log__.info('Download FANBOX Followed Artists mode.') - end_page = 0 + + + - if op_is_valid and len(args) > 0: - end_page = int(args[0]) - else: - end_page = input("Max Page = ").rstrip("\r") or 0 - end_page = int(end_page) + + + + + - result = __br__.fanboxGetFollowedUsers() - if len(result.supportedArtist) == 0: - PixivHelper.print_and_log("info", "No following artist!") + + if len(artists) == 0: + PixivHelper.print_and_log("info", f"No {via_type} artist!") return - PixivHelper.print_and_log("info", "Found {0} followed artist(s)".format(len(result.supportedArtist))) - print(result.supportedArtist) + PixivHelper.print_and_log("info", f"Found {len(artists)} {via_type} artist(s)") + print(", ".join(str(artists))) - for artist_id in result.supportedArtist: + for artist in artists: + __br__.fanboxUpdateArtistToken(artist) # Issue #567 try: - processFanboxArtist(artist_id, end_page) + processFanboxArtist(artist, end_page) except PixivException as pex: - PixivHelper.print_and_log("error", "Error processing FANBOX Artist: {0} ==> {1}".format(artist_id, pex.message)) + PixivHelper.print_and_log("error", f"Error processing {via_type} FANBOX Artist: {artist.artistId} ==> {pex.message}") def menu_fanbox_download_by_post_id(op_is_valid, args): @@ -1982,52 +1990,53 @@ def menu_fanbox_download_by_post_id(op_is_valid, args): del post -def processFanboxArtist(artist_id, end_page): +def processFanboxArtist(artist, end_page): current_page = 1 next_url = None image_count = 1 - while(True): - PixivHelper.print_and_log("info", "Processing {0}, page {1}".format(artist_id, current_page)) + while (True): + PixivHelper.print_and_log("info", "Processing {0}, page {1}".format(artist, current_page)) try: - result_artist = __br__.fanboxGetPostsFromArtist(artist_id, next_url) + posts = __br__.fanboxGetPostsFromArtist(artist, next_url) except PixivException as pex: print(pex) break - for post in result_artist.posts: + for post in posts: print("#{0}".format(image_count)) post.printPost() - + # images if post.type in PixivModelFanbox.FanboxPost._supportedType: - processFanboxImages(post, result_artist) + processFanboxImages(post, artist) image_count = image_count + 1 - if not result_artist.hasNextPage: - PixivHelper.print_and_log("info", "No more post for {0}".format(artist_id)) + if not artist.hasNextPage: + PixivHelper.print_and_log("info", "No more post for {0}".format(artist)) break current_page = current_page + 1 if end_page > 0 and current_page > end_page: - PixivHelper.print_and_log("info", "Reaching page limit for {0}, limit {1}".format(artist_id, end_page)) + PixivHelper.print_and_log("info", "Reaching page limit for {0}, limit {1}".format(artist, end_page)) break - next_url = result_artist.nextUrl + next_url = artist.nextUrl if next_url is None: - PixivHelper.print_and_log("info", "No more next page for {0}".format(artist_id)) + PixivHelper.print_and_log("info", "No more next page for {0}".format(artist)) break -def processFanboxImages(post, result_artist): - __dbManager__.insertPost(result_artist.artistId, post.imageId, post.imageTitle, +def processFanboxImages(post, artist): + __dbManager__.insertPost(artist.artistId, post.imageId, post.imageTitle, post.feeRequired, post.worksDate, post.type) if post.is_restricted: PixivHelper.print_and_log("info", "Skipping post: {0} due to restricted post.".format(post.imageId)) return - + result = __dbManager__.selectPostByPostId(post.imageId) if result: updated_date = result[5] if updated_date is not None and post.updatedDateDatetime <= datetime_z.parse_datetime(updated_date): - PixivHelper.print_and_log("info", "Skipping post: {0} bacause it was downloaded before.".format(post.imageId)) + PixivHelper.print_and_log("info", + "Skipping post: {0} bacause it was downloaded before.".format(post.imageId)) return # cover image @@ -2037,7 +2046,7 @@ def processFanboxImages(post, result_artist): "{0}_".format(post.imageId)) filename = PixivHelper.make_filename(__config__.filenameFormat, post, - artistInfo=result_artist, + artistInfo=artist, tagsSeparator=__config__.tagsSeparator, tagsLimit=__config__.tagsLimit, fileUrl=fake_image_url, @@ -2050,7 +2059,7 @@ def processFanboxImages(post, result_artist): print("Downloading cover from {0}".format(post.coverImageUrl)) print("Saved to {0}".format(filename)) - referer = "https://www.pixiv.net/fanbox/creator/{0}/post/{1}".format(result_artist.artistId, post.imageId) + referer = "https://www.pixiv.net/fanbox/creator/{0}/post/{1}".format(artist.artistId, post.imageId) # don't pass the post id and page number to skip db check (result, filename) = download_image(post.coverImageUrl, filename, @@ -2061,7 +2070,7 @@ def processFanboxImages(post, result_artist): PixivHelper.get_logger().debug("Download %s result: %s", filename, result) else: PixivHelper.print_and_log("info", "No Cover Image for post: {0}.".format(post.imageId)) - + if post.images is None or len(post.images) == 0: PixivHelper.print_and_log("info", "No Image available in post: {0}.".format(post.imageId)) # return @@ -2070,21 +2079,22 @@ def processFanboxImages(post, result_artist): print("Image Count = {0}".format(len(post.images))) for image_url in post.images: # fake the image_url for filename compatibility, add post id and pagenum - fake_image_url = image_url.replace("{0}/".format(post.imageId), "{0}_p{1}_".format(post.imageId, current_page)) + fake_image_url = image_url.replace("{0}/".format(post.imageId), + "{0}_p{1}_".format(post.imageId, current_page)) filename = PixivHelper.make_filename(__config__.filenameMangaFormat, - post, - artistInfo=result_artist, - tagsSeparator=__config__.tagsSeparator, - tagsLimit=__config__.tagsLimit, - fileUrl=fake_image_url, - bookmark=None, - searchTags='') + post, + artistInfo=artist, + tagsSeparator=__config__.tagsSeparator, + tagsLimit=__config__.tagsLimit, + fileUrl=fake_image_url, + bookmark=None, + searchTags='') filename = PixivHelper.sanitize_filename(filename, __config__.rootDirectory) post.linkToFile[image_url] = filename - referer = "https://www.pixiv.net/fanbox/creator/{0}/post/{1}".format(result_artist.artistId, post.imageId) + referer = "https://www.pixiv.net/fanbox/creator/{0}/post/{1}".format(artist.artistId, post.imageId) print("Downloading image {0} from {1}".format(current_page, image_url)) print("Saved to {0}".format(filename)) @@ -2106,13 +2116,13 @@ def processFanboxImages(post, result_artist): # Implement #447 filename = PixivHelper.make_filename(__config__.filenameInfoFormat, - post, - artistInfo=result_artist, - tagsSeparator=__config__.tagsSeparator, - tagsLimit=__config__.tagsLimit, - fileUrl="{0}".format(post.imageId), - bookmark=None, - searchTags='') + post, + artistInfo=artist, + tagsSeparator=__config__.tagsSeparator, + tagsLimit=__config__.tagsLimit, + fileUrl="{0}".format(post.imageId), + bookmark=None, + searchTags='') filename = PixivHelper.sanitize_filename(filename, __config__.rootDirectory) if __config__.writeImageInfo: @@ -2128,22 +2138,22 @@ def processFanboxImages(post, result_artist): __dbManager__.updatePostLastUpdateDate(post.imageId, post.updatedDate) -def menu_fanbox_download_by_artist_id(op_is_valid, args): - __log__.info('Download FANBOX by Artist ID mode.') +def menu_fanbox_download_by_artist_or_creator_id(op_is_valid, args): + __log__.info('Download FANBOX by Artist or Creator ID mode.') end_page = 0 - artist_id = '' + id = '' if op_is_valid and len(args) > 0: - artist_id = str(int(args[0])) + id = str(int(args[0])) if len(args) > 1: end_page = args[1] else: - artist_id = input("Artist ID = ").rstrip("\r") + id = input("Artist/Creator ID = ").rstrip("\r") end_page = input("Max Page = ").rstrip("\r") or 0 end_page = int(end_page) - - processFanboxArtist(artist_id, end_page) + artist = __br__.fanboxGetArtistById(id) + processFanboxArtist(artist, end_page) def menu_reload_config(): @@ -2180,9 +2190,9 @@ def setup_option_parser(): 11 - Download images from Member Bookmark 12 - Download images by Group Id f1 - Download from supported artists (FANBOX) -f2 - Download by artist id (FANBOX) +f2 - Download by artist/creator id (FANBOX) f3 - Download by post id (FANBOX) -f4 - Download from following artists (FANBOX) +f4 - Download from followed artists (FANBOX) e - Export online bookmark m - Export online user bookmark d - Manage database''') @@ -2260,13 +2270,13 @@ def main_loop(ewd, op_is_valid, selection, np_is_valid_local, args): menu_import_list() # PIXIV FANBOX elif selection == 'f1': - menu_fanbox_download_supported_artist(op_is_valid, args) + menu_fanbox_download_from_artist(op_is_valid, PixivModelFanbox.FanboxArtist.SUPPORTED, args) elif selection == 'f2': - menu_fanbox_download_by_artist_id(op_is_valid, args) + menu_fanbox_download_by_artist_or_creator_id(op_is_valid, args) elif selection == 'f3': menu_fanbox_download_by_post_id(op_is_valid, args) elif selection == 'f4': - menu_fanbox_download_followed_artists(op_is_valid, args) + menu_fanbox_download_from_artist(op_is_valid, PixivModelFanbox.FanboxArtist.FOLLOWED, args) # END PIXIV FANBOX elif selection == '-all': if not np_is_valid_local: From a92cb5f08af07a1a60e12d365fc4c8aad2f40163 Mon Sep 17 00:00:00 2001 From: amatuerCoder <17560146+bluerthanever@users.noreply.github.com> Date: Tue, 28 Apr 2020 13:38:24 +0800 Subject: [PATCH 15/22] Big changes 1. Removed `Fanbox` in importation 2. For method `_loadCookie`, added parameter `domain` and logic to decide what cookie it will add 3. In method `loginUsingCookie`, when calling `_loadCookie`, added `domain` parameter 4. Added new method `fanboxLoginUsingCookie`, mainly to check login status when getting artist list of followed or supported 5. Changed the name of method `fanboxGetSupportedUsers` to `fanboxGetUsers` with a new parameter via, to decide what kind of artist list to get. And returns a list of `FanboxArtist` instances, instead of integers 6. Merged the previously added `fanboxGetFollowedUsers` into `fanboxGetUsers` 7. Added method `fanboxUpdateArtistToken` to update `ArtistToken` and `ArtistName` of `FanboxArtist` instances 8. Added method `fanboxGetArtistById` to get`FanboxArtist` instance with creator/user id, to support the website transfer 9. Used the new APIs in some other FANBOX methods 10. Some minor format changes. --- PixivBrowserFactory.py | 235 ++++++++++++++++++++++++++--------------- 1 file changed, 152 insertions(+), 83 deletions(-) diff --git a/PixivBrowserFactory.py b/PixivBrowserFactory.py index 19f7ac09..0375535f 100644 --- a/PixivBrowserFactory.py +++ b/PixivBrowserFactory.py @@ -20,7 +20,7 @@ from PixivArtist import PixivArtist from PixivException import PixivException from PixivImage import PixivImage -from PixivModelFanbox import Fanbox, FanboxArtist, FanboxPost +from PixivModelFanbox import FanboxArtist, FanboxPost from PixivOAuth import PixivOAuth from PixivTags import PixivTags @@ -233,26 +233,33 @@ def fixUrl(self, url, useHttps=True): return "http://www.pixiv.net" + url return url - def _loadCookie(self, cookie_value): + def _loadCookie(self, cookie_value, domain): """ Load cookie to the Browser instance """ - ck = http.cookiejar.Cookie(version=0, name='PHPSESSID', value=cookie_value, port=None, - port_specified=False, domain='pixiv.net', domain_specified=False, - domain_initial_dot=False, path='/', path_specified=True, - secure=False, expires=None, discard=True, comment=None, - comment_url=None, rest={'HttpOnly': None}, rfc2109=False) + if "pixiv.net" in domain: + ck = http.cookiejar.Cookie(version=0, name='PHPSESSID', value=cookie_value, port=None, + port_specified=False, domain='pixiv.net', domain_specified=False, + domain_initial_dot=False, path='/', path_specified=True, + secure=False, expires=None, discard=True, comment=None, + comment_url=None, rest={'HttpOnly': None}, rfc2109=False) + elif "fanbox.cc" in domain: + ck = http.cookiejar.Cookie(version=0, name='FANBOXSESSID', value=cookie_value, port=None, + port_specified=False, domain='fanbox.cc', domain_specified=False, + domain_initial_dot=False, path='/', path_specified=True, + secure=False, expires=None, discard=True, comment=None, + comment_url=None, rest={'HttpOnly': None}, rfc2109=False) self.addCookie(ck) -# cookies = cookie_value.split(";") -# for cookie in cookies: -# temp = cookie.split("=") -# name = temp[0].strip() -# value= temp[1] if len(temp) > 1 else "" -# ck = cookielib.Cookie(version=0, name=name, value=value, port=None, -# port_specified=False, domain='pixiv.net', domain_specified=False, -# domain_initial_dot=False, path='/', path_specified=True, -# secure=False, expires=None, discard=True, comment=None, -# comment_url=None, rest={'HttpOnly': None}, rfc2109=False) -# self.addCookie(ck) + # cookies = cookie_value.split(";") + # for cookie in cookies: + # temp = cookie.split("=") + # name = temp[0].strip() + # value= temp[1] if len(temp) > 1 else "" + # ck = cookielib.Cookie(version=0, name=name, value=value, port=None, + # port_specified=False, domain='pixiv.net', domain_specified=False, + # domain_initial_dot=False, path='/', path_specified=True, + # secure=False, expires=None, discard=True, comment=None, + # comment_url=None, rest={'HttpOnly': None}, rfc2109=False) + # self.addCookie(ck) def _getInitConfig(self, page): init_config = page.find('input', attrs={'id': 'init-config'}) @@ -268,7 +275,7 @@ def loginUsingCookie(self, login_cookie=None): if len(login_cookie) > 0: PixivHelper.print_and_log('info', 'Trying to log in with saved cookie') self.clearCookie() - self._loadCookie(login_cookie) + self._loadCookie(login_cookie, "pixiv.net") res = self.open_with_retry('https://www.pixiv.net/') parsed = BeautifulSoup(res, features="html5lib").decode('utf-8') PixivHelper.get_logger().info('Logging in, return url: %s', res.geturl()) @@ -299,6 +306,40 @@ def loginUsingCookie(self, login_cookie=None): del parsed return result + def fanboxLoginUsingCookie(self, login_cookie=None): + """ Log in to Pixiv using saved cookie, return True if success """ + + if login_cookie is None or len(login_cookie) == 0: + login_cookie = self._config.cookieFanbox + + if len(login_cookie) > 0: + PixivHelper.print_and_log('info', 'Trying to log in with saved cookie') + self.clearCookie() + self._loadCookie(login_cookie, "fanbox.cc") + + req = mechanize.Request("https://www.fanbox.cc") + req.add_header('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8') + req.add_header('Origin', 'https://www.fanbox.cc') + req.add_header('User-Agent', self._config.useragent) + res = self.open_with_retry(req) + parsed = BeautifulSoup(res, features="html5lib").decode('utf-8') + PixivHelper.get_logger().info('Logging in, return url: %s', res.geturl()) + res.close() + + result = False + if '"user":{"isLoggedIn":true' in str(parsed): + result = True + + if result: + PixivHelper.print_and_log('info', 'Login successful.') + PixivHelper.get_logger().info('Logged in using cookie') + else: + PixivHelper.get_logger().info('Failed to log in using cookie') + PixivHelper.print_and_log('info', 'Cookie already expired/invalid.') + + del parsed + return result + def login(self, username, password): parsed = None try: @@ -501,7 +542,8 @@ def getMemberInfoWhitecube(self, member_id, artist, bookmark=False): self._put_to_cache(url, info) else: PixivHelper.print_and_log('info', 'Using OAuth to retrieve member info for: {0}'.format(member_id)) - if self._username is None or self._password is None or len(self._username) < 0 or len(self._password) < 0: + if self._username is None or self._password is None or len(self._username) < 0 or len( + self._password) < 0: raise PixivException("Empty Username or Password, remove cookie value and relogin, or add username/password to config.ini.") if self._oauth_manager is None: @@ -724,10 +766,16 @@ def handleDebugTagSearchPage(self, response, url): PixivHelper.safePrint(u"reply: {0}".format( PixivHelper.toUnicode(response))) - def fanboxGetSupportedUsers(self): - url = 'https://api.fanbox.cc/plan.listSupporting' - PixivHelper.print_and_log('info', f'Getting supported artists from {url}') - referer = "https://www.fanbox.cc/creators/supporting" + def fanboxGetUsers(self, via): + if via == FanboxArtist.SUPPORTED: + url = 'https://api.fanbox.cc/plan.listSupporting' + PixivHelper.print_and_log('info', f'Getting supported artists from {url}') + referer = "https://www.fanbox.cc/creators/supporting" + elif via == FanboxArtist.FOLLOWED: + url = 'https://api.fanbox.cc/creator.listFollowing' + PixivHelper.print_and_log('info', f'Getting supported artists from {url}') + referer = "https://www.fanbox.cc/creators/following" + req = mechanize.Request(url) req.add_header('Accept', 'application/json, text/plain, */*') req.add_header('Referer', referer) @@ -738,13 +786,30 @@ def fanboxGetSupportedUsers(self): # read the json response response = res.read() res.close() - result = Fanbox(response) - return result + _tzInfo = None + if self._config.useLocalTimezone: + _tzInfo = PixivHelper.LocalUTCOffsetTimezone() + artists = FanboxArtist.parseArtists(page=response, tzInfo=_tzInfo) + return artists + + def fanboxUpdateArtistToken(self, artist): + pixivArtist = PixivArtist(artist.artistId) + self.getMemberInfoWhitecube(artist.artistId, pixivArtist) + artist.artistName = pixivArtist.artistName + artist.artistToken = pixivArtist.artistToken + + def fanboxGetArtistById(self, id): + if re.match(r"^\d+$", id): + id_type = "userId" + else: + id_type = "creatorId" + + url = f'https://api.fanbox.cc/creator.get?{id_type}={id}' + PixivHelper.print_and_log('info', f'Getting artist information from {url}') + referer = "https://www.fanbox.cc" + if id_type == "creatorId": + referer += f"/@{id}" - def fanboxGetFollowedUsers(self): - url = 'https://api.fanbox.cc/creator.listFollowing' - PixivHelper.print_and_log('info', f'Getting supported artists from {url}') - referer = "https://www.fanbox.cc/creators/following" req = mechanize.Request(url) req.add_header('Accept', 'application/json, text/plain, */*') req.add_header('Referer', referer) @@ -755,60 +820,65 @@ def fanboxGetFollowedUsers(self): # read the json response response = res.read() res.close() - result = Fanbox(response) - return result + _tzInfo = None + if self._config.useLocalTimezone: + _tzInfo = PixivHelper.LocalUTCOffsetTimezone() - def fanboxGetPostsFromArtist(self, artist_id, next_url=""): + js = demjson.decode(response) + if "error" in js and js["error"]: + raise PixivException("Error when requesting Fanbox", 9999, page) + + if "body" in js and js["body"] is not None: + js_body = js["body"] + artist = FanboxArtist(js_body["user"]["userId"], + js_body["user"]["name"], + js_body["creatorId"], + tzInfo=_tzInfo) + self.fanboxUpdateArtistToken(artist) + return artist + + def fanboxGetPostsFromArtist(self, artist, next_url=""): ''' get all posts from the supported user from https://fanbox.pixiv.net/api/post.listCreator?userId=1305019&limit=10 ''' # Issue #641 if next_url is None or next_url == "": - url = f"https://fanbox.pixiv.net/api/post.listCreator?userId={artist_id}&limit=10" + url = f"https://api.fanbox.cc/post.listCreator?userId={artist.artistId}&limit=10" elif next_url.startswith("https://"): url = next_url else: - url = "https://www.pixiv.net" + next_url + url = "https://www.fanbox.cc" + next_url # Fix #494 PixivHelper.print_and_log('info', 'Getting posts from ' + url) - referer = f"https://www.pixiv.net/fanbox/creator/{artist_id}" + referer = f"https://www.fanbox.cc/@{artist.creatorId}" req = mechanize.Request(url) req.add_header('Accept', 'application/json, text/plain, */*') req.add_header('Referer', referer) - req.add_header('Origin', 'https://www.pixiv.net') + req.add_header('Origin', 'https://www.fanbox.cc') req.add_header('User-Agent', self._config.useragent) res = self.open_with_retry(req) response = res.read() PixivHelper.get_logger().debug(response.decode('utf8')) res.close() - # Issue #420 - _tzInfo = None - if self._config.useLocalTimezone: - _tzInfo = PixivHelper.LocalUTCOffsetTimezone() - result = FanboxArtist(artist_id, response, tzInfo=_tzInfo) + posts = artist.parsePosts(response) - pixivArtist = PixivArtist(artist_id) - self.getMemberInfoWhitecube(artist_id, pixivArtist) - result.artistName = pixivArtist.artistName - result.artistToken = pixivArtist.artistToken - - for post in result.posts: - js = self.fanboxGetPost(post.imageId, artist_id) + for post in posts: + js = self.fanboxGetPost(post.imageId, artist) post.parsePost(js["body"]) - return result + return posts - def fanboxGetPost(self, post_id, member_id=0): + def fanboxGetPost(self, post_id, artist=None): # https://fanbox.pixiv.net/api/post.info?postId=279561 # https://www.pixiv.net/fanbox/creator/104409/post/279561 - p_url = f"https://fanbox.pixiv.net/api/post.info?postId={post_id}" + p_url = f"https://api.fanbox.cc/post.info?postId={post_id}" # referer doesn't seeem to be essential - p_referer = f"https://www.pixiv.net/fanbox/creator/{member_id}/post/{post_id}" + p_referer = f"https://www.fanbox.cc/@{artist.creatorId}/posts/{post_id}" PixivHelper.get_logger().debug('Getting post detail from %s', p_url) p_req = mechanize.Request(p_url) p_req.add_header('Accept', 'application/json, text/plain, */*') p_req.add_header('Referer', p_referer) - p_req.add_header('Origin', 'https://www.pixiv.net') + p_req.add_header('Origin', 'https://www.fanbox.cc') p_req.add_header('User-Agent', self._config.useragent) p_res = self.open_with_retry(p_req) @@ -816,16 +886,15 @@ def fanboxGetPost(self, post_id, member_id=0): PixivHelper.get_logger().debug(p_response.decode('utf8')) p_res.close() js = demjson.decode(p_response) - if member_id: + if artist: return js else: _tzInfo = None if self._config.useLocalTimezone: _tzInfo = PixivHelper.LocalUTCOffsetTimezone() - user = object().__new__(FanboxArtist) - user.artistId = js["body"]["user"]["userId"] - user.name = js["body"]["user"]["name"] - post = FanboxPost(post_id, user, js["body"], _tzInfo) + artist = FanboxArtist(js["body"]["user"]["userId"], js["body"]["creatorId"], js["body"]["user"]["name"]) + self.fanboxUpdateArtistToken(artist) + post = FanboxPost(post_id, artist, js["body"], _tzInfo) return post @@ -908,34 +977,34 @@ def testSearchTags(): oldest_first, start_page) resultS.PrintInfo() - assert(len(resultS.itemList) > 0) + assert (len(resultS.itemList) > 0) def testImage(): print("test image mode") print(">>") (result, page) = b.getImagePage(60040975) print(result.PrintInfo()) - assert(len(result.imageTitle) > 0) + assert (len(result.imageTitle) > 0) print(result.artist.PrintInfo()) - assert(len(result.artist.artistToken) > 0) - assert("R-18" not in result.imageTags) + assert (len(result.artist.artistToken) > 0) + assert ("R-18" not in result.imageTags) print(">>") (result2, page2) = b.getImagePage(59628358) print(result2.PrintInfo()) - assert(len(result2.imageTitle) > 0) + assert (len(result2.imageTitle) > 0) print(result2.artist.PrintInfo()) - assert(len(result2.artist.artistToken) > 0) - assert("R-18" in result2.imageTags) + assert (len(result2.artist.artistToken) > 0) + assert ("R-18" in result2.imageTags) print(">> ugoira") (result3, page3) = b.getImagePage(60070169) print(result3.PrintInfo()) - assert(len(result3.imageTitle) > 0) + assert (len(result3.imageTitle) > 0) print(result3.artist.PrintInfo()) print(result3.ugoira_data) - assert(len(result3.artist.artistToken) > 0) - assert(result3.imageMode == 'ugoira_view') + assert (len(result3.artist.artistToken) > 0) + assert (result3.imageMode == 'ugoira_view') def testMember(): print("Test member mode") @@ -943,26 +1012,26 @@ def testMember(): (result3, page3) = b.getMemberPage( 1227869, page=1, bookmark=False, tags=None) print(result3.PrintInfo()) - assert(len(result3.artistToken) > 0) - assert(len(result3.imageList) > 0) + assert (len(result3.artistToken) > 0) + assert (len(result3.imageList) > 0) print(">>") (result4, page4) = b.getMemberPage( 1227869, page=2, bookmark=False, tags=None) print(result4.PrintInfo()) - assert(len(result4.artistToken) > 0) - assert(len(result4.imageList) > 0) + assert (len(result4.artistToken) > 0) + assert (len(result4.imageList) > 0) print(">>") (result5, page5) = b.getMemberPage( 4894, page=1, bookmark=False, tags=None) print(result5.PrintInfo()) - assert(len(result5.artistToken) > 0) - assert(len(result5.imageList) > 0) + assert (len(result5.artistToken) > 0) + assert (len(result5.imageList) > 0) print(">>") (result6, page6) = b.getMemberPage( 4894, page=3, bookmark=False, tags=None) print(result6.PrintInfo()) - assert(len(result6.artistToken) > 0) - assert(len(result6.imageList) > 0) + assert (len(result6.artistToken) > 0) + assert (len(result6.imageList) > 0) def testMemberBookmark(): print("Test member bookmarks mode") @@ -970,14 +1039,14 @@ def testMemberBookmark(): (result5, page5) = b.getMemberPage( 1227869, page=1, bookmark=True, tags=None) print(result5.PrintInfo()) - assert(len(result5.artistToken) > 0) - assert(len(result5.imageList) > 0) + assert (len(result5.artistToken) > 0) + assert (len(result5.imageList) > 0) print(">>") (result6, page6) = b.getMemberPage( 1227869, page=2, bookmark=True, tags=None) print(result6.PrintInfo()) - assert(len(result6.artistToken) > 0) - assert(len(result6.imageList) > 0) + assert (len(result6.artistToken) > 0) + assert (len(result6.imageList) > 0) print(">>") (result6, page6) = b.getMemberPage( 1227869, page=10, bookmark=True, tags=None) @@ -987,8 +1056,8 @@ def testMemberBookmark(): 1227869, page=12, bookmark=True, tags=None) if result6 is not None: print(result6.PrintInfo()) - assert(len(result6.artistToken) > 0) - assert(len(result6.imageList) == 0) + assert (len(result6.artistToken) > 0) + assert (len(result6.imageList) == 0) # testSearchTags() testImage() From 52d761a99c56c995432748ec812069a2d5ef3ba8 Mon Sep 17 00:00:00 2001 From: amatuerCoder <17560146+bluerthanever@users.noreply.github.com> Date: Tue, 28 Apr 2020 13:39:43 +0800 Subject: [PATCH 16/22] New option added Added new option 'cookieFanbox` --- PixivConfig.py | 1 + 1 file changed, 1 insertion(+) diff --git a/PixivConfig.py b/PixivConfig.py index d17e14ae..c9a742e7 100644 --- a/PixivConfig.py +++ b/PixivConfig.py @@ -111,6 +111,7 @@ class PixivConfig(): ConfigItem("Authentication", "username", ""), ConfigItem("Authentication", "password", ""), ConfigItem("Authentication", "cookie", ""), + ConfigItem("Authentication", "cookieFanbox", ""), ConfigItem("Authentication", "refresh_token", ""), ConfigItem("Pixiv", "numberOfPage", 0), From 38df85da0b4c116b3b568565a95ae400c56efd56 Mon Sep 17 00:00:00 2001 From: amatuerCoder <17560146+bluerthanever@users.noreply.github.com> Date: Tue, 28 Apr 2020 13:44:29 +0800 Subject: [PATCH 17/22] Added codes to check FANBOX login status in main() Also to load the `cookieFanbox` . But no errors would be raised currently --- PixivUtil2.py | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/PixivUtil2.py b/PixivUtil2.py index e02271a6..6f4ef696 100755 --- a/PixivUtil2.py +++ b/PixivUtil2.py @@ -1921,7 +1921,6 @@ def menu_fanbox_download_from_artist(op_is_valid, via, args): elif via == PixivModelFanbox.FanboxArtist.FOLLOWED: via_type = "followed" - __log__.info(f'Download FANBOX {via_type.capitalize()} Artists mode.') end_page = 0 @@ -1935,28 +1934,8 @@ def menu_fanbox_download_from_artist(op_is_valid, via, args): if not(fanbox_login): __log__.info("FANBOX login cookie string invalid, please update in config.ini") return - - artists = __br__.fanboxGetUsers(via) - - - - - - - - - - - - - - - - - - if len(artists) == 0: PixivHelper.print_and_log("info", f"No {via_type} artist!") return @@ -2537,6 +2516,9 @@ def main(): __log__.info(msg) result = doLogin(password, username) + fanbox_login = __br__.fanboxLoginUsingCookie() + if not (fanbox_login): + __log__.info("FANBOX login cookie string invalid, please update in config.ini") if result: np_is_valid, op_is_valid, selection = main_loop(ewd, op_is_valid, selection, np_is_valid, args) From 1f0a395e021fa308b5779d70554f7c0b55e1bcaa Mon Sep 17 00:00:00 2001 From: amatuerCoder <17560146+bluerthanever@users.noreply.github.com> Date: Tue, 28 Apr 2020 18:18:19 +0800 Subject: [PATCH 18/22] Minor change to enable creatorId passed as args --- PixivUtil2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PixivUtil2.py b/PixivUtil2.py index 6f4ef696..73284d81 100755 --- a/PixivUtil2.py +++ b/PixivUtil2.py @@ -2123,7 +2123,7 @@ def menu_fanbox_download_by_artist_or_creator_id(op_is_valid, args): id = '' if op_is_valid and len(args) > 0: - id = str(int(args[0])) + id = args[0] if len(args) > 1: end_page = args[1] else: From 26f39f563e009cabda55f9de9f045fbbb3ea5bbd Mon Sep 17 00:00:00 2001 From: amatuerCoder <17560146+bluerthanever@users.noreply.github.com> Date: Tue, 28 Apr 2020 18:33:30 +0800 Subject: [PATCH 19/22] Put request inside try except inside `fanboxLoginUsingCookie` In case error happens here and causes the whole util to fail --- PixivBrowserFactory.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/PixivBrowserFactory.py b/PixivBrowserFactory.py index 0375535f..f1cbe989 100644 --- a/PixivBrowserFactory.py +++ b/PixivBrowserFactory.py @@ -313,7 +313,7 @@ def fanboxLoginUsingCookie(self, login_cookie=None): login_cookie = self._config.cookieFanbox if len(login_cookie) > 0: - PixivHelper.print_and_log('info', 'Trying to log in with saved cookie') + PixivHelper.print_and_log('info', 'Trying to log in FANBOX with saved cookie') self.clearCookie() self._loadCookie(login_cookie, "fanbox.cc") @@ -321,10 +321,15 @@ def fanboxLoginUsingCookie(self, login_cookie=None): req.add_header('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8') req.add_header('Origin', 'https://www.fanbox.cc') req.add_header('User-Agent', self._config.useragent) - res = self.open_with_retry(req) - parsed = BeautifulSoup(res, features="html5lib").decode('utf-8') - PixivHelper.get_logger().info('Logging in, return url: %s', res.geturl()) - res.close() + try: + res = self.open_with_retry(req) + parsed = BeautifulSoup(res, features="html5lib").decode('utf-8') + PixivHelper.get_logger().info('Logging in, return url: %s', res.geturl()) + res.close() + except BaseException as e: + PixivHelper.get_logger().error('Error at fanboxLoginUsingCookie, please check cookieFanbox: ' + + format(sys.exc_info())) + return False result = False if '"user":{"isLoggedIn":true' in str(parsed): From 41af87cfd846099915a889566fa1db535ed50a86 Mon Sep 17 00:00:00 2001 From: amatuerCoder <17560146+bluerthanever@users.noreply.github.com> Date: Tue, 28 Apr 2020 18:38:46 +0800 Subject: [PATCH 20/22] Some print and indent correction? --- PixivBrowserFactory.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/PixivBrowserFactory.py b/PixivBrowserFactory.py index f1cbe989..ce13b9e0 100644 --- a/PixivBrowserFactory.py +++ b/PixivBrowserFactory.py @@ -268,6 +268,7 @@ def _getInitConfig(self, page): def loginUsingCookie(self, login_cookie=None): """ Log in to Pixiv using saved cookie, return True if success """ + result = False if login_cookie is None or len(login_cookie) == 0: login_cookie = self._config.cookie @@ -281,7 +282,6 @@ def loginUsingCookie(self, login_cookie=None): PixivHelper.get_logger().info('Logging in, return url: %s', res.geturl()) res.close() - result = False if "logout.php" in str(parsed): result = True if "pixiv.user.loggedIn = true" in str(parsed): @@ -303,11 +303,12 @@ def loginUsingCookie(self, login_cookie=None): PixivHelper.get_logger().info('Failed to log in using cookie') PixivHelper.print_and_log('info', 'Cookie already expired/invalid.') - del parsed + del parsed return result def fanboxLoginUsingCookie(self, login_cookie=None): """ Log in to Pixiv using saved cookie, return True if success """ + result = False if login_cookie is None or len(login_cookie) == 0: login_cookie = self._config.cookieFanbox @@ -324,14 +325,13 @@ def fanboxLoginUsingCookie(self, login_cookie=None): try: res = self.open_with_retry(req) parsed = BeautifulSoup(res, features="html5lib").decode('utf-8') - PixivHelper.get_logger().info('Logging in, return url: %s', res.geturl()) + PixivHelper.get_logger().info('Logging in with cookit to Fanbox, return url: %s', res.geturl()) res.close() except BaseException as e: PixivHelper.get_logger().error('Error at fanboxLoginUsingCookie, please check cookieFanbox: ' + format(sys.exc_info())) return False - result = False if '"user":{"isLoggedIn":true' in str(parsed): result = True @@ -342,7 +342,7 @@ def fanboxLoginUsingCookie(self, login_cookie=None): PixivHelper.get_logger().info('Failed to log in using cookie') PixivHelper.print_and_log('info', 'Cookie already expired/invalid.') - del parsed + del parsed return result def login(self, username, password): From 1d8337534b378927ae81f221c41a7acaeef5eec6 Mon Sep 17 00:00:00 2001 From: amatuerCoder <17560146+bluerthanever@users.noreply.github.com> Date: Tue, 28 Apr 2020 20:30:02 +0800 Subject: [PATCH 21/22] Changes to fit changes to classes in order to pass unit test --- test_PixivModel_fanbox.py | 242 +++++++++++++++++++++----------------- 1 file changed, 135 insertions(+), 107 deletions(-) diff --git a/test_PixivModel_fanbox.py b/test_PixivModel_fanbox.py index 21699b2e..a0022af8 100644 --- a/test_PixivModel_fanbox.py +++ b/test_PixivModel_fanbox.py @@ -7,7 +7,7 @@ import demjson import PixivHelper -from PixivModelFanbox import Fanbox, FanboxArtist, FanboxPost +from PixivModelFanbox import FanboxArtist, FanboxPost temp = PixivHelper.__re_manga_index @@ -21,90 +21,98 @@ def testFanboxSupportedArtist(self): reader = open('./test/Fanbox_supported_artist.json', 'r', encoding="utf-8") p = reader.read() reader.close() - result = Fanbox(p) + result = FanboxArtist.parseArtists(p) self.assertIsNotNone(result) - self.assertEqual(len(result.supportedArtist), 52) - self.assertTrue(4820 in result.supportedArtist) - self.assertTrue(11443 in result.supportedArtist) - self.assertTrue(226267 in result.supportedArtist) + self.assertEqual(len(result), 52) + self.assertTrue(4820 in [x.artistId for x in result]) + self.assertTrue(11443 in [x.artistId for x in result]) + self.assertTrue(226267 in [x.artistId for x in result]) def testFanboxArtistPosts(self): reader = open('./test/Fanbox_artist_posts.json', 'r', encoding="utf-8") p = reader.read() reader.close() - result = FanboxArtist(15521131, p) + + artist = FanboxArtist(15521131, "", "", None) + result = artist.parsePosts(p) + # result = FanboxArtist(15521131, p) self.assertIsNotNone(result) - self.assertEqual(result.artistId, 15521131) - self.assertTrue(result.hasNextPage) - self.assertTrue(len(result.nextUrl) > 0) - self.assertTrue(len(result.posts) > 0) + self.assertEqual(artist.artistId, 15521131) + self.assertTrue(artist.hasNextPage) + self.assertTrue(len(artist.nextUrl) > 0) + self.assertTrue(len(result) > 0) - for post in result.posts: + for post in result: self.assertFalse(post.is_restricted) # post-136761 image - self.assertEqual(result.posts[0].imageId, 136761) - self.assertTrue(len(result.posts[0].imageTitle) > 0) - self.assertTrue(len(result.posts[0].coverImageUrl) > 0) - self.assertEqual(result.posts[0].type, "image") - self.assertEqual(len(result.posts[0].images), 5) + self.assertEqual(result[0].imageId, 136761) + self.assertTrue(len(result[0].imageTitle) > 0) + self.assertTrue(len(result[0].coverImageUrl) > 0) + self.assertEqual(result[0].type, "image") + self.assertEqual(len(result[0].images), 5) # post-132919 text - self.assertEqual(result.posts[2].imageId, 132919) - self.assertTrue(len(result.posts[2].imageTitle) > 0) - self.assertIsNone(result.posts[2].coverImageUrl) - self.assertEqual(result.posts[2].type, "text") - self.assertEqual(len(result.posts[2].images), 0) + self.assertEqual(result[2].imageId, 132919) + self.assertTrue(len(result[2].imageTitle) > 0) + self.assertIsNone(result[2].coverImageUrl) + self.assertEqual(result[2].type, "text") + self.assertEqual(len(result[2].images), 0) # post-79695 image - self.assertEqual(result.posts[3].imageId, 79695) - self.assertTrue(len(result.posts[3].imageTitle) > 0) - self.assertIsNone(result.posts[3].coverImageUrl) - self.assertEqual(result.posts[3].type, "image") - self.assertEqual(len(result.posts[3].images), 4) + self.assertEqual(result[3].imageId, 79695) + self.assertTrue(len(result[3].imageTitle) > 0) + self.assertIsNone(result[3].coverImageUrl) + self.assertEqual(result[3].type, "image") + self.assertEqual(len(result[3].images), 4) def testFanboxArtistArticle(self): reader = open('./test/Fanbox_artist_posts_article.json', 'r', encoding="utf-8") p = reader.read() reader.close() - result = FanboxArtist(190026, p) + + artist = FanboxArtist(190026, "", "", None) + result = artist.parsePosts(p) + # result = FanboxArtist(190026, p) self.assertIsNotNone(result) - self.assertEqual(result.artistId, 190026) - self.assertTrue(result.hasNextPage) - self.assertTrue(len(result.nextUrl) > 0) - self.assertTrue(len(result.posts) > 0) + self.assertEqual(artist.artistId, 190026) + self.assertTrue(artist.hasNextPage) + self.assertTrue(len(artist.nextUrl) > 0) + self.assertTrue(len(result) > 0) # post-201946 article - self.assertEqual(result.posts[0].imageId, 201946) - self.assertTrue(len(result.posts[0].imageTitle) > 0) - self.assertIsNone(result.posts[0].coverImageUrl) - self.assertEqual(result.posts[0].type, "article") - self.assertEqual(len(result.posts[0].images), 5) - self.assertEqual(len(result.posts[0].body_text), 1292) + self.assertEqual(result[0].imageId, 201946) + self.assertTrue(len(result[0].imageTitle) > 0) + self.assertIsNone(result[0].coverImageUrl) + self.assertEqual(result[0].type, "article") + self.assertEqual(len(result[0].images), 5) + self.assertEqual(len(result[0].body_text), 1292) # result.posts[0].WriteInfo("./201946.txt") def testFanboxArtistArticleFileMap(self): reader = open('./test/creator_with_filemap.json', 'r', encoding="utf-8") p = reader.read() reader.close() - result = FanboxArtist(190026, p) + artist = FanboxArtist(190026, "", "", None) + result = artist.parsePosts(p) + # result = FanboxArtist(190026, p) self.assertIsNotNone(result) - self.assertEqual(result.artistId, 190026) - self.assertTrue(result.hasNextPage) - self.assertTrue(len(result.nextUrl) > 0) - self.assertTrue(len(result.posts) > 0) + self.assertEqual(artist.artistId, 190026) + self.assertTrue(artist.hasNextPage) + self.assertTrue(len(artist.nextUrl) > 0) + self.assertTrue(len(result) > 0) # post-201946 article - self.assertEqual(result.posts[0].imageId, 210980) - self.assertTrue(len(result.posts[0].imageTitle) > 0) - self.assertIsNone(result.posts[0].coverImageUrl) - self.assertEqual(result.posts[0].type, "article") - self.assertEqual(len(result.posts[0].images), 15) - self.assertEqual(len(result.posts[0].body_text), 3006) + self.assertEqual(result[0].imageId, 210980) + self.assertTrue(len(result[0].imageTitle) > 0) + self.assertIsNone(result[0].coverImageUrl) + self.assertEqual(result[0].type, "article") + self.assertEqual(len(result[0].images), 15) + self.assertEqual(len(result[0].body_text), 3006) # result.posts[0].WriteInfo("./210980.txt") @@ -112,21 +120,24 @@ def testFanboxArtistVideo(self): reader = open('./test/creator_posts_with_video.json', 'r', encoding="utf-8") p = reader.read() reader.close() - result = FanboxArtist(711048, p) + + artist = FanboxArtist(711048, "", "", None) + result = artist.parsePosts(p) + # result = FanboxArtist(711048, p) self.assertIsNotNone(result) - self.assertEqual(result.artistId, 711048) - self.assertTrue(result.hasNextPage) - self.assertTrue(len(result.nextUrl) > 0) - self.assertTrue(len(result.posts) > 0) + self.assertEqual(artist.artistId, 711048) + self.assertTrue(artist.hasNextPage) + self.assertTrue(len(artist.nextUrl) > 0) + self.assertTrue(len(result) > 0) # post-201946 article - self.assertEqual(result.posts[4].imageId, 330905) - self.assertTrue(len(result.posts[4].imageTitle) > 0) - self.assertEqual(result.posts[4].coverImageUrl, u'https://pixiv.pximg.net/c/1200x630_90_a2_g5/fanbox/public/images/post/330905/cover/3A2zPUg4s6iz17MM0Z45eWBj.jpeg') - self.assertEqual(result.posts[4].type, "video") - self.assertEqual(len(result.posts[4].images), 0) - self.assertEqual(len(result.posts[4].body_text), 99) + self.assertEqual(result[4].imageId, 330905) + self.assertTrue(len(result[4].imageTitle) > 0) + self.assertEqual(result[4].coverImageUrl, u'https://pixiv.pximg.net/c/1200x630_90_a2_g5/fanbox/public/images/post/330905/cover/3A2zPUg4s6iz17MM0Z45eWBj.jpeg') + self.assertEqual(result[4].type, "video") + self.assertEqual(len(result[4].images), 0) + self.assertEqual(len(result[4].body_text), 99) # result.posts[0].WriteInfo("./210980.txt") @@ -134,20 +145,23 @@ def testFanboxArtistArticleEmbedTwitter(self): reader = open('./test/creator_embedMap.json', 'r', encoding="utf-8") p = reader.read() reader.close() - result = FanboxArtist(68813, p) + + artist = FanboxArtist(68813, "", "", None) + result = artist.parsePosts(p) + # result = FanboxArtist(68813, p) self.assertIsNotNone(result) - self.assertEqual(result.artistId, 68813) - self.assertFalse(result.hasNextPage) - self.assertTrue(len(result.posts) > 0) + self.assertEqual(artist.artistId, 68813) + self.assertFalse(artist.hasNextPage) + self.assertTrue(len(result) > 0) # post-201946 article - self.assertEqual(result.posts[0].imageId, 285502) - self.assertTrue(len(result.posts[0].imageTitle) > 0) - self.assertEqual(result.posts[0].coverImageUrl, u'https://pixiv.pximg.net/c/1200x630_90_a2_g5/fanbox/public/images/post/285502/cover/orx9TCsiPFi5sgDdbvg4zwkX.jpeg') - self.assertEqual(result.posts[0].type, "article") - self.assertEqual(len(result.posts[0].images), 7) - self.assertEqual(len(result.posts[0].body_text), 3164) + self.assertEqual(result[0].imageId, 285502) + self.assertTrue(len(result[0].imageTitle) > 0) + self.assertEqual(result[0].coverImageUrl, u'https://pixiv.pximg.net/c/1200x630_90_a2_g5/fanbox/public/images/post/285502/cover/orx9TCsiPFi5sgDdbvg4zwkX.jpeg') + self.assertEqual(result[0].type, "article") + self.assertEqual(len(result[0].images), 7) + self.assertEqual(len(result[0].body_text), 3164) # result.posts[0].WriteInfo("./285502.txt") @@ -156,71 +170,83 @@ def testFanboxArtistPostsNextPage(self): reader = open('./test/Fanbox_artist_posts_nextpage.json', 'r', encoding="utf-8") p2 = reader.read() reader.close() - result = FanboxArtist(91029, p2) + + artist = FanboxArtist(91029, "", "", None) + result = artist.parsePosts(p2) + # result = FanboxArtist(91029, p2) self.assertIsNotNone(result) - self.assertEqual(result.artistId, 91029) - self.assertTrue(result.hasNextPage) - self.assertTrue(result.nextUrl is not None) - self.assertEqual(len(result.posts), 10) + self.assertEqual(artist.artistId, 91029) + self.assertTrue(artist.hasNextPage) + self.assertTrue(artist.nextUrl is not None) + self.assertEqual(len(result), 10) def testFanboxArtistPostsRestricted(self): reader = open('./test/Fanbox_artist_posts_restricted.json', 'r', encoding="utf-8") p = reader.read() reader.close() - result = FanboxArtist(15521131, p) + + artist = FanboxArtist(15521131, "", "", None) + result = artist.parsePosts(p) + # result = FanboxArtist(15521131, p) self.assertIsNotNone(result) - self.assertEqual(result.artistId, 15521131) - self.assertTrue(result.hasNextPage) - self.assertTrue(len(result.nextUrl) > 0) - self.assertEqual(len(result.posts), 10) + self.assertEqual(artist.artistId, 15521131) + self.assertTrue(artist.hasNextPage) + self.assertTrue(len(artist.nextUrl) > 0) + self.assertEqual(len(result), 10) - for post in result.posts: + for post in result: self.assertTrue(post.is_restricted) def testFanboxArtistPostsRestrictedNextPage(self): reader = open('./test/Fanbox_artist_posts_next_page_restricted.json', 'r', encoding="utf-8") p = reader.read() reader.close() - result = FanboxArtist(15521131, p) + artist = FanboxArtist(15521131, "", "", None) + result = artist.parsePosts(p) + # result = FanboxArtist(15521131, p) self.assertIsNotNone(result) - self.assertEqual(result.artistId, 15521131) - self.assertFalse(result.hasNextPage) - self.assertTrue(result.nextUrl is None) - self.assertEqual(len(result.posts), 6) + self.assertEqual(artist.artistId, 15521131) + self.assertFalse(artist.hasNextPage) + self.assertTrue(artist.nextUrl is None) + self.assertEqual(len(result), 6) - self.assertTrue(result.posts[0].is_restricted) - self.assertFalse(result.posts[1].is_restricted) + self.assertTrue(result[0].is_restricted) + self.assertFalse(result[1].is_restricted) def testFanboxOldApi(self): reader = open('./test/fanbox-posts-old-api.json', 'r', encoding="utf-8") p = reader.read() reader.close() - result = FanboxArtist(104409, p) + artist = FanboxArtist(104409, "", "", None) + result = artist.parsePosts(p) + # result = FanboxArtist(104409, p) self.assertIsNotNone(result) - self.assertEqual(len(result.posts), 2) - self.assertEqual(result.posts[0].imageId, 916) - self.assertEqual(len(result.posts[0].images), 8) - print(result.posts[0].images) - self.assertEqual(result.posts[1].imageId, 915) - self.assertEqual(len(result.posts[1].images), 1) - print(result.posts[1].images) + self.assertEqual(len(result), 2) + self.assertEqual(result[0].imageId, 916) + self.assertEqual(len(result[0].images), 8) + print(result[0].images) + self.assertEqual(result[1].imageId, 915) + self.assertEqual(len(result[1].images), 1) + print(result[1].images) def testFanboxNewApi(self): reader = open('./test/fanbox-posts-new-api.json', 'r', encoding="utf-8") p = reader.read() reader.close() - result = FanboxArtist(104409, p) + artist = FanboxArtist(104409, "", "", None) + result = artist.parsePosts(p) + # result = FanboxArtist(104409, p) self.assertIsNotNone(result) - self.assertEqual(len(result.posts), 10) - self.assertEqual(result.posts[0].imageId, 577968) - self.assertEqual(len(result.posts[0].images), 2) - self.assertEqual(result.posts[1].imageId, 535518) - self.assertEqual(len(result.posts[1].images), 2) + self.assertEqual(len(result), 10) + self.assertEqual(result[0].imageId, 577968) + self.assertEqual(len(result[0].images), 2) + self.assertEqual(result[1].imageId, 535518) + self.assertEqual(len(result[1].images), 2) def testFanboxNewApi2_MultiImages(self): reader = open('./test/Fanbox_post_with_multi_images.json', 'r', encoding="utf-8") @@ -254,10 +280,12 @@ def testFanboxFilename(self): reader = open('./test/Fanbox_artist_posts.json', 'r', encoding="utf-8") p = reader.read() reader.close() - result = FanboxArtist(15521131, p) + artist = FanboxArtist(15521131, "", "", None) + result = artist.parsePosts(p) + # result = FanboxArtist(15521131, p) self.assertIsNotNone(result) root_dir = os.path.abspath(os.path.curdir) - post = result.posts[0] + post = result[0] image_url = post.images[0] current_page = 0 fake_image_url = image_url.replace("{0}/".format(post.imageId), "{0}_p{1}_".format(post.imageId, current_page)) @@ -271,7 +299,7 @@ def simple_from_images(): filename = PixivHelper.make_filename(filename_format, post, - artistInfo=result, + artistInfo=artist, tagsSeparator=" ", tagsLimit=0, fileUrl=fake_image_url, @@ -288,7 +316,7 @@ def more_format(): filename = PixivHelper.make_filename(filename_format, post, - artistInfo=result, + artistInfo=artist, tagsSeparator=" ", tagsLimit=0, fileUrl=fake_image_url, @@ -307,7 +335,7 @@ def cover_more_format(): filename = PixivHelper.make_filename(filename_format, post, - artistInfo=result, + artistInfo=artist, tagsSeparator=" ", tagsLimit=0, fileUrl=fake_image_url, From 05456eaac49c2cc2d2f1758db0b4f8db6605c1a5 Mon Sep 17 00:00:00 2001 From: amatuerCoder <17560146+bluerthanever@users.noreply.github.com> Date: Tue, 28 Apr 2020 20:31:10 +0800 Subject: [PATCH 22/22] Added `creatorId` for the unit test --- test/Fanbox_supported_artist.json | 1461 ++++++++++++++--------------- 1 file changed, 730 insertions(+), 731 deletions(-) diff --git a/test/Fanbox_supported_artist.json b/test/Fanbox_supported_artist.json index df80d261..cba8db16 100644 --- a/test/Fanbox_supported_artist.json +++ b/test/Fanbox_supported_artist.json @@ -1,732 +1,731 @@ { - "body": [ - { - "id": "10930", - "title": "「好きな作家さんの同人誌をコレで買いなさい」または「画材の足しにして」", - "fee": 500, - "description": "月に一冊同人誌をおごってくれる素敵な人(← 好き♪\r\nまたは画材代を支援してお絵かきを応援してくれる方(← 好き♪\r\n限定公開のHな絵が見れる。", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/10930/cover/PZ07QSsQSzOe4sJ0y56GEKNK.jpeg", - "user": { - "userId": "4820", - "name": "猫耳 花音", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/4820/icon/AvB7gFWmDvmgaeDFB48XHXfA.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "50037", - "title": "500円", - "fee": 500, - "description": "投稿から6か月たったものはこちらに移動します。それとたまに500円限定の差分イラストを上げます。", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/50037/cover/QfOXflbPPvxdR4E59GMnvLG3.jpeg", - "user": { - "userId": "7968", - "name": "伊藤達哉", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/7968/icon/3TXFEP8ELMnaLhkS60eqEDH6.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "38114", - "title": "ラムネプラン", - "fee": 100, - "description": "双木こじろにブドウ糖90%のラムネを食べさせるプランです。\r\nペンタブの芯や絵の資料の資金になることもあります。", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/38114/cover/8AE1M6JTYutnFLfUeRsE0ofL.jpeg", - "user": { - "userId": "11443", - "name": "双木こじろ", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/11443/icon/quTymdIRXnfjFKEhlDVUM9ba.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "15330", - "title": "調教支援:ピンクローター", - "fee": 250, - "description": "あかいしの調教にピンクローターの力が加わり\nあかいしの戦闘力がちょっとだけ高まります。", - "coverImageUrl": null, - "user": { - "userId": "91029", - "name": "あかいししろいし", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/91029/icon/88cadMWRtY4Rak2TYCO2nxra.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "23834", - "title": "ドリンクタイム", - "fee": 200, - "description": "赤い牛とか怪物力を買います。", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/23834/cover/VLnjWLNnz8n3pFfm6YyZPTu1.jpeg", - "user": { - "userId": "151050", - "name": "わっちー", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/151050/icon/530GbYo4o0fCjt6tiUHT6RCt.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "22444", - "title": "サポートA", - "fee": 200, - "description": "通常投稿作品のR18バージョンや高解像度版、長い動画(うごくイラスト)が閲覧できます。支援金はソフトウエア購入、使用代金に使わせていただきます。\r\nYou can view R18 version of the contribution work, high resolution version, long video (Ugoku_illustrations). I will use your donations for software purchase and usage fee.", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/22444/cover/CXjsJ3oeXNFcy2iYlGXhm26N.jpeg", - "user": { - "userId": "195364", - "name": "tooo", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/195364/icon/nbyDJpVBz9jMsBgN9lafj0eP.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "7329", - "title": "ストパンエロ絵置き場(ダウンロード用)", - "fee": 250, - "description": "趣味で描いてPIXIVに上げたエロ絵や、その差分を投稿する場所。無料のところとほとんど変わらないから特に有料プランに入る必要はないけど、ダウンロードしやすいよう差分ごとファイルにまとめるよ(断面図や拡大図付き差分ファイル追加するかも)。\n後、以前描いたエロ絵のリメイクと差分をのっけるかも。最低月1更新。", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/7329/cover/n91lwh6eAjcV7w2MRah4Nmao.jpeg", - "user": { - "userId": "226267", - "name": "髭侍", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/226267/icon/sxsTDCwrUQwBj6uRuWKMAzj4.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "57409", - "title": "もっとお絵描き頑張ってね☆プラン", - "fee": 500, - "description": "特になにもありませんが、日頃のお絵描きをもっと頑張る気力が更に沸きます", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/57409/cover/cI6qBbgItDvHrHPOyx66gpS0.jpeg", - "user": { - "userId": "376735", - "name": "るいるい", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/376735/icon/GGTIP2kFojXxeHJE7cEcRva6.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "19421", - "title": " Just a pay some Rewards page,No payment content", - "fee": 100, - "description": "何もない!!!!!!!!!!!!!!!!!!!!\r\n只是一個打賞的頁面。\r\n沒有任何付費内容\r\nJust a paying some Tips/Reward's page.\r\nHas no payment content\r\nただ、いくつかのヒントのページを支払います。(machine)\r\n\r\n", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/19421/cover/Dr7lHtCssipQnCNLNFIMIgAz.jpeg", - "user": { - "userId": "634336", - "name": "喵行燈[pass]", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/634336/icon/YLe6B5XLbiWtXHiWVzn0WkJx.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "64153", - "title": "ラーメンプラン", - "fee": 500, - "description": "みかぐらがお昼ご飯にラーメンを食べられます。\npixivに投稿したイラストの高解像度版や未公開差分、限定公開イラストなど", - "coverImageUrl": null, - "user": { - "userId": "642151", - "name": "みかぐら", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/642151/icon/fpHzu25VUYF3y5y5kFBjMdjV.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "25985", - "title": "やる気スイッチ", - "fee": 300, - "description": "落書き・ラフ・高画質版が閲覧できます", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/25985/cover/jMlX9E92sLdTUyFjS9F30tYD.jpeg", - "user": { - "userId": "1030278", - "name": "とりあっとぐぬぬ", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/1030278/icon/lhS7qcfvYdo217eQunGlejQ1.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "43196", - "title": "エキスパートエディション", - "fee": 500, - "description": "マンガ形式の差分イラストなどを考えています。\r\n制作者は煮干しつけ麺が食べられます。", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/43196/cover/Xil20skVpr2sWsWVzZ1alOtt.jpeg", - "user": { - "userId": "1046936", - "name": "アブトマト", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/1046936/icon/0iBaWBh8JeNE7NHtXJnVnU3R.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "29051", - "title": "18禁っぽいの", - "fee": 200, - "description": "スク水娘を描く上でスク水をまだ着ていないスク水少女とか見れます。", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/29051/cover/njuKDrsR3Ls7I9c4hoSHj1ZB.jpeg", - "user": { - "userId": "1094478", - "name": "ろひつか", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/1094478/icon/PQCsaGKcNV59I2ilU3sFCIZY.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "77592", - "title": "500", - "fee": 500, - "description": "10月はPatreonの新作を中心に過去作品など投稿したいと思います。スケッチ、完成絵、モノクロなど含まれます。\nIn Octorber we will post new works mainly with past works of Patreon. Includes sketches, full shade, and monochrome.\n", - "coverImageUrl": null, - "user": { - "userId": "1131293", - "name": "Tenebre Publisher (CoCo)", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/1131293/icon/9eY25HnINd09fbu9biGhbsTI.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "32339", - "title": "Plan 5", - "fee": 500, - "description": "PIXIV公開画の差分画や、支援者様限定画など全てをご覧になれます。", - "coverImageUrl": null, - "user": { - "userId": "1149958", - "name": "カミマキ ツカミ", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/1149958/icon/6waWjowBUvtCKTEXti9Qw1Ql.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "26356", - "title": "えびみそプラン", - "fee": 500, - "description": "18禁や特殊な性癖のイラストや短編の漫画を不定期に掲載していきたいです。ご支援いただけたらうれしいです。※ファンティアと投稿内容は同じです。", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/26356/cover/ikiDiVF4usteRczoPkeH6Bn3.jpeg", - "user": { - "userId": "1276620", - "name": "いしみそ", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/1276620/icon/wqNjXH7IaRvWLxjcbmSuTc1h.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "51601", - "title": "実績解除:戦友", - "fee": 150, - "description": "ベア猫の作戦行動を援護した。", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/51601/cover/jg1YBTxD9cP3ruDvItU2uSzl.jpeg", - "user": { - "userId": "1368840", - "name": "ベア猫(遺伝子組み換えでない)", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/1368840/icon/TcKSISCf2q5TTASuxlBOmmnn.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "79834", - "title": "投げ銭(小3)", - "fee": 300, - "description": "女子小学3年生向けのプランです。\n山田がお弁当を1つ食べられます。", - "coverImageUrl": null, - "user": { - "userId": "1769484", - "name": "山田の性活が第一", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/1769484/icon/IXZrfHhSuDIGVTDihj0DLqYu.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "71632", - "title": "圧力", - "fee": 100, - "description": "創作活動を促す圧力がかかります。\r\n稀にイラストの原寸公開やラフ投稿があるかもしれません。", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/71632/cover/l2SLNfM0ZvdwsCIHKhQQal6m.jpeg", - "user": { - "userId": "1831980", - "name": "pxkanif + huyusilver", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/1831980/icon/0iYlM8p0zO477c7Tj2wW91ih.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "54568", - "title": "一筆に紅茶一杯のプラン", - "fee": 498, - "description": "エッチな絵の原動力に、一杯の紅茶を支援頂きたいプランです\npixivに投稿している絵の高解像度版や差分などを提供させていただきます\nあなたのご支援とお心に大変感謝いたします ", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/54568/cover/f2v7DFOuH0pDEM94Kqa5wVLL.jpeg", - "user": { - "userId": "2221925", - "name": "真綿", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/2221925/icon/TnrEk24ySSCTbgZxoiBCvrMU.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "62985", - "title": "300", - "fee": 300, - "description": "", - "coverImageUrl": null, - "user": { - "userId": "2408551", - "name": "ぽこにゃん", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/2408551/icon/SkhA1alyqbhMNs07daiHAbdc.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "51378", - "title": "毎月緊縛プラン", - "fee": 200, - "description": "(毎月更新しましたら古いほうを削除する予定です。お早めに保存して頂きますようお願いいたします。)\r\nFanbox始めました!!試運転として、月額200円で、毎月独占でお縛りのオリジナル画像を一枚上げてきます!基本、話題な女の子(アニメ、ソシャゲー中心)で書こうと思います!どうぞよろしくお願いします!!", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/51378/cover/kKIN01S5y3jpV3zPyXWFi3e9.jpeg", - "user": { - "userId": "3452804", - "name": "HaneRu", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/3452804/icon/b4AWDuOCKPCsgxsdYYURLlSL.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "78034", - "title": "エネルギー注入プラン Patreon 300", - "fee": 300, - "description": "支援が多いほど早く描けます~(わがまま \r\n不定期で未公開ラフを支援者だけに公開します", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/78034/cover/uaBHEnYIhTyAKaDBvVEN0ZAx.jpeg", - "user": { - "userId": "4417141", - "name": "クロニ", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/4417141/icon/hnNvWHyfFOXYGPl1vFTm2wKu.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "87526", - "title": "普通に応援", - "fee": 600, - "description": "普段のラフやイラストを観ることができます。\n気軽に応援プランより、月にもう一枚のスペシャルイラストを観ることがきるようになります。\nショットストーリーの1ー2ページを観ることができます。", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/87526/cover/6R5J5c5FohoeZ3BZoAHUZOyv.jpeg", - "user": { - "userId": "4704179", - "name": "万天気像", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/4704179/icon/1HlyC5Axr3StL3XQUHZvpQDj.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "70032", - "title": "PSD元ファイルを取得", - "fee": 750, - "description": "可以得到作者本月新作的PSD原文件\n作者の今月新作のPSDファイルを入手できます。\nget the original PSD file that the author wrote this month.", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/70032/cover/WhaKe10PO8BWn7ploP688BW4.jpeg", - "user": { - "userId": "5165814", - "name": "神奇火鸡(Wonder Turkey)", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/5165814/icon/NzsxKESsUOsH2gkEn8kxCFl8.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "14795", - "title": "見放題プラン", - "fee": 300, - "description": "見放題プランです。", - "coverImageUrl": null, - "user": { - "userId": "5664611", - "name": "Θωθ", - "iconUrl": null - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "95835", - "title": "Previews of drawing in progress.", - "fee": 150, - "description": "Sketches with news and ideas of drawings I am currently working on. \r\nJist like in patreon. :3\r\nMight also post colored progress reports of larger projects. ^^", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/95835/cover/s0tOyvCCcNXA2V24lAAwPcg7.jpeg", - "user": { - "userId": "5944252", - "name": "Hotel01", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/5944252/icon/R6Gu6354EmTMQO6NP97eDVdC.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "59121", - "title": "毎月一枚r18同人プラン", - "fee": 200, - "description": "月末で一枚以上のr18同人イラストを限定公開します!テーマは今期アニメや、ゲーム等、ネトラレ、陵辱等もあります!", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/59121/cover/kf5nD3JrXkjABocaaDgY9Mkb.jpeg", - "user": { - "userId": "6049901", - "name": "鬼針草", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/6049901/icon/SHqjwGwp3G3Ya7S3k6o4wfTf.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "39309", - "title": "プラン500", - "fee": 500, - "description": "高解像度イラストを閲覧することができます。\r\nYou can see high resolution images.", - "coverImageUrl": null, - "user": { - "userId": "6357272", - "name": "ろるゐ紳士", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/6357272/icon/XKIw7maSfZV8xAvPHbvL6lnO.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "86864", - "title": "600円プラン", - "fee": 600, - "description": "いつも通り、ファンボックスがpatreonみたいに使います。\n不安定ですが、毎月1差分絵セット保証します、2セットが頑張ります。", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/86864/cover/eOEV1YvAzynCfL1aYj08ptOG.jpeg", - "user": { - "userId": "6610818", - "name": "サーモン", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/6610818/icon/uXK2ehrKEL8prJwLx5qebuXF.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "93775", - "title": "It helps me concentrate on my work.", - "fee": 200, - "description": "I will upload a private picture for the fan box once a month.", - "coverImageUrl": null, - "user": { - "userId": "10414967", - "name": "kkta", - "iconUrl": null - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "13242", - "title": "スタンダートプラン", - "fee": 500, - "description": "えっちな絵を見たい方のスタンダートなプランです。\r\nTwitterにはスケベすぎて載せられない支援者限定イラストやエロ差分、文字なし差分などを定期的に載せます。", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/13242/cover/KqDzOeEmxS2pa78DiuHEGkbB.jpeg", - "user": { - "userId": "10594960", - "name": "弥猫うた", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/10594960/icon/xXk4UqYlTXuHaFN6QEdWWkE2.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "49868", - "title": "お賽銭箱", - "fee": 200, - "description": "pixiv、ツイッター等で上げてる絵の裸verなど上げたいと思います~", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/49868/cover/mZnXCfifwWTrsCxB7W92WGqk.jpeg", - "user": { - "userId": "11229342", - "name": "サインこす", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/11229342/icon/AIfY06L4CPM2WiadhfMlHViT.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "72698", - "title": "喜欢的话给个赞助吧…", - "fee": 200, - "description": "", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/72698/cover/clb46Zb6PrwAHjh17fOHMN3D.jpeg", - "user": { - "userId": "12016484", - "name": "HLL.ALSG99", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/12016484/icon/EaoIZnz0fP6xxkCQ4siTyd4o.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "59173", - "title": "落書き", - "fee": 500, - "description": "ここで私の普段の落書きと受付のイラストの落書きを発表する。落書きほとんどは人体緊縛の姿についての絵です。そしてみんなと一緒にいい場面を構想したいと思います。\r\n我會在這裡發佈一些平時構想的草稿以及約稿的草圖,草圖大部分情況下是突然想到的能用在捆綁里的人體結構 我很樂意和大家一起想後面的構思之類的東西_(:з」∠)_", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/59173/cover/zjFQY2WTAs0bR6MNklWYXBlS.jpeg", - "user": { - "userId": "12063067", - "name": "臣訾", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/12063067/icon/ciyGaxmudPK7wsrqY0kDcwRj.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "51253", - "title": "毎月緊縛プラン", - "fee": 200, - "description": "Fanbox始めました!!試運転として、月額200円で、毎月独占でお縛りのオリジナル画像を一枚上げてきます!基本、話題な女の子(アニメ、ソシャゲー中心)で書こうと思います!どうぞよろしくお願いします!!", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/51253/cover/RwTIAoJsAnIWJWNYNAkhEfBg.jpeg", - "user": { - "userId": "13379747", - "name": "ひみつ", - "iconUrl": null - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "94525", - "title": "水月的Fanbox计划", - "fee": 300, - "description": "水月的色图细化计划,应该每月能更新一次吧", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/94525/cover/A0AZiixXTt6NgsQw7NGQTDDF.jpeg", - "user": { - "userId": "13524241", - "name": "水月岩雲ー仕事募集中", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/13524241/icon/8XcQMzKzVLJlydmb1afNBg2w.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "61259", - "title": "レベル3:限定イラスト", - "fee": 1000, - "description": "イラストとマンガ以上 未公開イラスト1枚(縛りと拘束),限定イラストは差分もありよ", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/61259/cover/JQRm1mgrOe5U8xIqPpIExkmE.jpeg", - "user": { - "userId": "17305623", - "name": "雪ノ嵐&异端丶", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/17305623/icon/86rMpiapcC8E7wNGhz8KHghK.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "2399", - "title": "資金援助プラン", - "fee": 500, - "description": "入ってくれる方は神様仏様マミさん\n支援頂いたお金は画材購入、環境整備、同人活動の為に使わせて頂きます。\n現状では頒布物の内容の一部や原稿進捗(全体図、線画など)を投稿して行きます\n何かしらの御礼的コンテンツを考え中(差分絵とか", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/2399/cover/SwWbY8hVcTq2R1LIcRVPonYi.jpeg", - "user": { - "userId": "17813543", - "name": "やんまーみ", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/17813543/icon/UYKInBg5X3ExpjAwmcnYZAOG.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "87716", - "title": "とどおが頑張って描こうとするプラン", - "fee": 500, - "description": "100円のプランの内容に加え、過去にpixivで上げた絵の高解像度版やボツ絵などが見れます。\r\nまた、とどおが支援に感謝して無理をしない範囲でがんばろうとします。\r\n", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/87716/cover/fL5I25T8uid0HGiVYH6QjqHN.jpeg", - "user": { - "userId": "18961759", - "name": "とどお", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/18961759/icon/7uqGq9JlT0XjdHSRiBAfs1Wi.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "20046", - "title": "イラスト気に入ったよ的な感じで・・・", - "fee": 300, - "description": "もしもpixiv等でのイラストで気に入った方がいましたら、投げ銭みたいな感じで支援していただけると嬉しいです。特に大きな見返りがあるわけではないのでご注意ください。", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/20046/cover/fe1nD773JNAQld3Csw3LurpS.jpeg", - "user": { - "userId": "19116803", - "name": "かなとふ", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/19116803/icon/ph0eU9nf0g2RiTyZvo0ZANoJ.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "95894", - "title": "おべんとプラン", - "fee": 500, - "description": "100円のプランに加え、pixivなどで公開したイラストの高解像度版や限定おまけ差分をご覧いただけます。\n\nご支援金は創作活動費として使用されます。", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/95894/cover/strRAGNfkBdtU6kFOeIcQJNY.jpeg", - "user": { - "userId": "22553630", - "name": "桜屋敷とんこつ", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/22553630/icon/b6XKPWx6nZabxwPgFHlpXrC0.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "79440", - "title": "500円プラン", - "fee": 500, - "description": "I will post illustrations for only supporters every month\r\n毎月サポーターのみにイラストを投稿します", - "coverImageUrl": null, - "user": { - "userId": "23311297", - "name": "nekomom", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/23311297/icon/NZTaY6CgR8zHuwywXHfIURqC.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "85396", - "title": "ラフぽいぽい祭り", - "fee": 100, - "description": "ラフを載せまくる。以上です!よろしくお願いします!\r\n1週間に1~2回の更新を目標に頑張ります(。ᵕᴗᵕ。)\r\n", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/85396/cover/H0IqMyCwbwUQOLe6kICNYZA2.jpeg", - "user": { - "userId": "23626451", - "name": "にゆん", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/23626451/icon/c0X6sE0XAWId3KVWQqMhCgar.jpeg" - }, - "hasAdultContent": false, - "paymentMethod": "gmo_card" - }, - { - "id": "84482", - "title": "通常プラン", - "fee": 880, - "description": "現状のところプランは一つだけですので、すべての作品を閲覧することができます。", - "coverImageUrl": null, - "user": { - "userId": "23652509", - "name": "識林ほりう", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/23652509/icon/r4zsbbGehIK6QLuM4dO3iWkJ.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "67599", - "title": "スタンダードプラン", - "fee": 400, - "description": "「継続的に支援したい!」という方向けのスタンダードなプランです。\n\nThis is a standard plan.\nFor those who want to support me continuously.", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/67599/cover/yf2vSvP7JDsigIdHjoFCpevH.jpeg", - "user": { - "userId": "25900946", - "name": "ogyadya", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/25900946/icon/DlFEK7OYdUzSvSdle9EBTEA9.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "57468", - "title": "月刊誌", - "fee": 500, - "description": "お賽銭的なヤツ", - "coverImageUrl": null, - "user": { - "userId": "27569300", - "name": "仁外", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/27569300/icon/A85RkerebOnRg3B0PaLE1c63.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "51441", - "title": "うなぎ丼", - "fee": 600, - "description": "公開しない差分の閲覧が可能になります。その上、毎月独占で拘束の画像を一枚上げてきます。", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/51441/cover/qtSz3auZWlThSBv5TFFJcqQY.jpeg", - "user": { - "userId": "30716447", - "name": "ginklaga", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/30716447/icon/t1uxsE6MApu2Lkn2azwnJvHp.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "48637", - "title": "+他作品", - "fee": 1000, - "description": "「γ-ガンマ-」以外の作品のイラストも観覧出来る様になります。\r\nこちらのプランで「γ-ガンマ-」のイラストも全て観覧出来ます。", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/48637/cover/zf7Ia0StIIv9eoDf2Y9H8xGZ.jpeg", - "user": { - "userId": "35438167", - "name": "γ", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/35438167/icon/X7POgmP3psmeubiiglej7qCV.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "82919", - "title": "fan (+3pts/month)", - "fee": 300, - "description": "being a fan you get to see them exclusives c;", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/82919/cover/rgoQIUmWblcjGcjXmSuDu0zg.jpeg", - "user": { - "userId": "40169508", - "name": "crumbles", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/40169508/icon/buR2OoPLjCm1RagEvyHYatyC.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "93759", - "title": "100円TIPS", - "fee": 100, - "description": "full-size picture and the process of my works only! I would be very thankful!\nこちらは原サイズ画像と創作流れしかありません\n只有原尺寸圖與我的感謝(´∀`)", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/93759/cover/BsXC8FH3Wxi3VfVV2OYa8tl5.jpeg", - "user": { - "userId": "42936714", - "name": "BLAM", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/42936714/icon/dOnO2ZaKTk91VORpBchIYAEc.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - }, - { - "id": "96263", - "title": "投げ銭プラン", - "fee": 100, - "description": "投げ銭のプランです。ちょっとしたおまけ等が見れる予定です。プランに興味がなくても、フォローやコメント、コミッション等していただけると励みになります。", - "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/96263/cover/UEN4nNSXxVictnIr2NqM3z2U.jpeg", - "user": { - "userId": "44433394", - "name": "Pita17", - "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/44433394/icon/MjeSA6YIUQHHCAtcAlMAfpht.jpeg" - }, - "hasAdultContent": true, - "paymentMethod": "gmo_card" - } - ] -} \ No newline at end of file + "body": [{ + "id": "10930", + "title": "「好きな作家さんの同人誌をコレで買いなさい」または「画材の足しにして」", + "fee": 500, + "description": "月に一冊同人誌をおごってくれる素敵な人(← 好き♪\r\nまたは画材代を支援してお絵かきを応援してくれる方(← 好き♪\r\n限定公開のHな絵が見れる。", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/10930/cover/PZ07QSsQSzOe4sJ0y56GEKNK.jpeg", + "user": { + "userId": "4820", + "name": "猫耳 花音", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/4820/icon/AvB7gFWmDvmgaeDFB48XHXfA.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "50037", + "title": "500円", + "fee": 500, + "description": "投稿から6か月たったものはこちらに移動します。それとたまに500円限定の差分イラストを上げます。", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/50037/cover/QfOXflbPPvxdR4E59GMnvLG3.jpeg", + "user": { + "userId": "7968", + "name": "伊藤達哉", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/7968/icon/3TXFEP8ELMnaLhkS60eqEDH6.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "38114", + "title": "ラムネプラン", + "fee": 100, + "description": "双木こじろにブドウ糖90%のラムネを食べさせるプランです。\r\nペンタブの芯や絵の資料の資金になることもあります。", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/38114/cover/8AE1M6JTYutnFLfUeRsE0ofL.jpeg", + "user": { + "userId": "11443", + "name": "双木こじろ", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/11443/icon/quTymdIRXnfjFKEhlDVUM9ba.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "15330", + "title": "調教支援:ピンクローター", + "fee": 250, + "description": "あかいしの調教にピンクローターの力が加わり\nあかいしの戦闘力がちょっとだけ高まります。", + "coverImageUrl": null, + "user": { + "userId": "91029", + "name": "あかいししろいし", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/91029/icon/88cadMWRtY4Rak2TYCO2nxra.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "23834", + "title": "ドリンクタイム", + "fee": 200, + "description": "赤い牛とか怪物力を買います。", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/23834/cover/VLnjWLNnz8n3pFfm6YyZPTu1.jpeg", + "user": { + "userId": "151050", + "name": "わっちー", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/151050/icon/530GbYo4o0fCjt6tiUHT6RCt.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "22444", + "title": "サポートA", + "fee": 200, + "description": "通常投稿作品のR18バージョンや高解像度版、長い動画(うごくイラスト)が閲覧できます。支援金はソフトウエア購入、使用代金に使わせていただきます。\r\nYou can view R18 version of the contribution work, high resolution version, long video (Ugoku_illustrations). I will use your donations for software purchase and usage fee.", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/22444/cover/CXjsJ3oeXNFcy2iYlGXhm26N.jpeg", + "user": { + "userId": "195364", + "name": "tooo", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/195364/icon/nbyDJpVBz9jMsBgN9lafj0eP.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "7329", + "title": "ストパンエロ絵置き場(ダウンロード用)", + "fee": 250, + "description": "趣味で描いてPIXIVに上げたエロ絵や、その差分を投稿する場所。無料のところとほとんど変わらないから特に有料プランに入る必要はないけど、ダウンロードしやすいよう差分ごとファイルにまとめるよ(断面図や拡大図付き差分ファイル追加するかも)。\n後、以前描いたエロ絵のリメイクと差分をのっけるかも。最低月1更新。", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/7329/cover/n91lwh6eAjcV7w2MRah4Nmao.jpeg", + "user": { + "userId": "226267", + "name": "髭侍", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/226267/icon/sxsTDCwrUQwBj6uRuWKMAzj4.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "57409", + "title": "もっとお絵描き頑張ってね☆プラン", + "fee": 500, + "description": "特になにもありませんが、日頃のお絵描きをもっと頑張る気力が更に沸きます", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/57409/cover/cI6qBbgItDvHrHPOyx66gpS0.jpeg", + "user": { + "userId": "376735", + "name": "るいるい", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/376735/icon/GGTIP2kFojXxeHJE7cEcRva6.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "19421", + "title": " Just a pay some Rewards page,No payment content", + "fee": 100, + "description": "何もない!!!!!!!!!!!!!!!!!!!!\r\n只是一個打賞的頁面。\r\n沒有任何付費内容\r\nJust a paying some Tips/Reward's page.\r\nHas no payment content\r\nただ、いくつかのヒントのページを支払います。(machine)\r\n\r\n", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/19421/cover/Dr7lHtCssipQnCNLNFIMIgAz.jpeg", + "user": { + "userId": "634336", + "name": "喵行燈[pass]", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/634336/icon/YLe6B5XLbiWtXHiWVzn0WkJx.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "64153", + "title": "ラーメンプラン", + "fee": 500, + "description": "みかぐらがお昼ご飯にラーメンを食べられます。\npixivに投稿したイラストの高解像度版や未公開差分、限定公開イラストなど", + "coverImageUrl": null, + "user": { + "userId": "642151", + "name": "みかぐら", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/642151/icon/fpHzu25VUYF3y5y5kFBjMdjV.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "25985", + "title": "やる気スイッチ", + "fee": 300, + "description": "落書き・ラフ・高画質版が閲覧できます", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/25985/cover/jMlX9E92sLdTUyFjS9F30tYD.jpeg", + "user": { + "userId": "1030278", + "name": "とりあっとぐぬぬ", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/1030278/icon/lhS7qcfvYdo217eQunGlejQ1.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "43196", + "title": "エキスパートエディション", + "fee": 500, + "description": "マンガ形式の差分イラストなどを考えています。\r\n制作者は煮干しつけ麺が食べられます。", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/43196/cover/Xil20skVpr2sWsWVzZ1alOtt.jpeg", + "user": { + "userId": "1046936", + "name": "アブトマト", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/1046936/icon/0iBaWBh8JeNE7NHtXJnVnU3R.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "29051", + "title": "18禁っぽいの", + "fee": 200, + "description": "スク水娘を描く上でスク水をまだ着ていないスク水少女とか見れます。", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/29051/cover/njuKDrsR3Ls7I9c4hoSHj1ZB.jpeg", + "user": { + "userId": "1094478", + "name": "ろひつか", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/1094478/icon/PQCsaGKcNV59I2ilU3sFCIZY.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "77592", + "title": "500", + "fee": 500, + "description": "10月はPatreonの新作を中心に過去作品など投稿したいと思います。スケッチ、完成絵、モノクロなど含まれます。\nIn Octorber we will post new works mainly with past works of Patreon. Includes sketches, full shade, and monochrome.\n", + "coverImageUrl": null, + "user": { + "userId": "1131293", + "name": "Tenebre Publisher (CoCo)", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/1131293/icon/9eY25HnINd09fbu9biGhbsTI.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "32339", + "title": "Plan 5", + "fee": 500, + "description": "PIXIV公開画の差分画や、支援者様限定画など全てをご覧になれます。", + "coverImageUrl": null, + "user": { + "userId": "1149958", + "name": "カミマキ ツカミ", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/1149958/icon/6waWjowBUvtCKTEXti9Qw1Ql.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "26356", + "title": "えびみそプラン", + "fee": 500, + "description": "18禁や特殊な性癖のイラストや短編の漫画を不定期に掲載していきたいです。ご支援いただけたらうれしいです。※ファンティアと投稿内容は同じです。", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/26356/cover/ikiDiVF4usteRczoPkeH6Bn3.jpeg", + "user": { + "userId": "1276620", + "name": "いしみそ", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/1276620/icon/wqNjXH7IaRvWLxjcbmSuTc1h.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "51601", + "title": "実績解除:戦友", + "fee": 150, + "description": "ベア猫の作戦行動を援護した。", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/51601/cover/jg1YBTxD9cP3ruDvItU2uSzl.jpeg", + "user": { + "userId": "1368840", + "name": "ベア猫(遺伝子組み換えでない)", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/1368840/icon/TcKSISCf2q5TTASuxlBOmmnn.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "79834", + "title": "投げ銭(小3)", + "fee": 300, + "description": "女子小学3年生向けのプランです。\n山田がお弁当を1つ食べられます。", + "coverImageUrl": null, + "user": { + "userId": "1769484", + "name": "山田の性活が第一", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/1769484/icon/IXZrfHhSuDIGVTDihj0DLqYu.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "71632", + "title": "圧力", + "fee": 100, + "description": "創作活動を促す圧力がかかります。\r\n稀にイラストの原寸公開やラフ投稿があるかもしれません。", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/71632/cover/l2SLNfM0ZvdwsCIHKhQQal6m.jpeg", + "user": { + "userId": "1831980", + "name": "pxkanif + huyusilver", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/1831980/icon/0iYlM8p0zO477c7Tj2wW91ih.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "54568", + "title": "一筆に紅茶一杯のプラン", + "fee": 498, + "description": "エッチな絵の原動力に、一杯の紅茶を支援頂きたいプランです\npixivに投稿している絵の高解像度版や差分などを提供させていただきます\nあなたのご支援とお心に大変感謝いたします ", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/54568/cover/f2v7DFOuH0pDEM94Kqa5wVLL.jpeg", + "user": { + "userId": "2221925", + "name": "真綿", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/2221925/icon/TnrEk24ySSCTbgZxoiBCvrMU.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "62985", + "title": "300", + "fee": 300, + "description": "", + "coverImageUrl": null, + "user": { + "userId": "2408551", + "name": "ぽこにゃん", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/2408551/icon/SkhA1alyqbhMNs07daiHAbdc.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "51378", + "title": "毎月緊縛プラン", + "fee": 200, + "description": "(毎月更新しましたら古いほうを削除する予定です。お早めに保存して頂きますようお願いいたします。)\r\nFanbox始めました!!試運転として、月額200円で、毎月独占でお縛りのオリジナル画像を一枚上げてきます!基本、話題な女の子(アニメ、ソシャゲー中心)で書こうと思います!どうぞよろしくお願いします!!", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/51378/cover/kKIN01S5y3jpV3zPyXWFi3e9.jpeg", + "user": { + "userId": "3452804", + "name": "HaneRu", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/3452804/icon/b4AWDuOCKPCsgxsdYYURLlSL.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "78034", + "title": "エネルギー注入プラン Patreon 300", + "fee": 300, + "description": "支援が多いほど早く描けます~(わがまま \r\n不定期で未公開ラフを支援者だけに公開します", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/78034/cover/uaBHEnYIhTyAKaDBvVEN0ZAx.jpeg", + "user": { + "userId": "4417141", + "name": "クロニ", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/4417141/icon/hnNvWHyfFOXYGPl1vFTm2wKu.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "87526", + "title": "普通に応援", + "fee": 600, + "description": "普段のラフやイラストを観ることができます。\n気軽に応援プランより、月にもう一枚のスペシャルイラストを観ることがきるようになります。\nショットストーリーの1ー2ページを観ることができます。", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/87526/cover/6R5J5c5FohoeZ3BZoAHUZOyv.jpeg", + "user": { + "userId": "4704179", + "name": "万天気像", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/4704179/icon/1HlyC5Axr3StL3XQUHZvpQDj.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "70032", + "title": "PSD元ファイルを取得", + "fee": 750, + "description": "可以得到作者本月新作的PSD原文件\n作者の今月新作のPSDファイルを入手できます。\nget the original PSD file that the author wrote this month.", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/70032/cover/WhaKe10PO8BWn7ploP688BW4.jpeg", + "user": { + "userId": "5165814", + "name": "神奇火鸡(Wonder Turkey)", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/5165814/icon/NzsxKESsUOsH2gkEn8kxCFl8.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "14795", + "title": "見放題プラン", + "fee": 300, + "description": "見放題プランです。", + "coverImageUrl": null, + "user": { + "userId": "5664611", + "name": "Θωθ", + "iconUrl": null + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "95835", + "title": "Previews of drawing in progress.", + "fee": 150, + "description": "Sketches with news and ideas of drawings I am currently working on. \r\nJist like in patreon. :3\r\nMight also post colored progress reports of larger projects. ^^", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/95835/cover/s0tOyvCCcNXA2V24lAAwPcg7.jpeg", + "user": { + "userId": "5944252", + "name": "Hotel01", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/5944252/icon/R6Gu6354EmTMQO6NP97eDVdC.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "59121", + "title": "毎月一枚r18同人プラン", + "fee": 200, + "description": "月末で一枚以上のr18同人イラストを限定公開します!テーマは今期アニメや、ゲーム等、ネトラレ、陵辱等もあります!", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/59121/cover/kf5nD3JrXkjABocaaDgY9Mkb.jpeg", + "user": { + "userId": "6049901", + "name": "鬼針草", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/6049901/icon/SHqjwGwp3G3Ya7S3k6o4wfTf.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "39309", + "title": "プラン500", + "fee": 500, + "description": "高解像度イラストを閲覧することができます。\r\nYou can see high resolution images.", + "coverImageUrl": null, + "user": { + "userId": "6357272", + "name": "ろるゐ紳士", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/6357272/icon/XKIw7maSfZV8xAvPHbvL6lnO.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "86864", + "title": "600円プラン", + "fee": 600, + "description": "いつも通り、ファンボックスがpatreonみたいに使います。\n不安定ですが、毎月1差分絵セット保証します、2セットが頑張ります。", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/86864/cover/eOEV1YvAzynCfL1aYj08ptOG.jpeg", + "user": { + "userId": "6610818", + "name": "サーモン", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/6610818/icon/uXK2ehrKEL8prJwLx5qebuXF.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "93775", + "title": "It helps me concentrate on my work.", + "fee": 200, + "description": "I will upload a private picture for the fan box once a month.", + "coverImageUrl": null, + "user": { + "userId": "10414967", + "name": "kkta", + "iconUrl": null + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "13242", + "title": "スタンダートプラン", + "fee": 500, + "description": "えっちな絵を見たい方のスタンダートなプランです。\r\nTwitterにはスケベすぎて載せられない支援者限定イラストやエロ差分、文字なし差分などを定期的に載せます。", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/13242/cover/KqDzOeEmxS2pa78DiuHEGkbB.jpeg", + "user": { + "userId": "10594960", + "name": "弥猫うた", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/10594960/icon/xXk4UqYlTXuHaFN6QEdWWkE2.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "49868", + "title": "お賽銭箱", + "fee": 200, + "description": "pixiv、ツイッター等で上げてる絵の裸verなど上げたいと思います~", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/49868/cover/mZnXCfifwWTrsCxB7W92WGqk.jpeg", + "user": { + "userId": "11229342", + "name": "サインこす", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/11229342/icon/AIfY06L4CPM2WiadhfMlHViT.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "72698", + "title": "喜欢的话给个赞助吧…", + "fee": 200, + "description": "", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/72698/cover/clb46Zb6PrwAHjh17fOHMN3D.jpeg", + "user": { + "userId": "12016484", + "name": "HLL.ALSG99", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/12016484/icon/EaoIZnz0fP6xxkCQ4siTyd4o.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "59173", + "title": "落書き", + "fee": 500, + "description": "ここで私の普段の落書きと受付のイラストの落書きを発表する。落書きほとんどは人体緊縛の姿についての絵です。そしてみんなと一緒にいい場面を構想したいと思います。\r\n我會在這裡發佈一些平時構想的草稿以及約稿的草圖,草圖大部分情況下是突然想到的能用在捆綁里的人體結構 我很樂意和大家一起想後面的構思之類的東西_(:з」∠)_", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/59173/cover/zjFQY2WTAs0bR6MNklWYXBlS.jpeg", + "user": { + "userId": "12063067", + "name": "臣訾", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/12063067/icon/ciyGaxmudPK7wsrqY0kDcwRj.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "51253", + "title": "毎月緊縛プラン", + "fee": 200, + "description": "Fanbox始めました!!試運転として、月額200円で、毎月独占でお縛りのオリジナル画像を一枚上げてきます!基本、話題な女の子(アニメ、ソシャゲー中心)で書こうと思います!どうぞよろしくお願いします!!", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/51253/cover/RwTIAoJsAnIWJWNYNAkhEfBg.jpeg", + "user": { + "userId": "13379747", + "name": "ひみつ", + "iconUrl": null + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "94525", + "title": "水月的Fanbox计划", + "fee": 300, + "description": "水月的色图细化计划,应该每月能更新一次吧", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/94525/cover/A0AZiixXTt6NgsQw7NGQTDDF.jpeg", + "user": { + "userId": "13524241", + "name": "水月岩雲ー仕事募集中", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/13524241/icon/8XcQMzKzVLJlydmb1afNBg2w.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "61259", + "title": "レベル3:限定イラスト", + "fee": 1000, + "description": "イラストとマンガ以上 未公開イラスト1枚(縛りと拘束),限定イラストは差分もありよ", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/61259/cover/JQRm1mgrOe5U8xIqPpIExkmE.jpeg", + "user": { + "userId": "17305623", + "name": "雪ノ嵐&异端丶", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/17305623/icon/86rMpiapcC8E7wNGhz8KHghK.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "2399", + "title": "資金援助プラン", + "fee": 500, + "description": "入ってくれる方は神様仏様マミさん\n支援頂いたお金は画材購入、環境整備、同人活動の為に使わせて頂きます。\n現状では頒布物の内容の一部や原稿進捗(全体図、線画など)を投稿して行きます\n何かしらの御礼的コンテンツを考え中(差分絵とか", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/2399/cover/SwWbY8hVcTq2R1LIcRVPonYi.jpeg", + "user": { + "userId": "17813543", + "name": "やんまーみ", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/17813543/icon/UYKInBg5X3ExpjAwmcnYZAOG.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "87716", + "title": "とどおが頑張って描こうとするプラン", + "fee": 500, + "description": "100円のプランの内容に加え、過去にpixivで上げた絵の高解像度版やボツ絵などが見れます。\r\nまた、とどおが支援に感謝して無理をしない範囲でがんばろうとします。\r\n", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/87716/cover/fL5I25T8uid0HGiVYH6QjqHN.jpeg", + "user": { + "userId": "18961759", + "name": "とどお", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/18961759/icon/7uqGq9JlT0XjdHSRiBAfs1Wi.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "20046", + "title": "イラスト気に入ったよ的な感じで・・・", + "fee": 300, + "description": "もしもpixiv等でのイラストで気に入った方がいましたら、投げ銭みたいな感じで支援していただけると嬉しいです。特に大きな見返りがあるわけではないのでご注意ください。", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/20046/cover/fe1nD773JNAQld3Csw3LurpS.jpeg", + "user": { + "userId": "19116803", + "name": "かなとふ", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/19116803/icon/ph0eU9nf0g2RiTyZvo0ZANoJ.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "95894", + "title": "おべんとプラン", + "fee": 500, + "description": "100円のプランに加え、pixivなどで公開したイラストの高解像度版や限定おまけ差分をご覧いただけます。\n\nご支援金は創作活動費として使用されます。", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/95894/cover/strRAGNfkBdtU6kFOeIcQJNY.jpeg", + "user": { + "userId": "22553630", + "name": "桜屋敷とんこつ", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/22553630/icon/b6XKPWx6nZabxwPgFHlpXrC0.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "79440", + "title": "500円プラン", + "fee": 500, + "description": "I will post illustrations for only supporters every month\r\n毎月サポーターのみにイラストを投稿します", + "coverImageUrl": null, + "user": { + "userId": "23311297", + "name": "nekomom", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/23311297/icon/NZTaY6CgR8zHuwywXHfIURqC.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "85396", + "title": "ラフぽいぽい祭り", + "fee": 100, + "description": "ラフを載せまくる。以上です!よろしくお願いします!\r\n1週間に1~2回の更新を目標に頑張ります(。ᵕᴗᵕ。)\r\n", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/85396/cover/H0IqMyCwbwUQOLe6kICNYZA2.jpeg", + "user": { + "userId": "23626451", + "name": "にゆん", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/23626451/icon/c0X6sE0XAWId3KVWQqMhCgar.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": false, + "paymentMethod": "gmo_card" + }, { + "id": "84482", + "title": "通常プラン", + "fee": 880, + "description": "現状のところプランは一つだけですので、すべての作品を閲覧することができます。", + "coverImageUrl": null, + "user": { + "userId": "23652509", + "name": "識林ほりう", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/23652509/icon/r4zsbbGehIK6QLuM4dO3iWkJ.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "67599", + "title": "スタンダードプラン", + "fee": 400, + "description": "「継続的に支援したい!」という方向けのスタンダードなプランです。\n\nThis is a standard plan.\nFor those who want to support me continuously.", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/67599/cover/yf2vSvP7JDsigIdHjoFCpevH.jpeg", + "user": { + "userId": "25900946", + "name": "ogyadya", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/25900946/icon/DlFEK7OYdUzSvSdle9EBTEA9.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "57468", + "title": "月刊誌", + "fee": 500, + "description": "お賽銭的なヤツ", + "coverImageUrl": null, + "user": { + "userId": "27569300", + "name": "仁外", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/27569300/icon/A85RkerebOnRg3B0PaLE1c63.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "51441", + "title": "うなぎ丼", + "fee": 600, + "description": "公開しない差分の閲覧が可能になります。その上、毎月独占で拘束の画像を一枚上げてきます。", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/51441/cover/qtSz3auZWlThSBv5TFFJcqQY.jpeg", + "user": { + "userId": "30716447", + "name": "ginklaga", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/30716447/icon/t1uxsE6MApu2Lkn2azwnJvHp.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "48637", + "title": "+他作品", + "fee": 1000, + "description": "「γ-ガンマ-」以外の作品のイラストも観覧出来る様になります。\r\nこちらのプランで「γ-ガンマ-」のイラストも全て観覧出来ます。", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/48637/cover/zf7Ia0StIIv9eoDf2Y9H8xGZ.jpeg", + "user": { + "userId": "35438167", + "name": "γ", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/35438167/icon/X7POgmP3psmeubiiglej7qCV.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "82919", + "title": "fan (+3pts/month)", + "fee": 300, + "description": "being a fan you get to see them exclusives c;", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/82919/cover/rgoQIUmWblcjGcjXmSuDu0zg.jpeg", + "user": { + "userId": "40169508", + "name": "crumbles", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/40169508/icon/buR2OoPLjCm1RagEvyHYatyC.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "93759", + "title": "100円TIPS", + "fee": 100, + "description": "full-size picture and the process of my works only! I would be very thankful!\nこちらは原サイズ画像と創作流れしかありません\n只有原尺寸圖與我的感謝(´∀`)", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/93759/cover/BsXC8FH3Wxi3VfVV2OYa8tl5.jpeg", + "user": { + "userId": "42936714", + "name": "BLAM", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/42936714/icon/dOnO2ZaKTk91VORpBchIYAEc.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }, { + "id": "96263", + "title": "投げ銭プラン", + "fee": 100, + "description": "投げ銭のプランです。ちょっとしたおまけ等が見れる予定です。プランに興味がなくても、フォローやコメント、コミッション等していただけると励みになります。", + "coverImageUrl": "https://pixiv.pximg.net/c/936x600_90_a2_g5/fanbox/public/images/plan/96263/cover/UEN4nNSXxVictnIr2NqM3z2U.jpeg", + "user": { + "userId": "44433394", + "name": "Pita17", + "iconUrl": "https://pixiv.pximg.net/c/160x160_90_a2_g5/fanbox/public/images/user/44433394/icon/MjeSA6YIUQHHCAtcAlMAfpht.jpeg" + }, + "creatorId": "madeToPassUnitTest", + "hasAdultContent": true, + "paymentMethod": "gmo_card" + }] +}