├── .gitignore ├── LICENSE ├── PixivAPI ├── PAPIAuthor.h ├── PAPIAuthor.m ├── PAPIIllust.h ├── PAPIIllust.m ├── PAPIIllustList.h ├── PAPIIllustList.m ├── PixivAPI.h ├── PixivAPI.js ├── PixivAPI.m ├── PixivDefines.h ├── RCTPixivAPI.h ├── RCTPixivAPI.m ├── RCTPixivAPI.xcodeproj │ └── project.pbxproj ├── SAPIIllust.h └── SAPIIllust.m └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # dev test 21 | test/ 22 | 23 | # CocoaPods 24 | # 25 | # We recommend against adding the Pods directory to your .gitignore. However 26 | # you should judge for yourself, the pros and cons are mentioned at: 27 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 28 | # 29 | # Pods/ 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015 Hao Zhou 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PixivAPI/PAPIAuthor.h: -------------------------------------------------------------------------------- 1 | // 2 | // PAPIAuthor.h 3 | // 4 | // Created by Zhou Hao on 14/10/19. 5 | // Copyright (c) 2014 Zhou Hao. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface PAPIAuthor : NSObject 11 | 12 | /** 13 | * Parse NSDictionary of PAPI json result to PAPIAuthor 14 | * 15 | * @param NSDictionary of PAPI json result 16 | * 17 | * @return PAPIAuthor object 18 | */ 19 | + (PAPIAuthor *)parseJsonDictionaryToModel:(NSDictionary *)jsonData; 20 | 21 | - (NSDictionary *)toObject; 22 | 23 | @property (strong, nonatomic) NSDictionary *raw; // origin json data from PAPI 24 | @property (strong, nonatomic) NSDictionary *response; // response[0] for illust 25 | 26 | #pragma mark - Author properties 27 | 28 | @property (nonatomic, readonly) NSInteger author_id; 29 | @property (strong, nonatomic, readonly) NSString *account; 30 | @property (strong, nonatomic, readonly) NSString *name; 31 | 32 | @property (strong, nonatomic, readonly) NSDictionary *profile; 33 | @property (strong, nonatomic, readonly) NSArray *tags; 34 | @property (strong, nonatomic, readonly) NSString *introduction; 35 | @property (strong, nonatomic, readonly) NSString *gender; 36 | @property (strong, nonatomic, readonly) NSString *contacts; 37 | @property (strong, nonatomic, readonly) NSString *job; 38 | @property (strong, nonatomic, readonly) NSString *location; 39 | @property (strong, nonatomic, readonly) NSString *workspace; 40 | @property (strong, nonatomic, readonly) NSString *birth_date; 41 | @property (strong, nonatomic, readonly) NSString *homepage; 42 | @property (strong, nonatomic, readonly) NSString *blood_type; 43 | 44 | @property (strong, nonatomic, readonly) NSString *email; 45 | @property (strong, nonatomic, readonly) NSString *is_premium; 46 | 47 | @property (strong, nonatomic, readonly) NSDictionary *profile_image_urls; 48 | @property (strong, nonatomic, readonly) NSString *profile_url_px_50x50; 49 | @property (strong, nonatomic, readonly) NSString *profile_url_px_170x170; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /PixivAPI/PAPIAuthor.m: -------------------------------------------------------------------------------- 1 | // 2 | // PAPIAuthor.m 3 | // 4 | // Created by Zhou Hao on 14/10/19. 5 | // Copyright (c) 2014 Zhou Hao. All rights reserved. 6 | // 7 | 8 | #import "PAPIAuthor.h" 9 | #import "PixivDefines.h" 10 | 11 | @implementation PAPIAuthor 12 | 13 | - (NSString *)description 14 | { 15 | return [NSString stringWithFormat:@"Author: %@(account=%@, id=%ld)", self.name, self.account, (long)self.author_id]; 16 | } 17 | 18 | + (PAPIAuthor *)parseJsonDictionaryToModel:(NSDictionary *)jsonData 19 | { 20 | if (![jsonData objectForKey:@"count"] || ![jsonData objectForKey:@"response"]) { 21 | NSLog(@"jsonData.count for jsonData.response not found"); 22 | return nil; 23 | } 24 | if ([[jsonData objectForKey:@"count"] integerValue] != 1) { 25 | NSLog(@"response count %ld > 1", (long)[[jsonData objectForKey:@"count"] integerValue]); 26 | return nil; 27 | } 28 | 29 | PAPIAuthor *author = [[PAPIAuthor alloc] init]; 30 | author.raw = jsonData; 31 | author.response = [jsonData[@"response"] firstObject]; 32 | return author; 33 | } 34 | 35 | - (NSDictionary *)toObject 36 | { 37 | return self.response; 38 | } 39 | 40 | #pragma mark - Author properties 41 | 42 | - (NSInteger)safeIntegerValue:(id)data 43 | { 44 | if (data == [NSNull null]) { 45 | return PIXIV_INT_INVALID; 46 | } 47 | return [data integerValue]; 48 | } 49 | 50 | - (NSDictionary *)profile 51 | { 52 | return self.response[@"profile"]; 53 | } 54 | - (NSArray *)tags 55 | { 56 | return self.profile[@"tags"]; 57 | } 58 | - (NSString *)introduction 59 | { 60 | return self.profile[@"introduction"]; 61 | } 62 | - (NSString *)gender 63 | { 64 | return self.profile[@"gender"]; 65 | } 66 | - (NSString *)contacts 67 | { 68 | return self.profile[@"contacts"]; 69 | } 70 | - (NSString *)job 71 | { 72 | return self.profile[@"job"]; 73 | } 74 | - (NSString *)location 75 | { 76 | return self.profile[@"location"]; 77 | } 78 | - (NSString *)workspace 79 | { 80 | return self.profile[@"workspace"]; 81 | } 82 | - (NSString *)birth_date 83 | { 84 | return self.profile[@"birth_date"]; 85 | } 86 | - (NSString *)homepage 87 | { 88 | return self.profile[@"homepage"]; 89 | } 90 | - (NSString *)blood_type 91 | { 92 | return self.profile[@"blood_type"]; 93 | } 94 | 95 | - (NSString *)account 96 | { 97 | return self.response[@"account"]; 98 | } 99 | 100 | - (NSString *)name 101 | { 102 | return self.response[@"name"]; 103 | } 104 | 105 | - (NSString *)email 106 | { 107 | return self.response[@"email"]; 108 | } 109 | 110 | - (NSString *)is_premium 111 | { 112 | return self.response[@"is_premium"]; 113 | } 114 | 115 | - (NSDictionary *)profile_image_urls 116 | { 117 | if ([self.response[@"profile_image_urls"] isKindOfClass:[NSDictionary class]]) { 118 | return self.response[@"profile_image_urls"]; 119 | } 120 | return nil; 121 | } 122 | - (NSString *)profile_url_px_50x50 123 | { 124 | return self.profile_image_urls[@"px_50x50"]; 125 | } 126 | - (NSString *)profile_url_px_170x170 127 | { 128 | return self.profile_image_urls[@"px_170x170"]; 129 | } 130 | 131 | - (NSInteger)author_id 132 | { 133 | return [self safeIntegerValue:self.response[@"id"]]; 134 | } 135 | 136 | #pragma mark - NSCoding 137 | 138 | - (id)initWithCoder:(NSCoder *)aDecoder 139 | { 140 | self = [super init]; 141 | if (!self) { 142 | return nil; 143 | } 144 | 145 | self.raw = [aDecoder decodeObjectForKey:@"raw"]; 146 | self.response = [aDecoder decodeObjectForKey:@"response"]; 147 | return self; 148 | } 149 | 150 | - (void)encodeWithCoder:(NSCoder *)aCoder 151 | { 152 | [aCoder encodeObject:self.raw forKey:@"raw"]; 153 | [aCoder encodeObject:self.response forKey:@"response"]; 154 | } 155 | 156 | @end 157 | -------------------------------------------------------------------------------- /PixivAPI/PAPIIllust.h: -------------------------------------------------------------------------------- 1 | // 2 | // PAPIIllust.h 3 | // 4 | // Created by Zhou Hao on 14/10/19. 5 | // Copyright (c) 2014 Zhou Hao. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface PAPIIllust : NSObject 11 | 12 | /** 13 | * Parse NSDictionary of PAPI json result to PAPIIllust 14 | * 15 | * @param NSDictionary of PAPI json result 16 | * 17 | * @return PAPIIllust object 18 | */ 19 | + (PAPIIllust *)parseJsonDictionaryToModel:(NSDictionary *)jsonData; 20 | + (PAPIIllust *)parseRawDictionaryToModel:(NSDictionary *)jsonData isWork:(BOOL)isWork; 21 | 22 | - (NSDictionary *)toObject; 23 | 24 | @property (strong, nonatomic) NSDictionary *raw; // origin json data from PAPI 25 | @property (strong, nonatomic) NSDictionary *response; // response[0] for illust 26 | 27 | #pragma mark - Illust properties 28 | 29 | @property (nonatomic, readonly) NSInteger illust_id; 30 | @property (strong, nonatomic, readonly) NSString *title; 31 | @property (strong, nonatomic, readonly) NSString *type; 32 | @property (nonatomic, readonly) NSInteger page_count; 33 | 34 | @property (strong, nonatomic, readonly) NSArray *tags; 35 | @property (strong, nonatomic, readonly) NSString *caption; 36 | @property (strong, nonatomic, readonly) NSString *tools; 37 | 38 | @property (strong, nonatomic, readonly) NSString *age_limit; 39 | @property (nonatomic, readonly) NSInteger publicity; 40 | @property (nonatomic, readonly) BOOL is_manga; 41 | 42 | @property (strong, nonatomic, readonly) NSDictionary *stats; 43 | @property (strong, nonatomic, readonly) NSDictionary *favorited_count; 44 | @property (nonatomic, readonly) NSInteger favorited_private; 45 | @property (nonatomic, readonly) NSInteger favorited_public; 46 | @property (nonatomic, readonly) NSInteger views_count; 47 | @property (nonatomic, readonly) NSInteger score; 48 | @property (nonatomic, readonly) NSInteger scored_count; 49 | @property (nonatomic, readonly) NSInteger commented_count; 50 | 51 | @property (nonatomic, readonly) NSInteger favorite_id; 52 | @property (nonatomic, readonly) BOOL is_liked; 53 | 54 | @property (strong, nonatomic, readonly) NSDictionary *image_urls; 55 | @property (strong, nonatomic, readonly) NSString *url_px_128x128; 56 | @property (strong, nonatomic, readonly) NSString *url_px_480mw; 57 | @property (strong, nonatomic, readonly) NSString *url_large; 58 | @property (strong, nonatomic, readonly) NSString *url_small; 59 | @property (strong, nonatomic, readonly) NSString *url_medium; 60 | 61 | @property (nonatomic, readonly) NSInteger width; 62 | @property (nonatomic, readonly) NSInteger height; 63 | 64 | @property (strong, nonatomic, readonly) NSDictionary *user; 65 | @property (nonatomic, readonly) NSInteger author_id; 66 | @property (strong, nonatomic, readonly) NSString *account; 67 | @property (strong, nonatomic, readonly) NSString *name; 68 | @property (nonatomic, readonly) BOOL is_friend; 69 | @property (nonatomic, readonly) BOOL is_following; 70 | @property (nonatomic, readonly) BOOL is_follower; 71 | @property (strong, nonatomic, readonly) NSDictionary *profile_image_urls; 72 | @property (strong, nonatomic, readonly) NSString *profile_url_px_50x50; 73 | @property (strong, nonatomic, readonly) NSString *profile_url_px_170x170; 74 | 75 | @property (strong, nonatomic, readonly) NSString *created_time; 76 | @property (strong, nonatomic, readonly) NSString *reuploaded_time; 77 | @property (strong, nonatomic, readonly) NSString *book_style; 78 | 79 | @property (strong, nonatomic, readonly) NSDictionary *metadata; 80 | @property (strong, nonatomic, readonly) NSArray *pages; 81 | 82 | @property (strong, nonatomic, readonly) NSString *true_url_large; 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /PixivAPI/PAPIIllust.m: -------------------------------------------------------------------------------- 1 | // 2 | // PAPIIllust.m 3 | // 4 | // Created by Zhou Hao on 14/10/19. 5 | // Copyright (c) 2014 Zhou Hao. All rights reserved. 6 | // 7 | 8 | #import "PAPIIllust.h" 9 | #import "PixivDefines.h" 10 | 11 | @implementation PAPIIllust 12 | 13 | - (NSString *)description 14 | { 15 | return [NSString stringWithFormat:@"Illust: [%@(id=%ld)] %@(id=%ld), size=%ldx%ld", 16 | self.name, (long)self.author_id, self.title, (long)self.illust_id, (long)self.width, (long)self.height]; 17 | } 18 | 19 | + (PAPIIllust *)parseJsonDictionaryToModel:(NSDictionary *)jsonData 20 | { 21 | if (![jsonData objectForKey:@"count"] || ![jsonData objectForKey:@"response"]) { 22 | NSLog(@"jsonData.count or jsonData.response not found"); 23 | return nil; 24 | } 25 | if ([[jsonData objectForKey:@"count"] integerValue] != 1) { 26 | NSLog(@"response count %ld > 1", (long)[[jsonData objectForKey:@"count"] integerValue]); 27 | return nil; 28 | } 29 | 30 | PAPIIllust *illust = [[PAPIIllust alloc] init]; 31 | illust.raw = jsonData; 32 | illust.response = [jsonData[@"response"] firstObject]; 33 | return illust; 34 | } 35 | 36 | + (PAPIIllust *)parseRawDictionaryToModel:(NSDictionary *)jsonData isWork:(BOOL)isWork 37 | { 38 | NSDictionary *data = nil; 39 | if (isWork) { 40 | data = jsonData; 41 | } else { 42 | if ([jsonData objectForKey:@"work"]) { 43 | data = jsonData[@"work"]; 44 | } 45 | } 46 | 47 | if (!data) { 48 | NSLog(@"unknow data: %@", jsonData); 49 | return nil; 50 | } 51 | if (![data objectForKey:@"id"] || ![data objectForKey:@"title"]) { 52 | NSLog(@"data.id or data.title not found"); 53 | return nil; 54 | } 55 | 56 | PAPIIllust *illust = [[PAPIIllust alloc] init]; 57 | illust.raw = @{ 58 | @"response": @[data], 59 | }; 60 | illust.response = data; 61 | return illust; 62 | } 63 | 64 | - (NSDictionary *)toObject 65 | { 66 | return self.response; 67 | } 68 | 69 | #pragma mark - Illust properties 70 | 71 | - (NSInteger)safeIntegerValue:(id)data 72 | { 73 | if (data == [NSNull null]) { 74 | return PIXIV_INT_INVALID; 75 | } 76 | return [data integerValue]; 77 | } 78 | 79 | - (NSInteger)publicity 80 | { 81 | return [self safeIntegerValue:self.response[@"publicity"]]; 82 | } 83 | 84 | - (BOOL)is_manga 85 | { 86 | return [self.response[@"is_manga"] integerValue] ? YES : NO; 87 | } 88 | 89 | - (NSDictionary *)stats 90 | { 91 | if ([self.response[@"stats"] isKindOfClass:[NSDictionary class]]) { 92 | return self.response[@"stats"]; 93 | } 94 | return nil; 95 | } 96 | - (NSDictionary *)favorited_count 97 | { 98 | if ([self.stats[@"favorited_count"] isKindOfClass:[NSDictionary class]]) { 99 | return self.stats[@"favorited_count"]; 100 | } 101 | return nil; 102 | } 103 | - (NSInteger)favorited_private 104 | { 105 | return [self safeIntegerValue:self.favorited_count[@"private"]]; 106 | } 107 | - (NSInteger)favorited_public 108 | { 109 | return [self safeIntegerValue:self.favorited_count[@"public"]]; 110 | } 111 | - (NSInteger)score 112 | { 113 | return [self safeIntegerValue:self.stats[@"score"]]; 114 | } 115 | - (NSInteger)views_count 116 | { 117 | return [self safeIntegerValue:self.stats[@"views_count"]]; 118 | } 119 | - (NSInteger)scored_count 120 | { 121 | return [self safeIntegerValue:self.stats[@"scored_count"]]; 122 | } 123 | - (NSInteger)commented_count 124 | { 125 | return [self safeIntegerValue:self.stats[@"commented_count"]]; 126 | } 127 | 128 | - (NSInteger)favorite_id 129 | { 130 | return [self safeIntegerValue:self.response[@"favorite_id"]]; 131 | } 132 | 133 | - (NSArray *)tags 134 | { 135 | return self.response[@"tags"]; 136 | } 137 | 138 | - (NSString *)type 139 | { 140 | return self.response[@"type"]; 141 | } 142 | 143 | - (BOOL)is_liked 144 | { 145 | return [self.response[@"is_liked"] integerValue] ? YES : NO; 146 | } 147 | 148 | - (NSInteger)page_count 149 | { 150 | return [self safeIntegerValue:self.response[@"page_count"]]; 151 | } 152 | 153 | - (NSDictionary *)image_urls 154 | { 155 | if ([self.response[@"image_urls"] isKindOfClass:[NSDictionary class]]) { 156 | return self.response[@"image_urls"]; 157 | } 158 | return nil; 159 | } 160 | - (NSString *)url_small 161 | { 162 | return self.image_urls[@"small"]; 163 | } 164 | - (NSString *)url_large 165 | { 166 | return self.image_urls[@"large"]; 167 | } 168 | - (NSString *)url_px_128x128 169 | { 170 | return self.image_urls[@"px_128x128"]; 171 | } 172 | - (NSString *)url_medium 173 | { 174 | return self.image_urls[@"medium"]; 175 | } 176 | - (NSString *)url_px_480mw 177 | { 178 | return self.image_urls[@"px_480mw"]; 179 | } 180 | 181 | - (NSInteger)height 182 | { 183 | return [self safeIntegerValue:self.response[@"height"]]; 184 | } 185 | 186 | - (NSString *)caption 187 | { 188 | return self.response[@"caption"]; 189 | } 190 | 191 | - (NSString *)tools 192 | { 193 | NSArray *toolList = self.response[@"tools"]; 194 | return [toolList componentsJoinedByString:@" "]; 195 | } 196 | 197 | - (NSDictionary *)user 198 | { 199 | if ([self.response[@"user"] isKindOfClass:[NSDictionary class]]) { 200 | return self.response[@"user"]; 201 | } 202 | return nil; 203 | } 204 | - (NSString *)account 205 | { 206 | return self.user[@"account"]; 207 | } 208 | - (NSString *)name 209 | { 210 | return self.user[@"name"]; 211 | } 212 | - (BOOL)is_friend 213 | { 214 | return [self.user[@"is_friend"] integerValue] ? YES : NO; 215 | } 216 | - (BOOL)is_following 217 | { 218 | return [self.user[@"is_following"] integerValue] ? YES : NO; 219 | } 220 | - (BOOL)is_follower 221 | { 222 | return [self.user[@"is_follower"] integerValue] ? YES : NO; 223 | } 224 | - (NSDictionary *)profile_image_urls 225 | { 226 | if ([self.user[@"profile_image_urls"] isKindOfClass:[NSDictionary class]]) { 227 | return self.user[@"profile_image_urls"]; 228 | } 229 | return nil; 230 | } 231 | - (NSString *)profile_url_px_50x50 232 | { 233 | return self.profile_image_urls[@"px_50x50"]; 234 | } 235 | - (NSString *)profile_url_px_170x170 236 | { 237 | return self.profile_image_urls[@"px_170x170"]; 238 | } 239 | - (NSInteger)author_id 240 | { 241 | return [self safeIntegerValue:self.user[@"id"]]; 242 | } 243 | 244 | - (NSString *)reuploaded_time 245 | { 246 | return self.response[@"reuploaded_time"]; 247 | } 248 | 249 | - (NSString *)created_time 250 | { 251 | return self.response[@"created_time"]; 252 | } 253 | 254 | - (NSString *)title 255 | { 256 | return self.response[@"title"]; 257 | } 258 | 259 | - (NSInteger)illust_id 260 | { 261 | return [self safeIntegerValue:self.response[@"id"]]; 262 | } 263 | 264 | - (NSString *)book_style 265 | { 266 | return self.response[@"book_style"]; 267 | } 268 | 269 | - (NSString *)age_limit 270 | { 271 | return self.response[@"age_limit"]; 272 | } 273 | 274 | - (NSInteger)width 275 | { 276 | return [self safeIntegerValue:self.response[@"width"]]; 277 | } 278 | 279 | - (NSDictionary *)metadata 280 | { 281 | if ([self.response[@"metadata"] isKindOfClass:[NSDictionary class]]) { 282 | return self.response[@"metadata"]; 283 | } 284 | return nil; 285 | } 286 | - (NSArray *)pages 287 | { 288 | if ([self.metadata[@"pages"] isKindOfClass:[NSArray class]]) { 289 | return self.metadata[@"pages"]; 290 | } 291 | return nil; 292 | } 293 | 294 | - (NSString *)true_url_large 295 | { 296 | if (!self.pages) { 297 | if (self.page_count > 1) { 298 | NSString *url = self.url_large; 299 | NSRange range = [url rangeOfString:@"_p0" options:NSBackwardsSearch]; 300 | if (range.length > 0) { 301 | // New illust with _p0 ext, just return url_large 302 | return url; 303 | } else { 304 | // Old illust storage, add _p0 in url_large.ext 305 | // FIX BUG: some illust has "?timestamp" at end, so seach backward 306 | NSRange ext_dot = [url rangeOfString:@"." options:NSBackwardsSearch]; 307 | NSString *url_base = [url substringWithRange:NSMakeRange(0, ext_dot.location)]; 308 | NSString *url_ext = [url substringFromIndex:ext_dot.location]; 309 | return [NSString stringWithFormat:@"%@_p0%@", url_base, url_ext]; 310 | } 311 | } else { 312 | return self.url_large; 313 | } 314 | } else { 315 | return [self.pages firstObject][@"image_urls"][@"large"]; 316 | } 317 | } 318 | 319 | #pragma mark - NSCoding 320 | 321 | - (id)initWithCoder:(NSCoder *)aDecoder 322 | { 323 | self = [super init]; 324 | if (!self) { 325 | return nil; 326 | } 327 | 328 | self.raw = [aDecoder decodeObjectForKey:@"raw"]; 329 | self.response = [aDecoder decodeObjectForKey:@"response"]; 330 | return self; 331 | } 332 | 333 | - (void)encodeWithCoder:(NSCoder *)aCoder 334 | { 335 | [aCoder encodeObject:self.raw forKey:@"raw"]; 336 | [aCoder encodeObject:self.response forKey:@"response"]; 337 | } 338 | 339 | @end 340 | -------------------------------------------------------------------------------- /PixivAPI/PAPIIllustList.h: -------------------------------------------------------------------------------- 1 | // 2 | // PAPIIllustList.h 3 | // 4 | // Created by Zhou Hao on 14/10/19. 5 | // Copyright (c) 2014 Zhou Hao. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | #import "PAPIIllust.h" 11 | 12 | @interface PAPIIllustList : NSObject 13 | 14 | /** 15 | * Parse NSDictionary of PAPI json result to PAPIIllustList 16 | * 17 | * @param NSDictionary of PAPI json result 18 | * 19 | * @return PAPIIllustList object 20 | */ 21 | + (PAPIIllustList *)parseJsonDictionaryToModelList:(NSDictionary *)jsonData isWork:(BOOL)isWork; 22 | 23 | - (NSArray *)toObjectList; 24 | 25 | @property (strong, nonatomic) NSDictionary *raw; // origin json data from PAPI 26 | @property (strong, nonatomic) NSDictionary *pagination; // pagination info 27 | 28 | @property (strong, nonatomic) NSArray *illusts; // of PAPIIllust 29 | @property (nonatomic, readonly) NSInteger count; 30 | 31 | #pragma mark - pagination properties 32 | 33 | @property (nonatomic, readonly) NSInteger per_page; 34 | @property (nonatomic, readonly) NSInteger total; 35 | @property (nonatomic, readonly) NSInteger pages; 36 | 37 | @property (nonatomic, readonly) NSInteger current; 38 | @property (nonatomic, readonly) NSInteger next; 39 | @property (nonatomic, readonly) NSInteger previous; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /PixivAPI/PAPIIllustList.m: -------------------------------------------------------------------------------- 1 | // 2 | // PAPIIllustList.m 3 | // 4 | // Created by Zhou Hao on 14/10/19. 5 | // Copyright (c) 2014 Zhou Hao. All rights reserved. 6 | // 7 | 8 | #import "PAPIIllustList.h" 9 | #import "PixivDefines.h" 10 | 11 | @implementation PAPIIllustList 12 | 13 | + (PAPIIllustList *)parseJsonDictionaryToModelList:(NSDictionary *)jsonData isWork:(BOOL)isWork 14 | { 15 | if (![jsonData objectForKey:@"count"] || ![jsonData objectForKey:@"response"]) { 16 | NSLog(@"jsonData.count for jsonData.response not found"); 17 | return nil; 18 | } 19 | 20 | PAPIIllustList *list = [[PAPIIllustList alloc] init]; 21 | list.raw = jsonData; 22 | list.pagination = nil; 23 | if ([[jsonData objectForKey:@"pagination"] isKindOfClass:[NSDictionary class]]) { 24 | list.pagination = jsonData[@"pagination"]; 25 | } 26 | 27 | // from response[] gen NSArray of PAPIIllust 28 | NSMutableArray *tmpIllusts = [[NSMutableArray alloc] init]; 29 | NSArray *responseList = nil; 30 | if ([[jsonData[@"response"] firstObject] objectForKey:@"works"]) { 31 | responseList = [[jsonData[@"response"] firstObject] objectForKey:@"works"]; 32 | } else { 33 | responseList = jsonData[@"response"]; 34 | } 35 | for (NSDictionary *jsonIllust in responseList) { 36 | PAPIIllust *illust = [PAPIIllust parseRawDictionaryToModel:jsonIllust isWork:isWork]; 37 | if (!illust) { 38 | NSLog(@"parseRaw() error:\n%@", jsonIllust); 39 | continue; 40 | } 41 | [tmpIllusts addObject:illust]; 42 | } 43 | list.illusts = tmpIllusts; 44 | 45 | return list; 46 | } 47 | 48 | - (NSArray *)toObjectList; 49 | { 50 | NSMutableArray *tmpIllusts = [[NSMutableArray alloc] init]; 51 | for (PAPIIllust *illust in self.illusts) { 52 | [tmpIllusts addObject:illust.toObject]; 53 | } 54 | return tmpIllusts; 55 | } 56 | 57 | - (NSInteger)count 58 | { 59 | return [self.illusts count]; 60 | } 61 | 62 | #pragma mark - pagination properties 63 | 64 | - (NSInteger)safeIntegerValue:(id)data 65 | { 66 | if (data == [NSNull null]) { 67 | return PIXIV_INT_INVALID; 68 | } 69 | return [data integerValue]; 70 | } 71 | 72 | - (NSInteger)per_page 73 | { 74 | return [self safeIntegerValue:self.pagination[@"per_page"]]; 75 | } 76 | 77 | - (NSInteger)total 78 | { 79 | return [self safeIntegerValue:self.pagination[@"total"]]; 80 | } 81 | 82 | - (NSInteger)pages 83 | { 84 | return [self safeIntegerValue:self.pagination[@"pages"]]; 85 | } 86 | 87 | - (NSInteger)current 88 | { 89 | return [self safeIntegerValue:self.pagination[@"current"]]; 90 | } 91 | 92 | - (NSInteger)next 93 | { 94 | return [self safeIntegerValue:self.pagination[@"next"]]; 95 | } 96 | 97 | - (NSInteger)previous 98 | { 99 | return [self safeIntegerValue:self.pagination[@"previous"]]; 100 | } 101 | 102 | #pragma mark - NSCoding 103 | 104 | - (id)initWithCoder:(NSCoder *)aDecoder 105 | { 106 | self = [super init]; 107 | if (!self) { 108 | return nil; 109 | } 110 | 111 | self.raw = [aDecoder decodeObjectForKey:@"raw"]; 112 | self.pagination = [aDecoder decodeObjectForKey:@"pagination"]; 113 | self.illusts = [aDecoder decodeObjectForKey:@"illusts"]; 114 | return self; 115 | } 116 | 117 | - (void)encodeWithCoder:(NSCoder *)aCoder 118 | { 119 | [aCoder encodeObject:self.raw forKey:@"raw"]; 120 | [aCoder encodeObject:self.pagination forKey:@"pagination"]; 121 | [aCoder encodeObject:self.illusts forKey:@"illusts"]; 122 | } 123 | 124 | @end 125 | -------------------------------------------------------------------------------- /PixivAPI/PixivAPI.h: -------------------------------------------------------------------------------- 1 | // 2 | // PixivAPI.h 3 | // 4 | // Created by Zhou Hao on 14-10-8. 5 | // Copyright (c) 2014 Zhou Hao. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "PixivDefines.h" 10 | 11 | #define DEPRECATED_API __attribute__((deprecated)) 12 | 13 | @interface PixivAPI : NSObject 14 | 15 | @property (strong, nonatomic) NSString *access_token; 16 | @property (strong, nonatomic) NSString *session; 17 | @property (nonatomic) NSInteger user_id; 18 | 19 | @property (strong, nonatomic) NSOperationQueue *operationQueue; 20 | 21 | #pragma mark - common 22 | 23 | + (PixivAPI *)sharedInstance; 24 | - (instancetype)init; 25 | 26 | /** 27 | * Async run operation in Queue, and then call onCompletion 28 | * 29 | * @param queuePriority set 0 for NSOperationQueuePriorityNormal 30 | * @param mainOperations code block for sync(blocking) api 31 | * @param onCompletion completion block on mainQueue 32 | */ 33 | - (void)asyncBlockingQueue:(void (^)(void))mainOperations; 34 | - (void)asyncBlockingQueue:(NSOperationQueuePriority)queuePriority operations:(void (^)(void))mainOperations; 35 | /** 36 | * Wait all operations (by asyncBlockingQueue:) are finished 37 | */ 38 | - (void)asyncWaitAllOperationsAreFinished:(void (^)(void))onCompletion; 39 | 40 | /** 41 | * Run operation in mainQueue, it was short for [NSOperationQueue mainQueue] addOperationWithBlock: 42 | */ 43 | - (void)onMainQueue:(void (^)(void))operationBlock; 44 | 45 | /** 46 | * API Fetch URL 47 | * 48 | * @param method HTTP/HTTPS method (GET or POST) 49 | * @param url base url 50 | * @param headers header for request 51 | * @param params url encode params for GET/POST 52 | * @param data payload for POST 53 | * 54 | * @return _buildResponse: encoded NSDictionary 55 | */ 56 | - (NSDictionary *)URLFetch:(NSString *)method url:(NSString *)url 57 | headers:(NSDictionary *)headers params:(NSDictionary *)params data:(NSDictionary *)data; 58 | 59 | /** 60 | * API Fetch URL 61 | * 62 | * @param method HTTP/HTTPS method (GET or POST) 63 | * @param url base url 64 | * @param headers header for request 65 | * @param params url encode params for GET/POST 66 | * @param data payload for POST 67 | * @param error responsive error message from server 68 | * 69 | * @return _buildResponse: encoded NSDictionary 70 | */ 71 | - (NSDictionary *)URLFetch:(NSString *)method url:(NSString *)url 72 | headers:(NSDictionary *)headers params:(NSDictionary *)params 73 | data:(NSDictionary *)data error:(NSString **)error; 74 | 75 | #pragma mark - login 76 | 77 | /** 78 | * login 79 | * oauth.secure.pixiv.net/auth/token 80 | * 81 | * @return json dictionary from auth/token 82 | */ 83 | - (NSDictionary *)login:(NSString *)username password:(NSString *)password; 84 | 85 | /** 86 | * login 87 | * oauth.secure.pixiv.net/auth/token 88 | * 89 | * @return json dictionary from auth/token 90 | */ 91 | - (NSDictionary *)login:(NSString *)username password:(NSString *)password error:(NSString **)error; 92 | 93 | /** 94 | * login (if local auth expired) 95 | * 96 | * @return YES - success; NO - error 97 | */ 98 | - (BOOL)loginIfNeeded:(NSString *)username password:(NSString *)password; 99 | - (void)loginIfNeeded:(NSString *)username password:(NSString *)password error:(NSString **)error; 100 | 101 | - (BOOL)loadAuthFromUserDefaults:(NSString *)username; 102 | - (void)dropAuthFromUserDefaults; 103 | 104 | /** 105 | * Set session string for PHPSESSID (get PHPSESSID from api.session) 106 | * 107 | * @param access_token [last AccessToken from api.access_token] 108 | * @param session [last PHPSESSID from api.session] 109 | * 110 | */ 111 | - (void)set_auth:(NSString *)access_token session:(NSString *)session; 112 | 113 | #pragma mark - SAPI exports 114 | 115 | /** 116 | * 每日排行 117 | * ranking.php 118 | * 119 | * @param page [1-n] 120 | * @param mode [day, week, month] 121 | * @param content [all, male, female, original] 122 | * @param requireAuth NO 123 | * 124 | * @return NSArray of SAPIIllust 125 | */ 126 | - (NSArray *)SAPI_ranking:(NSInteger)page mode:(NSString *)mode content:(NSString *)content requireAuth:(BOOL)requireAuth DEPRECATED_API; 127 | 128 | /** 129 | * 过去的排行 130 | * ranking_log.php 131 | * 132 | * @param Date_Year 2014 133 | * @param Date_Month 4 134 | * @param Date_Day 15 135 | * @param mode [daily, weekly, monthly, male, female, rookie], r18[daily_r18, weekly_r18, male_r18, female_r18, r18g] 136 | * @param page [1-n] 137 | * @param requireAuth YES - for r18 set 138 | * 139 | * @return NSArray of SAPIIllust 140 | */ 141 | - (NSArray *)SAPI_ranking_log:(NSInteger)Date_Year month:(NSInteger)Date_Month day:(NSInteger)Date_Day 142 | mode:(NSString *)mode page:(NSInteger)page requireAuth:(BOOL)requireAuth DEPRECATED_API; 143 | 144 | /** 145 | * 作品信息 (新版本已使用 PAPI_works: 代替) 146 | * illust.php 147 | * 148 | * @param illust_id [id for illust] 149 | * @param requireAuth YES - for r18 result 150 | * 151 | * @return SAPIIllust 152 | */ 153 | - (SAPIIllust *)SAPI_illust:(NSInteger)illust_id requireAuth:(BOOL)requireAuth DEPRECATED_API; 154 | 155 | /** 156 | * 用户作品列表 157 | * member_illust.php 158 | * 159 | * @param author_id [id for author] 160 | * @param page [1-n] 161 | * @param requireAuth YES - for r18 result 162 | * 163 | * @return NSArray of SAPIIllust 164 | */ 165 | - (NSArray *)SAPI_member_illust:(NSInteger)author_id page:(NSInteger)page requireAuth:(BOOL)requireAuth DEPRECATED_API; 166 | 167 | /** 168 | * 用户资料 (新版本已使用 PAPI_users: 代替) 169 | * user.php 170 | * 171 | * @param user_id [id for author] 172 | * @param requireAuth NO 173 | * 174 | * @return SAPIIllust(Author) 175 | */ 176 | - (SAPIIllust *)SAPI_user:(NSInteger)author_id requireAuth:(BOOL)requireAuth DEPRECATED_API; 177 | 178 | /** 179 | * 用户收藏 (新版本已使用 PAPI_users_favorite_works: 代替) 180 | * bookmark.php (Authentication required) 181 | * 182 | * @param author_id [id for author] 183 | * @param page [1-n] 184 | * @param requireAuth YES - for r18 result 185 | * 186 | * @return NSArray of SAPIIllust 187 | */ 188 | - (NSArray *)SAPI_bookmark:(NSInteger)author_id page:(NSInteger)page requireAuth:(BOOL)requireAuth DEPRECATED_API; 189 | 190 | /** 191 | * 标记书签的用户 192 | * illust_bookmarks.php 193 | * 194 | * @param illust_id [id for illust] 195 | * @param page [1-n] 196 | * @param requireAuth NO 197 | * 198 | * @return NSArray of SAPIIllust(Author) 199 | */ 200 | - (NSArray *)SAPI_illust_bookmarks:(NSInteger)illust_id page:(NSInteger)page requireAuth:(BOOL)requireAuth DEPRECATED_API; 201 | 202 | /** 203 | * 关注 204 | * bookmark_user_all.php (Authentication required) 205 | * 206 | * @param author_id [id for author] 207 | * @param page [1-n] 208 | * 209 | * @return NSArray of SAPIIllust(Author) 210 | */ 211 | - (NSArray *)SAPI_bookmark_user_all:(NSInteger)author_id page:(NSInteger)page requireAuth:(BOOL)requireAuth DEPRECATED_API; 212 | 213 | /** 214 | * 好P友 215 | * mypixiv_all.php 216 | * 217 | * @param author_id [id for author] 218 | * @param page [1-n] 219 | * 220 | * @return NSArray of SAPIIllust(Author) 221 | */ 222 | - (NSArray *)SAPI_mypixiv_all:(NSInteger)author_id page:(NSInteger)page requireAuth:(BOOL)requireAuth DEPRECATED_API; 223 | 224 | 225 | #pragma mark - Public-API exports 226 | 227 | /** 228 | * 作品详细 229 | * works/.json 230 | * 231 | * @return PAPIIllust 232 | */ 233 | - (PAPIIllust *)PAPI_works:(NSInteger)illust_id; 234 | 235 | /** 236 | * 用户资料 237 | * users/.json 238 | * 239 | * @return PAPIAuthor 240 | */ 241 | - (PAPIAuthor *)PAPI_users:(NSInteger)author_id; 242 | 243 | /** 244 | * 我的订阅 245 | * me/feeds.json 246 | * 247 | * @param show_r18 NO - hide r18 illusts 248 | * 249 | * @return PAPIIllustList 250 | */ 251 | - (PAPIIllustList *)PAPI_me_feeds:(BOOL)show_r18; 252 | 253 | /** 254 | * 用户作品列表 255 | * users//works.json 256 | * 257 | * @param page [1-n] 258 | * @param publicity YES - public; NO - private (only auth user) 259 | * 260 | * @return PAPIIllustList 261 | */ 262 | - (PAPIIllustList *)PAPI_users_works:(NSInteger)author_id page:(NSInteger)page publicity:(BOOL)publicity; 263 | 264 | /** 265 | * 用户收藏 266 | * users//favorite_works.json 267 | * 268 | * @param page [1-n] 269 | * @param publicity YES - public; NO - private (only auth user) 270 | * 271 | * @return PAPIIllustList 272 | */ 273 | - (PAPIIllustList *)PAPI_users_favorite_works:(NSInteger)author_id page:(NSInteger)page publicity:(BOOL)publicity; 274 | 275 | /** 276 | * (写)新增收藏 277 | * 278 | * @param illust_id [id for work] 279 | * @param publicity YES - public; NO - private (only auth user) 280 | * 281 | * @return favorite_id for delete 282 | */ 283 | - (NSInteger)PAPI_add_favorite_works:(NSInteger)illust_id publicity:(BOOL)publicity; 284 | 285 | /** 286 | * (写)取消收藏 287 | * 288 | * @param favorite_id id return form PAPI_add_favorite_works: 289 | * 290 | * @return YES - success 291 | */ 292 | - (BOOL)PAPI_del_favorite_works:(NSInteger)favorite_id; 293 | 294 | /** 295 | * 排行榜/过去排行榜 296 | * ranking/all 297 | * 298 | * @param mode [daily, weekly, monthly, male, female, rookie, daily_r18, weekly_r18, male_r18, female_r18, r18g] 299 | * @param page [1-n] 300 | * @param date '2015-04-01' (* ranking_log) 301 | * 302 | * @return PAPIIllustList 303 | */ 304 | - (PAPIIllustList *)PAPI_ranking_all:(NSString *)mode page:(NSInteger)page; 305 | - (PAPIIllustList *)PAPI_ranking_log:(NSString *)mode page:(NSInteger)page date:(NSString *)date; 306 | 307 | @end 308 | -------------------------------------------------------------------------------- /PixivAPI/PixivAPI.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @providesModule PixivAPI 3 | * @flow 4 | */ 5 | 'use strict'; 6 | 7 | var PixivAPI = require('NativeModules').PixivAPI; 8 | 9 | module.exports = { 10 | loginIfNeeded: function(username:string, password:string, callback:Function) { 11 | PixivAPI.loginIfNeeded(username, password, callback); 12 | }, 13 | 14 | // PAPI 15 | PAPI_works: function(illust_id:number, callback:Function) { 16 | PixivAPI.PAPI_works( illust_id, (results) => callback(JSON.parse(results)) ); 17 | }, 18 | PAPI_users: function(author_id:number, callback:Function) { 19 | PixivAPI.PAPI_users( author_id, (results) => callback(JSON.parse(results)) ); 20 | }, 21 | PAPI_me_feeds: function(show_r18:boolean, callback:Function) { 22 | PixivAPI.PAPI_me_feeds( show_r18, (results) => callback(JSON.parse(results)) ); 23 | }, 24 | PAPI_users_works: function(author_id:number, page:number, publicity:boolean, callback:Function) { 25 | PixivAPI.PAPI_users_works( author_id, page, publicity, (results) => callback(JSON.parse(results)) ); 26 | }, 27 | PAPI_users_favorite_works: function(author_id:number, page:number, publicity:boolean, callback:Function) { 28 | PixivAPI.PAPI_users_favorite_works( author_id, page, publicity, (results) => callback(JSON.parse(results)) ); 29 | }, 30 | PAPI_ranking_all: function(mode:string, page:number, callback:Function) { 31 | PixivAPI.PAPI_ranking_all( mode, page, (results) => callback(JSON.parse(results)) ); 32 | }, 33 | PAPI_ranking_log: function(mode:string, page:number, date:string, callback:Function) { 34 | PixivAPI.PAPI_ranking_log( mode, page, date, (results) => callback(JSON.parse(results)) ); 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /PixivAPI/PixivAPI.m: -------------------------------------------------------------------------------- 1 | // 2 | // PixivAPI.m 3 | // 4 | // Created by Zhou Hao on 14-10-8. 5 | // Copyright (c) 2014 Zhou Hao. All rights reserved. 6 | // 7 | 8 | #import "PixivAPI.h" 9 | 10 | @interface PixivAPI() 11 | @property (strong, nonatomic) NSString *login_root; 12 | @property (strong, nonatomic) NSString *sapi_root; 13 | @property (strong, nonatomic) NSString *papi_root; 14 | @property (strong, nonatomic) NSDictionary *default_headers; 15 | @end 16 | 17 | @implementation PixivAPI 18 | 19 | + (PixivAPI *)sharedInstance 20 | { 21 | static dispatch_once_t onceToken; 22 | static PixivAPI *instance = nil; 23 | dispatch_once(&onceToken, ^{ 24 | instance = [[PixivAPI alloc] init]; 25 | }); 26 | return instance; 27 | } 28 | 29 | - (instancetype)init 30 | { 31 | self = [super init]; 32 | 33 | if (self) { 34 | self.login_root = PIXIV_LOGIN_ROOT; 35 | self.sapi_root = PIXIV_SAPI_ROOT; 36 | self.papi_root = PIXIV_PAPI_ROOT; 37 | self.default_headers = PIXIV_DEFAULT_HEADERS; 38 | self.session = nil; 39 | } 40 | return self; 41 | } 42 | 43 | #pragma mark - Asynchronous BlockingQueue 44 | 45 | - (NSOperationQueue *)operationQueue 46 | { 47 | if (!_operationQueue) { 48 | _operationQueue = [[NSOperationQueue alloc] init]; 49 | _operationQueue.maxConcurrentOperationCount = MAX_CONCURRENT_OPERATION_COUNT; 50 | } 51 | return _operationQueue; 52 | } 53 | 54 | - (void)asyncBlockingQueue:(NSOperationQueuePriority)queuePriority operations:(void (^)(void))mainOperations 55 | { 56 | NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ 57 | mainOperations(); 58 | }]; 59 | [operation setQueuePriority:queuePriority]; 60 | [self.operationQueue addOperation:operation]; 61 | } 62 | 63 | - (void)asyncBlockingQueue:(void (^)(void))mainOperations 64 | { 65 | [self asyncBlockingQueue:NSOperationQueuePriorityNormal operations:mainOperations]; 66 | } 67 | 68 | - (void)asyncWaitAllOperationsAreFinished:(void (^)(void))onCompletion 69 | { 70 | dispatch_async(dispatch_get_main_queue(), ^{ 71 | [self.operationQueue waitUntilAllOperationsAreFinished]; 72 | onCompletion(); 73 | }); 74 | } 75 | 76 | - (void)onMainQueue:(void (^)(void))operationBlock 77 | { 78 | [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 79 | operationBlock(); 80 | }]; 81 | } 82 | 83 | #pragma mark - URL Fetcher 84 | 85 | - (NSDictionary *)URLFetch:(NSString *)method url:(NSString *)url 86 | headers:(NSDictionary *)headers params:(NSDictionary *)params data:(NSDictionary *)data 87 | { 88 | NSString *error = nil; 89 | return [self URLFetch:method url:url headers:headers params:params data:data error:&error]; 90 | } 91 | 92 | - (NSDictionary *)URLFetch:(NSString *)method url:(NSString *)url 93 | headers:(NSDictionary *)headers params:(NSDictionary *)params 94 | data:(NSDictionary *)data error:(NSString **)errorResponseMessage 95 | { 96 | *errorResponseMessage = nil; 97 | 98 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; 99 | [request setHTTPMethod:method]; 100 | [request setTimeoutInterval:MAX_PIXIVAPI_FETCH_TIMEOUT]; 101 | 102 | /* headers */ 103 | for (NSString* key in self.default_headers) 104 | [request setValue:[self.default_headers objectForKey:key] forHTTPHeaderField:key]; 105 | // user headers 106 | for (NSString* key in headers) 107 | [request setValue:[headers objectForKey:key] forHTTPHeaderField:key]; 108 | 109 | /* url */ 110 | NSString *request_url = [NSString stringWithString:url]; 111 | NSString *url_params = [PixivAPI _encodeDictionary:params]; 112 | if (url_params) 113 | request_url = [NSString stringWithFormat:@"%@?%@", request_url, url_params]; 114 | [request setURL:[NSURL URLWithString:request_url]]; 115 | 116 | /* body */ 117 | NSString *payload = [PixivAPI _encodeDictionary:data]; 118 | if (payload) 119 | [request setHTTPBody:[payload dataUsingEncoding:NSUTF8StringEncoding]]; 120 | 121 | /* request */ 122 | NSURLResponse *response = nil; 123 | NSError *error = nil; 124 | NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; 125 | if (error) { 126 | NSLog(@"Error sending request: %@", [error localizedDescription]); 127 | *errorResponseMessage = [error localizedDescription]; 128 | return nil; 129 | } 130 | 131 | NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; 132 | if (httpResponse.statusCode != 200) { 133 | NSString *payload = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]; 134 | NSLog(@"Error HTTP %ld:\n%@", (long)httpResponse.statusCode, payload); 135 | 136 | NSError *error = nil; 137 | NSDictionary* errorJsonResult = [NSJSONSerialization JSONObjectWithData:responseData 138 | options:kNilOptions error:&error]; 139 | if (error) { 140 | *errorResponseMessage = error.description; 141 | return nil; 142 | } 143 | *errorResponseMessage = [[[errorJsonResult valueForKey:@"errors"] 144 | valueForKey:@"system"] valueForKey:@"message"]; 145 | return nil; 146 | } 147 | 148 | return [PixivAPI _buildResponse:httpResponse data:responseData]; 149 | } 150 | 151 | + (NSString*)_encodeDictionary:(NSDictionary*)dictionary 152 | { 153 | NSMutableArray *parts = [[NSMutableArray alloc] init]; 154 | for (NSString *key in dictionary) { 155 | id value = [dictionary objectForKey:key]; 156 | if (![value isKindOfClass:[NSString class]]) { 157 | value = [NSString stringWithFormat:@"%@", value]; 158 | } 159 | NSString *encodedValue = [value stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 160 | NSString *encodedKey = [key stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 161 | [parts addObject:[NSString stringWithFormat: @"%@=%@", encodedKey, encodedValue]]; 162 | } 163 | if ([parts count] > 0) { 164 | return [parts componentsJoinedByString:@"&"]; 165 | } else { 166 | return nil; 167 | } 168 | } 169 | 170 | + (NSDictionary *)_buildResponse:(NSHTTPURLResponse *)response data:(NSData *)data 171 | { 172 | return @{ 173 | @"header": [response allHeaderFields], 174 | @"data": data, 175 | @"payload": [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding], 176 | }; 177 | } 178 | 179 | #pragma mark - OAuth Login 180 | 181 | - (NSDictionary *)login:(NSString *)username password:(NSString *)password 182 | { 183 | NSString *error = nil; 184 | return [self login:username password:password error:&error]; 185 | } 186 | 187 | - (NSDictionary *)login:(NSString *)username password:(NSString *)password error:(NSString **)errorMessage 188 | { 189 | *errorMessage = nil; 190 | 191 | NSString *url = self.login_root; 192 | NSDictionary *login_headers = @{ @"Referer": @"http://www.pixiv.net/", }; 193 | 194 | NSDictionary* data = @{ 195 | @"username": username, 196 | @"password": password, 197 | // OAuth login from PixivIOSApp/5.1.1 198 | @"grant_type": @"password", 199 | @"client_id": @"bYGKuGVw91e0NMfPGp44euvGt59s", 200 | @"client_secret": @"HP3RmkgAmEGro0gn1x9ioawQE8WMfvLXDz3ZqxpK", 201 | }; 202 | 203 | NSDictionary *response = [self URLFetch:@"POST" url:url headers:login_headers 204 | params:nil data:data error:errorMessage]; 205 | 206 | if (*errorMessage) { 207 | return nil; 208 | } 209 | 210 | if (!response || (!response[@"data"])) { 211 | *errorMessage = @"response is nil."; 212 | return nil; 213 | } 214 | 215 | // from response.payload get AccessToken 216 | NSError* error; 217 | NSDictionary* json_result = [NSJSONSerialization JSONObjectWithData:response[@"data"] options:kNilOptions error:&error]; 218 | if (error) { 219 | *errorMessage = error.description; 220 | return nil; 221 | } 222 | self.access_token = json_result[@"response"][@"access_token"]; 223 | self.user_id = [json_result[@"response"][@"user"][@"id"] integerValue]; 224 | NSLog(@"AccessToken:%@", self.access_token); 225 | 226 | // from response.header["Set-Cookie"] get PHPSESSID 227 | NSString *raw_cookie = response[@"header"][@"Set-Cookie"]; 228 | for (NSString *cookie in [raw_cookie componentsSeparatedByString:@"; "]) { 229 | NSRange range = [cookie rangeOfString:@"PHPSESSID="]; 230 | if (range.length > 0) { 231 | self.session = [cookie substringFromIndex:range.length]; 232 | NSLog(@"Session:%@", self.session); 233 | break; 234 | } 235 | } 236 | 237 | [self saveAuthToUserDefaults:self.access_token session:self.session username:username user_id:self.user_id]; 238 | return json_result; 239 | } 240 | 241 | - (BOOL)loginIfNeeded:(NSString *)username password:(NSString *)password 242 | { 243 | if (![self loadAuthFromUserDefaults:username]) { 244 | // Auth expired, call login: 245 | NSDictionary *auth = [self login:username password:password]; 246 | if (auth) { 247 | return YES; 248 | } else { 249 | return NO; 250 | } 251 | } else { 252 | // load auth success 253 | return YES; 254 | } 255 | } 256 | 257 | - (void)loginIfNeeded:(NSString *)username password:(NSString *)password error:(NSString **)errorMessage 258 | { 259 | *errorMessage = nil; 260 | 261 | if (![self loadAuthFromUserDefaults:username]) { 262 | [self login:username password:password error:errorMessage]; 263 | } 264 | } 265 | 266 | - (BOOL)_has_auth 267 | { 268 | if (self.access_token && self.session) { 269 | return YES; 270 | } 271 | return NO; 272 | } 273 | 274 | - (void)set_auth:(NSString *)access_token session:(NSString *)session 275 | { 276 | self.access_token = access_token; 277 | self.session = session; 278 | } 279 | 280 | - (void)saveAuthToUserDefaults:(NSString *)access_token session:(NSString *)session username:(NSString *)username user_id:(NSInteger)user_id 281 | { 282 | NSDate *now = [NSDate date]; 283 | NSDate *expire = [now dateByAddingTimeInterval: 3600.0-30.0]; // -30 network timeout sec 284 | 285 | NSDictionary *auth_storage = @{ 286 | @"expired": expire, 287 | @"username": username, 288 | @"user_id": @(user_id), 289 | @"bearer_token": access_token, 290 | @"session": session, 291 | }; 292 | 293 | [[NSUserDefaults standardUserDefaults] setObject:auth_storage forKey:PIXIV_AUTH_STORAGE_KEY]; 294 | [[NSUserDefaults standardUserDefaults] synchronize]; 295 | } 296 | 297 | // return NO if Auth expired 298 | - (BOOL)loadAuthFromUserDefaults:(NSString *)username 299 | { 300 | NSDictionary *auth_storage = [[NSUserDefaults standardUserDefaults] objectForKey:PIXIV_AUTH_STORAGE_KEY]; 301 | if (auth_storage) { 302 | NSDate *now = [NSDate date]; 303 | NSDate *expire = auth_storage[@"expired"]; 304 | if ([expire compare:now] == NSOrderedDescending) { 305 | if (username) { 306 | // if username is set, check username 307 | NSString *last_user = auth_storage[@"username"]; 308 | if (![username isEqualToString:last_user]) { 309 | NSLog(@"found last auth for '%@', but search for user '%@'", last_user, username); 310 | return NO; 311 | } 312 | } 313 | 314 | self.access_token = auth_storage[@"bearer_token"]; 315 | self.session = auth_storage[@"session"]; 316 | if (auth_storage[@"user_id"]) { 317 | self.user_id = [auth_storage[@"user_id"] integerValue]; 318 | } else { 319 | self.user_id = 0; 320 | return NO; // old entry, retry login 321 | } 322 | NSLog(@"find vailed Auth:\nAccessToken=%@\nSession=%@", self.access_token, self.session); 323 | return YES; 324 | } 325 | } 326 | return NO; 327 | } 328 | 329 | - (void)dropAuthFromUserDefaults 330 | { 331 | [[NSUserDefaults standardUserDefaults] removeObjectForKey:PIXIV_AUTH_STORAGE_KEY]; 332 | [[NSUserDefaults standardUserDefaults] synchronize]; 333 | } 334 | 335 | #pragma mark - SAPI common 336 | 337 | // match state for parsePayload() 338 | typedef NS_ENUM(NSInteger, PARSER_STATE) { 339 | PARSER_STATE_NONE = 0, // not match 340 | PARSER_STATE_DQUOTES = 1, // first " 341 | PARSER_STATE_DQUOTES_CLOSE = 2 // check "...(["],)|(["]") 342 | }; 343 | 344 | /** 345 | * Pixiv SAPI result State Machine 346 | * 347 | * @param payload SPAI line record 348 | * 349 | * @return Array of NSString 350 | */ 351 | + (SAPIIllust *)parsePayload:(NSString *)payload 352 | { 353 | PARSER_STATE matchState = PARSER_STATE_NONE; 354 | NSString *token = @""; 355 | NSMutableArray *result = [[NSMutableArray alloc] init]; 356 | 357 | for (int i = 0; i < [payload length]; i++) { 358 | NSString *c = [payload substringWithRange:NSMakeRange(i, 1)]; 359 | 360 | switch (matchState) { 361 | case PARSER_STATE_NONE: 362 | if ([c isEqualToString:@"\""]) { 363 | matchState = PARSER_STATE_DQUOTES; 364 | token = @""; 365 | } else if ([c isEqualToString:@","]) { 366 | [result addObject:token]; 367 | token = @""; 368 | } else { 369 | token = [token stringByAppendingString:c]; 370 | } 371 | break; 372 | 373 | case PARSER_STATE_DQUOTES: 374 | if ([c isEqualToString:@"\""]) { 375 | matchState = PARSER_STATE_DQUOTES_CLOSE; // check 376 | } else { 377 | token = [token stringByAppendingString:c]; 378 | } 379 | break; 380 | 381 | case PARSER_STATE_DQUOTES_CLOSE: 382 | if ([c isEqualToString:@"\""]) { 383 | matchState = PARSER_STATE_DQUOTES; // found "", it's a " in string 384 | [token stringByAppendingString:@"\""]; 385 | } else { 386 | [result addObject:token]; 387 | token = @""; 388 | matchState = PARSER_STATE_NONE; 389 | } 390 | break; 391 | } 392 | } 393 | 394 | return [SAPIIllust parseDataArrayToModel:result]; 395 | } 396 | 397 | + (NSArray *)parsePayloadList:(NSString *)payload 398 | { 399 | NSMutableArray *listResult = [[NSMutableArray alloc] init]; 400 | NSArray *inputLines = [payload componentsSeparatedByString:@"\n"]; 401 | 402 | for (NSString *line in inputLines) { 403 | SAPIIllust *illust = [PixivAPI parsePayload:line]; 404 | if (illust) { 405 | [listResult addObject:illust]; 406 | } 407 | } 408 | 409 | return listResult; 410 | } 411 | 412 | - (SAPIIllust *)_SAPI_URLFetch:(NSString *)api_url params:(NSDictionary *)params requireAuth:(BOOL)requireAuth 413 | { 414 | NSString *url = [NSString stringWithFormat:@"%@%@", self.sapi_root, api_url]; 415 | NSMutableDictionary *req_params = [NSMutableDictionary dictionaryWithDictionary:params]; 416 | 417 | if (requireAuth) { 418 | if (![self _has_auth]) { 419 | NSLog(@"Authentication required! Call login: or set_session: first!"); 420 | return nil; 421 | } 422 | req_params[@"PHPSESSID"] = self.session; 423 | } 424 | 425 | NSDictionary *response = [self URLFetch:@"GET" url:url headers:nil params:req_params data:nil]; 426 | //NSLog(@"pixiv return: %@", response[@"payload"]); 427 | return [PixivAPI parsePayload:response[@"payload"]]; 428 | } 429 | 430 | - (NSArray *)_SAPI_URLFetchList:(NSString *)api_url params:(NSDictionary *)params requireAuth:(BOOL)requireAuth 431 | { 432 | NSString *url = [NSString stringWithFormat:@"%@%@", self.sapi_root, api_url]; 433 | NSMutableDictionary *req_params = [NSMutableDictionary dictionaryWithDictionary:params]; 434 | 435 | if (requireAuth) { 436 | if (![self _has_auth]) { 437 | NSLog(@"Authentication required! Call login: or set_session: first!"); 438 | return nil; 439 | } 440 | req_params[@"PHPSESSID"] = self.session; 441 | } 442 | 443 | NSDictionary *response = [self URLFetch:@"GET" url:url headers:nil params:req_params data:nil]; 444 | //NSLog(@"pixiv return: %@", response[@"payload"]); 445 | return [PixivAPI parsePayloadList:response[@"payload"]]; 446 | } 447 | 448 | #pragma mark - SAPI define 449 | 450 | - (NSArray *)SAPI_ranking:(NSInteger)page mode:(NSString *)mode content:(NSString *)content requireAuth:(BOOL)requireAuth 451 | { 452 | NSString *api_url = @"ranking.php"; 453 | NSDictionary *params = @{ 454 | @"content": content, 455 | @"mode": mode, 456 | @"p": @((page>0) ? page : 1), 457 | }; 458 | return [self _SAPI_URLFetchList:api_url params:params requireAuth:requireAuth]; 459 | } 460 | 461 | - (NSArray *)SAPI_ranking_log:(NSInteger)Date_Year month:(NSInteger)Date_Month day:(NSInteger)Date_Day 462 | mode:(NSString *)mode page:(NSInteger)page requireAuth:(BOOL)requireAuth 463 | { 464 | NSString *api_url = @"ranking_log.php"; 465 | NSDictionary *params = @{ 466 | @"mode": mode, 467 | @"Date_Year": [NSString stringWithFormat:@"%ld", (long)Date_Year], 468 | @"Date_Month": [NSString stringWithFormat:@"%.2ld", (long)Date_Month], 469 | @"Date_Day": [NSString stringWithFormat:@"%.2ld", (long)Date_Day], 470 | @"p": @((page>0) ? page : 1), 471 | }; 472 | return [self _SAPI_URLFetchList:api_url params:params requireAuth:requireAuth]; 473 | } 474 | 475 | - (SAPIIllust *)SAPI_illust:(NSInteger)illust_id requireAuth:(BOOL)requireAuth 476 | { 477 | NSString *api_url = @"illust.php"; 478 | NSDictionary *params = @{ 479 | @"illust_id": @(illust_id), 480 | }; 481 | return [self _SAPI_URLFetch:api_url params:params requireAuth:requireAuth]; 482 | } 483 | 484 | - (NSArray *)SAPI_member_illust:(NSInteger)author_id page:(NSInteger)page requireAuth:(BOOL)requireAuth 485 | { 486 | NSString *api_url = @"member_illust.php"; 487 | NSDictionary *params = @{ 488 | @"id": @(author_id), 489 | @"p": @((page>0) ? page : 1), 490 | }; 491 | return [self _SAPI_URLFetchList:api_url params:params requireAuth:requireAuth]; 492 | } 493 | 494 | - (SAPIIllust *)SAPI_user:(NSInteger)author_id requireAuth:(BOOL)requireAuth 495 | { 496 | NSString *api_url = @"user.php"; 497 | NSDictionary *params = @{ 498 | @"user_id": @(author_id), 499 | @"level": @3, 500 | }; 501 | return [self _SAPI_URLFetch:api_url params:params requireAuth:requireAuth]; 502 | } 503 | 504 | - (NSArray *)SAPI_bookmark:(NSInteger)author_id page:(NSInteger)page requireAuth:(BOOL)requireAuth 505 | { 506 | NSString *api_url = @"bookmark.php"; 507 | NSDictionary *params = @{ 508 | @"id": @(author_id), 509 | @"p": @((page>0) ? page : 1), 510 | }; 511 | return [self _SAPI_URLFetchList:api_url params:params requireAuth:requireAuth]; 512 | } 513 | 514 | - (NSArray *)SAPI_illust_bookmarks:(NSInteger)illust_id page:(NSInteger)page requireAuth:(BOOL)requireAuth 515 | { 516 | NSString *api_url = @"illust_bookmarks.php"; 517 | NSDictionary *params = @{ 518 | @"illust_id": @(illust_id), 519 | @"p": @((page>0) ? page : 1), 520 | }; 521 | return [self _SAPI_URLFetchList:api_url params:params requireAuth:requireAuth]; 522 | } 523 | 524 | - (NSArray *)SAPI_bookmark_user_all:(NSInteger)author_id page:(NSInteger)page requireAuth:(BOOL)requireAuth 525 | { 526 | NSString *api_url = @"bookmark_user_all.php"; 527 | NSDictionary *params = @{ 528 | @"id": @(author_id), 529 | @"p": @((page>0) ? page : 1), 530 | @"rest": @"show", 531 | }; 532 | return [self _SAPI_URLFetchList:api_url params:params requireAuth:requireAuth]; 533 | } 534 | 535 | - (NSArray *)SAPI_mypixiv_all:(NSInteger)author_id page:(NSInteger)page requireAuth:(BOOL)requireAuth 536 | { 537 | NSString *api_url = @"mypixiv_all.php"; 538 | NSDictionary *params = @{ 539 | @"id": @(author_id), 540 | @"p": @((page>0) ? page : 1), 541 | }; 542 | return [self _SAPI_URLFetchList:api_url params:params requireAuth:requireAuth]; 543 | } 544 | 545 | #pragma mark - Public-API common 546 | 547 | - (id)_PAPI_URLFetch:(NSString *)api_url params:(NSDictionary *)params isIllust:(BOOL)isIllust 548 | { 549 | NSString *url = [NSString stringWithFormat:@"%@%@", self.papi_root, api_url]; 550 | 551 | if (![self _has_auth]) { 552 | NSLog(@"Authentication required! Call login: or set_session: first!"); 553 | return nil; 554 | } 555 | NSDictionary *papi_headers = @{ 556 | @"Authorization": [NSString stringWithFormat:@"Bearer %@", self.access_token], 557 | @"Cookie": [NSString stringWithFormat:@"PHPSESSID=%@", self.session], 558 | }; 559 | 560 | NSDictionary *response = [self URLFetch:@"GET" url:url headers:papi_headers params:params data:nil]; 561 | if (!response[@"data"]) { 562 | NSLog(@"GET %@ return data: nil: %@", url, response); 563 | return nil; 564 | } 565 | 566 | NSError* error; 567 | NSDictionary* json_result = [NSJSONSerialization JSONObjectWithData:response[@"data"] options:kNilOptions error:&error]; 568 | //NSLog(@"pixiv json: %@", json_result); 569 | if (error) { 570 | return nil; 571 | } 572 | 573 | if (isIllust) { 574 | return [PAPIIllust parseJsonDictionaryToModel:json_result]; 575 | } else { 576 | return [PAPIAuthor parseJsonDictionaryToModel:json_result]; 577 | } 578 | } 579 | 580 | /** 581 | * @param isWork YES - response is work{}; NO - response wrap and has work key 582 | */ 583 | - (id)_PAPI_URLFetchList:(NSString *)api_url params:(NSDictionary *)params isWork:(BOOL)isWork 584 | { 585 | NSString *url = [NSString stringWithFormat:@"%@%@", self.papi_root, api_url]; 586 | 587 | if (![self _has_auth]) { 588 | NSLog(@"Authentication required! Call login: or set_session: first!"); 589 | return nil; 590 | } 591 | NSDictionary *papi_headers = @{ 592 | @"Authorization": [NSString stringWithFormat:@"Bearer %@", self.access_token], 593 | @"Cookie": [NSString stringWithFormat:@"PHPSESSID=%@", self.session], 594 | }; 595 | 596 | NSDictionary *response = [self URLFetch:@"GET" url:url headers:papi_headers params:params data:nil]; 597 | NSError* error; 598 | NSDictionary* json_result = [NSJSONSerialization JSONObjectWithData:response[@"data"] options:kNilOptions error:&error]; 599 | //NSLog(@"pixiv json: %@", json_result); 600 | if (error) { 601 | return nil; 602 | } 603 | 604 | return [PAPIIllustList parseJsonDictionaryToModelList:json_result isWork:isWork]; 605 | } 606 | 607 | - (NSArray *)_PAPI_URLPost:(NSString *)api_url payload:(NSDictionary *)payload 608 | { 609 | NSString *url = [NSString stringWithFormat:@"%@%@", self.papi_root, api_url]; 610 | 611 | if (![self _has_auth]) { 612 | NSLog(@"Authentication required! Call login: or set_session: first!"); 613 | return nil; 614 | } 615 | NSDictionary *papi_headers = @{ 616 | @"Authorization": [NSString stringWithFormat:@"Bearer %@", self.access_token], 617 | @"Cookie": [NSString stringWithFormat:@"PHPSESSID=%@", self.session], 618 | }; 619 | 620 | NSDictionary *response = [self URLFetch:@"POST" url:url headers:papi_headers params:nil data:payload]; 621 | if (!response[@"data"]) { 622 | NSLog(@"POST %@ return data: nil: %@", url, response); 623 | return nil; 624 | } 625 | 626 | NSError* error; 627 | NSDictionary* json_result = [NSJSONSerialization JSONObjectWithData:response[@"data"] options:kNilOptions error:&error]; 628 | if ((error) || (!json_result[@"status"]) || (![json_result[@"status"] isEqualToString:@"success"])) { 629 | NSLog(@"POST %@ failed: %@", url, json_result); 630 | return nil; 631 | } 632 | 633 | return json_result[@"response"]; 634 | } 635 | 636 | - (BOOL)_PAPI_URLDelete:(NSString *)api_url params:(NSDictionary *)params 637 | { 638 | NSString *url = [NSString stringWithFormat:@"%@%@", self.papi_root, api_url]; 639 | 640 | if (![self _has_auth]) { 641 | NSLog(@"Authentication required! Call login: or set_session: first!"); 642 | return NO; 643 | } 644 | NSDictionary *papi_headers = @{ 645 | @"Authorization": [NSString stringWithFormat:@"Bearer %@", self.access_token], 646 | @"Cookie": [NSString stringWithFormat:@"PHPSESSID=%@", self.session], 647 | }; 648 | 649 | NSDictionary *response = [self URLFetch:@"DELETE" url:url headers:papi_headers params:params data:nil]; 650 | if (!response[@"data"]) { 651 | NSLog(@"POST %@ return data: nil: %@", url, response); 652 | return NO; 653 | } 654 | 655 | NSError* error; 656 | NSDictionary* json_result = [NSJSONSerialization JSONObjectWithData:response[@"data"] options:kNilOptions error:&error]; 657 | if ((error) || (!json_result[@"status"]) || (![json_result[@"status"] isEqualToString:@"success"])) { 658 | NSLog(@"DELETE %@ failed: %@", url, json_result); 659 | return NO; 660 | } 661 | 662 | return YES; 663 | } 664 | 665 | #pragma mark - Public-API define 666 | 667 | - (PAPIIllust *)PAPI_works:(NSInteger)illust_id 668 | { 669 | NSString *api_url = [NSString stringWithFormat:@"works/%lu.json", (unsigned long)illust_id]; 670 | NSDictionary *params = @{ 671 | @"profile_image_sizes": @"px_170x170,px_50x50", 672 | @"image_sizes": @"px_128x128,small,medium,large,px_480mw", 673 | @"include_stats": @"true", 674 | }; 675 | return [self _PAPI_URLFetch:api_url params:params isIllust:YES]; 676 | } 677 | 678 | - (PAPIAuthor *)PAPI_users:(NSInteger)author_id 679 | { 680 | NSString *api_url = [NSString stringWithFormat:@"users/%lu.json", (unsigned long)author_id]; 681 | NSDictionary *params = @{ 682 | @"profile_image_sizes": @"px_170x170,px_50x50", 683 | @"image_sizes": @"px_128x128,small,medium,large,px_480mw", 684 | @"include_stats": @1, 685 | @"include_profile": @1, 686 | @"include_workspace": @1, 687 | @"include_contacts": @1, 688 | }; 689 | return [self _PAPI_URLFetch:api_url params:params isIllust:NO]; 690 | } 691 | 692 | - (PAPIIllustList *)PAPI_me_feeds:(BOOL)show_r18 693 | { 694 | NSString *api_url = @"me/feeds.json"; 695 | NSDictionary *params = @{ 696 | @"relation": @"all", 697 | @"type": @"touch_nottext", 698 | @"show_r18": show_r18 ? @1 : @0, 699 | }; 700 | return [self _PAPI_URLFetchList:api_url params:params isWork:YES]; 701 | } 702 | 703 | - (PAPIIllustList *)PAPI_users_works:(NSInteger)author_id page:(NSInteger)page publicity:(BOOL)publicity 704 | { 705 | NSString *api_url = [NSString stringWithFormat:@"users/%ld/works.json", (unsigned long)author_id]; 706 | NSDictionary *params = @{ 707 | @"page": @(page), 708 | @"per_page": @30, 709 | @"publicity": publicity ? @"public" : @"private", 710 | @"include_work": @"true", 711 | @"include_stats": @"true", 712 | @"image_sizes": @"px_128x128,px_480mw,large", 713 | @"profile_image_sizes": @"px_170x170,px_50x50", 714 | }; 715 | return [self _PAPI_URLFetchList:api_url params:params isWork:YES]; 716 | } 717 | 718 | - (PAPIIllustList *)PAPI_users_favorite_works:(NSInteger)author_id page:(NSInteger)page publicity:(BOOL)publicity 719 | { 720 | NSString *api_url = [NSString stringWithFormat:@"users/%ld/favorite_works.json", (unsigned long)author_id]; 721 | NSDictionary *params = @{ 722 | @"page": @(page), 723 | @"per_page": @30, 724 | @"publicity": publicity ? @"public" : @"private", 725 | @"include_work": @"true", 726 | @"include_stats": @"true", 727 | @"image_sizes": @"px_128x128,small,medium,large,px_480mw", 728 | @"profile_image_sizes": @"px_170x170,px_50x50", 729 | }; 730 | // response has a header outside each work, so set isWork:NO 731 | return [self _PAPI_URLFetchList:api_url params:params isWork:NO]; 732 | } 733 | 734 | - (NSInteger)PAPI_add_favorite_works:(NSInteger)illust_id publicity:(BOOL)publicity 735 | { 736 | NSString *api_url = @"me/favorite_works"; 737 | NSDictionary *payload = @{ 738 | @"work_id": @(illust_id), 739 | @"publicity": publicity ? @"public" : @"private", 740 | }; 741 | 742 | NSArray *response = [self _PAPI_URLPost:api_url payload:payload]; 743 | if ([response firstObject][@"id"]) { 744 | return [[response firstObject][@"id"] integerValue]; 745 | } else { 746 | return PIXIV_ID_INVALID; 747 | } 748 | } 749 | 750 | - (BOOL)PAPI_del_favorite_works:(NSInteger)favorite_id 751 | { 752 | NSString *api_url = @"me/favorite_works"; 753 | NSDictionary *params = @{ 754 | @"ids": @(favorite_id), 755 | }; 756 | return [self _PAPI_URLDelete:api_url params:params]; 757 | } 758 | 759 | - (PAPIIllustList *)PAPI_ranking_all:(NSString *)mode page:(NSInteger)page 760 | { 761 | NSString *api_url = @"ranking/all"; 762 | NSDictionary *params = @{ 763 | @"mode": mode, 764 | @"page": @(page), 765 | @"per_page": @50, 766 | @"image_sizes": @"px_128x128,px_480mw,large", 767 | @"profile_image_sizes": @"px_170x170,px_50x50", 768 | @"include_stats": @"true", 769 | @"include_sanity_level": @"true", 770 | }; 771 | return [self _PAPI_URLFetchList:api_url params:params isWork:NO]; 772 | } 773 | 774 | - (PAPIIllustList *)PAPI_ranking_log:(NSString *)mode page:(NSInteger)page date:(NSString *)date 775 | { 776 | NSString *api_url = @"ranking/all"; 777 | NSDictionary *params = @{ 778 | @"mode": mode, 779 | @"page": @(page), 780 | @"per_page": @50, 781 | @"date": date, 782 | @"image_sizes": @"px_128x128,px_480mw,large", 783 | @"profile_image_sizes": @"px_170x170,px_50x50", 784 | @"include_stats": @"true", 785 | @"include_sanity_level": @"true", 786 | }; 787 | return [self _PAPI_URLFetchList:api_url params:params isWork:NO]; 788 | } 789 | 790 | @end 791 | -------------------------------------------------------------------------------- /PixivAPI/PixivDefines.h: -------------------------------------------------------------------------------- 1 | // 2 | // PixivDefines.h 3 | // 4 | // Created by Zhou Hao on 14/10/20. 5 | // Copyright (c) 2014 Zhou Hao. All rights reserved. 6 | // 7 | 8 | #import "SAPIIllust.h" 9 | #import "PAPIAuthor.h" 10 | #import "PAPIIllust.h" 11 | #import "PAPIIllustList.h" 12 | 13 | #pragma mark - configs 14 | 15 | // NSOperationQueue maxConcurrentOperationCount define 16 | #define MAX_CONCURRENT_OPERATION_COUNT (2) 17 | // API fetch timeout 18 | #define MAX_PIXIVAPI_FETCH_TIMEOUT (30) 19 | 20 | 21 | #pragma mark - defaults 22 | 23 | // return value if a ingeter field is NSNull 24 | #define PIXIV_INT_INVALID (-1) 25 | 26 | // Auth key for NSUserDefaults 27 | #define PIXIV_AUTH_STORAGE_KEY @"PixivAPI_Auth" 28 | 29 | // API URLs 30 | #define PIXIV_LOGIN_ROOT @"https://oauth.secure.pixiv.net/auth/token" 31 | #define PIXIV_SAPI_ROOT @"http://spapi.pixiv.net/iphone/" 32 | #define PIXIV_PAPI_ROOT @"https://public-api.secure.pixiv.net/v1/" 33 | 34 | #define PIXIV_DEFAULT_HEADERS @{ \ 35 | @"Referer": @"http://spapi.pixiv.net/", \ 36 | @"User-Agent": @"PixivIOSApp/5.1.1", \ 37 | @"Content-Type": @"application/x-www-form-urlencoded", \ 38 | } 39 | -------------------------------------------------------------------------------- /PixivAPI/RCTPixivAPI.h: -------------------------------------------------------------------------------- 1 | #import "RCTBridgeModule.h" 2 | #import "PixivAPI.h" 3 | 4 | @interface RCTPixivAPI : NSObject 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /PixivAPI/RCTPixivAPI.m: -------------------------------------------------------------------------------- 1 | #import "RCTPixivAPI.h" 2 | 3 | @implementation RCTPixivAPI 4 | 5 | RCT_EXPORT_MODULE() 6 | 7 | #define PAPI_CALL(...) do { \ 8 | NSDictionary *object = [[[PixivAPI sharedInstance] __VA_ARGS__] toObject]; \ 9 | callback(@[[RCTPixivAPI toJSONString:object]]); \ 10 | } while(0) 11 | 12 | #define PAPI_LIST_CALL(...) do { \ 13 | NSArray *array = [[[PixivAPI sharedInstance] __VA_ARGS__] toObjectList]; \ 14 | callback(@[[RCTPixivAPI toJSONString:array]]); \ 15 | } while(0) 16 | 17 | #pragma mark - JSON helper 18 | 19 | + (NSString *)toJSONString:(id)data 20 | { 21 | NSError *error; 22 | NSData *jsonData = [NSJSONSerialization dataWithJSONObject:data options:0 error:&error]; 23 | return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 24 | } 25 | 26 | #pragma mark - Common exports 27 | 28 | RCT_EXPORT_METHOD(loginIfNeeded:(NSString *)username password:(NSString *)password 29 | callback:(RCTResponseSenderBlock)callback) 30 | { 31 | BOOL success = [[PixivAPI sharedInstance] loginIfNeeded:username password:password]; 32 | callback(@[[NSNumber numberWithBool:success]]); 33 | } 34 | 35 | #pragma mark - PAPI exports 36 | 37 | RCT_EXPORT_METHOD(PAPI_works:(NSInteger)illust_id callback:(RCTResponseSenderBlock)callback) 38 | { 39 | PAPI_CALL(PAPI_works:illust_id); 40 | } 41 | 42 | RCT_EXPORT_METHOD(PAPI_users:(NSInteger)author_id callback:(RCTResponseSenderBlock)callback) 43 | { 44 | PAPI_CALL(PAPI_users:author_id); 45 | } 46 | 47 | RCT_EXPORT_METHOD(PAPI_me_feeds:(BOOL)show_r18 callback:(RCTResponseSenderBlock)callback) 48 | { 49 | PAPI_LIST_CALL(PAPI_me_feeds:show_r18); 50 | } 51 | 52 | RCT_EXPORT_METHOD(PAPI_users_works:(NSInteger)author_id page:(NSInteger)page publicity:(BOOL)publicity callback:(RCTResponseSenderBlock)callback) 53 | { 54 | PAPI_LIST_CALL(PAPI_users_works:author_id page:page publicity:publicity); 55 | } 56 | 57 | RCT_EXPORT_METHOD(PAPI_users_favorite_works:(NSInteger)author_id page:(NSInteger)page publicity:(BOOL)publicity callback:(RCTResponseSenderBlock)callback) 58 | { 59 | PAPI_LIST_CALL(PAPI_users_favorite_works:author_id page:page publicity:publicity); 60 | } 61 | 62 | RCT_EXPORT_METHOD(PAPI_ranking_all:(NSString *)mode page:(NSInteger)page callback:(RCTResponseSenderBlock)callback) 63 | { 64 | PAPI_LIST_CALL(PAPI_ranking_all:mode page:page); 65 | } 66 | 67 | RCT_EXPORT_METHOD(PAPI_ranking_log:(NSString *)mode page:(NSInteger)page date:(NSString *)date callback:(RCTResponseSenderBlock)callback) 68 | { 69 | PAPI_LIST_CALL(PAPI_ranking_log:mode page:page date:date); 70 | } 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /PixivAPI/RCTPixivAPI.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 832C819C1AAF6E1A007FA2F7 /* RCTPixivAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = 832C819B1AAF6E1A007FA2F7 /* RCTPixivAPI.m */; }; 11 | CB619ABE1ADFFE3D009E6244 /* PAPIAuthor.m in Sources */ = {isa = PBXBuildFile; fileRef = CB619AB31ADFFE3D009E6244 /* PAPIAuthor.m */; }; 12 | CB619ABF1ADFFE3D009E6244 /* PAPIIllust.m in Sources */ = {isa = PBXBuildFile; fileRef = CB619AB51ADFFE3D009E6244 /* PAPIIllust.m */; }; 13 | CB619AC01ADFFE3D009E6244 /* PAPIIllustList.m in Sources */ = {isa = PBXBuildFile; fileRef = CB619AB71ADFFE3D009E6244 /* PAPIIllustList.m */; }; 14 | CB619AC11ADFFE3D009E6244 /* PixivAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = CB619ABA1ADFFE3D009E6244 /* PixivAPI.m */; }; 15 | CB619AC21ADFFE3D009E6244 /* SAPIIllust.m in Sources */ = {isa = PBXBuildFile; fileRef = CB619ABD1ADFFE3D009E6244 /* SAPIIllust.m */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 832C817E1AAF6DEF007FA2F7 /* CopyFiles */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 2147483647; 22 | dstPath = "include/$(PRODUCT_NAME)"; 23 | dstSubfolderSpec = 16; 24 | files = ( 25 | ); 26 | runOnlyForDeploymentPostprocessing = 0; 27 | }; 28 | /* End PBXCopyFilesBuildPhase section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | 832C81801AAF6DEF007FA2F7 /* libRCTPixivAPI.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTPixivAPI.a; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | 832C819A1AAF6E1A007FA2F7 /* RCTPixivAPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPixivAPI.h; sourceTree = ""; }; 33 | 832C819B1AAF6E1A007FA2F7 /* RCTPixivAPI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPixivAPI.m; sourceTree = ""; }; 34 | CB09404F1B2B29D600F671ED /* PixivAPI.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = PixivAPI.js; sourceTree = ""; }; 35 | CB619AB21ADFFE3D009E6244 /* PAPIAuthor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PAPIAuthor.h; sourceTree = ""; }; 36 | CB619AB31ADFFE3D009E6244 /* PAPIAuthor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PAPIAuthor.m; sourceTree = ""; }; 37 | CB619AB41ADFFE3D009E6244 /* PAPIIllust.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PAPIIllust.h; sourceTree = ""; }; 38 | CB619AB51ADFFE3D009E6244 /* PAPIIllust.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PAPIIllust.m; sourceTree = ""; }; 39 | CB619AB61ADFFE3D009E6244 /* PAPIIllustList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PAPIIllustList.h; sourceTree = ""; }; 40 | CB619AB71ADFFE3D009E6244 /* PAPIIllustList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PAPIIllustList.m; sourceTree = ""; }; 41 | CB619AB81ADFFE3D009E6244 /* PixivAPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PixivAPI.h; sourceTree = ""; }; 42 | CB619ABA1ADFFE3D009E6244 /* PixivAPI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PixivAPI.m; sourceTree = ""; }; 43 | CB619ABB1ADFFE3D009E6244 /* PixivDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PixivDefines.h; sourceTree = ""; }; 44 | CB619ABC1ADFFE3D009E6244 /* SAPIIllust.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SAPIIllust.h; sourceTree = ""; }; 45 | CB619ABD1ADFFE3D009E6244 /* SAPIIllust.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SAPIIllust.m; sourceTree = ""; }; 46 | /* End PBXFileReference section */ 47 | 48 | /* Begin PBXFrameworksBuildPhase section */ 49 | 832C817D1AAF6DEF007FA2F7 /* Frameworks */ = { 50 | isa = PBXFrameworksBuildPhase; 51 | buildActionMask = 2147483647; 52 | files = ( 53 | ); 54 | runOnlyForDeploymentPostprocessing = 0; 55 | }; 56 | /* End PBXFrameworksBuildPhase section */ 57 | 58 | /* Begin PBXGroup section */ 59 | 832C81771AAF6DEF007FA2F7 = { 60 | isa = PBXGroup; 61 | children = ( 62 | CB619AC31ADFFE4F009E6244 /* PixivAPI */, 63 | 832C819A1AAF6E1A007FA2F7 /* RCTPixivAPI.h */, 64 | 832C819B1AAF6E1A007FA2F7 /* RCTPixivAPI.m */, 65 | 832C81811AAF6DEF007FA2F7 /* Products */, 66 | ); 67 | indentWidth = 2; 68 | sourceTree = ""; 69 | tabWidth = 2; 70 | }; 71 | 832C81811AAF6DEF007FA2F7 /* Products */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 832C81801AAF6DEF007FA2F7 /* libRCTPixivAPI.a */, 75 | ); 76 | name = Products; 77 | sourceTree = ""; 78 | }; 79 | CB619AC31ADFFE4F009E6244 /* PixivAPI */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | CB09404F1B2B29D600F671ED /* PixivAPI.js */, 83 | CB619AB21ADFFE3D009E6244 /* PAPIAuthor.h */, 84 | CB619AB31ADFFE3D009E6244 /* PAPIAuthor.m */, 85 | CB619AB41ADFFE3D009E6244 /* PAPIIllust.h */, 86 | CB619AB51ADFFE3D009E6244 /* PAPIIllust.m */, 87 | CB619AB61ADFFE3D009E6244 /* PAPIIllustList.h */, 88 | CB619AB71ADFFE3D009E6244 /* PAPIIllustList.m */, 89 | CB619AB81ADFFE3D009E6244 /* PixivAPI.h */, 90 | CB619ABA1ADFFE3D009E6244 /* PixivAPI.m */, 91 | CB619ABB1ADFFE3D009E6244 /* PixivDefines.h */, 92 | CB619ABC1ADFFE3D009E6244 /* SAPIIllust.h */, 93 | CB619ABD1ADFFE3D009E6244 /* SAPIIllust.m */, 94 | ); 95 | name = PixivAPI; 96 | sourceTree = ""; 97 | }; 98 | /* End PBXGroup section */ 99 | 100 | /* Begin PBXNativeTarget section */ 101 | 832C817F1AAF6DEF007FA2F7 /* RCTPixivAPI */ = { 102 | isa = PBXNativeTarget; 103 | buildConfigurationList = 832C81941AAF6DF0007FA2F7 /* Build configuration list for PBXNativeTarget "RCTPixivAPI" */; 104 | buildPhases = ( 105 | 832C817C1AAF6DEF007FA2F7 /* Sources */, 106 | 832C817D1AAF6DEF007FA2F7 /* Frameworks */, 107 | 832C817E1AAF6DEF007FA2F7 /* CopyFiles */, 108 | ); 109 | buildRules = ( 110 | ); 111 | dependencies = ( 112 | ); 113 | name = RCTPixivAPI; 114 | productName = RCTPixivAPI; 115 | productReference = 832C81801AAF6DEF007FA2F7 /* libRCTPixivAPI.a */; 116 | productType = "com.apple.product-type.library.static"; 117 | }; 118 | /* End PBXNativeTarget section */ 119 | 120 | /* Begin PBXProject section */ 121 | 832C81781AAF6DEF007FA2F7 /* Project object */ = { 122 | isa = PBXProject; 123 | attributes = { 124 | LastUpgradeCheck = 0620; 125 | ORGANIZATIONNAME = Facebook; 126 | TargetAttributes = { 127 | 832C817F1AAF6DEF007FA2F7 = { 128 | CreatedOnToolsVersion = 6.2; 129 | }; 130 | }; 131 | }; 132 | buildConfigurationList = 832C817B1AAF6DEF007FA2F7 /* Build configuration list for PBXProject "RCTPixivAPI" */; 133 | compatibilityVersion = "Xcode 3.2"; 134 | developmentRegion = English; 135 | hasScannedForEncodings = 0; 136 | knownRegions = ( 137 | en, 138 | ); 139 | mainGroup = 832C81771AAF6DEF007FA2F7; 140 | productRefGroup = 832C81811AAF6DEF007FA2F7 /* Products */; 141 | projectDirPath = ""; 142 | projectRoot = ""; 143 | targets = ( 144 | 832C817F1AAF6DEF007FA2F7 /* RCTPixivAPI */, 145 | ); 146 | }; 147 | /* End PBXProject section */ 148 | 149 | /* Begin PBXSourcesBuildPhase section */ 150 | 832C817C1AAF6DEF007FA2F7 /* Sources */ = { 151 | isa = PBXSourcesBuildPhase; 152 | buildActionMask = 2147483647; 153 | files = ( 154 | CB619ABE1ADFFE3D009E6244 /* PAPIAuthor.m in Sources */, 155 | CB619ABF1ADFFE3D009E6244 /* PAPIIllust.m in Sources */, 156 | CB619AC11ADFFE3D009E6244 /* PixivAPI.m in Sources */, 157 | CB619AC21ADFFE3D009E6244 /* SAPIIllust.m in Sources */, 158 | CB619AC01ADFFE3D009E6244 /* PAPIIllustList.m in Sources */, 159 | 832C819C1AAF6E1A007FA2F7 /* RCTPixivAPI.m in Sources */, 160 | ); 161 | runOnlyForDeploymentPostprocessing = 0; 162 | }; 163 | /* End PBXSourcesBuildPhase section */ 164 | 165 | /* Begin XCBuildConfiguration section */ 166 | 832C81921AAF6DF0007FA2F7 /* Debug */ = { 167 | isa = XCBuildConfiguration; 168 | buildSettings = { 169 | ALWAYS_SEARCH_USER_PATHS = NO; 170 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 171 | CLANG_CXX_LIBRARY = "libc++"; 172 | CLANG_ENABLE_MODULES = YES; 173 | CLANG_ENABLE_OBJC_ARC = YES; 174 | CLANG_WARN_BOOL_CONVERSION = YES; 175 | CLANG_WARN_CONSTANT_CONVERSION = YES; 176 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 177 | CLANG_WARN_EMPTY_BODY = YES; 178 | CLANG_WARN_ENUM_CONVERSION = YES; 179 | CLANG_WARN_INT_CONVERSION = YES; 180 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 181 | CLANG_WARN_UNREACHABLE_CODE = YES; 182 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 183 | COPY_PHASE_STRIP = NO; 184 | ENABLE_STRICT_OBJC_MSGSEND = YES; 185 | GCC_C_LANGUAGE_STANDARD = gnu99; 186 | GCC_DYNAMIC_NO_PIC = NO; 187 | GCC_OPTIMIZATION_LEVEL = 0; 188 | GCC_PREPROCESSOR_DEFINITIONS = ( 189 | "DEBUG=1", 190 | "$(inherited)", 191 | ); 192 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 193 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 194 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 195 | GCC_WARN_UNDECLARED_SELECTOR = YES; 196 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 197 | GCC_WARN_UNUSED_FUNCTION = YES; 198 | GCC_WARN_UNUSED_VARIABLE = YES; 199 | HEADER_SEARCH_PATHS = ( 200 | "$(inherited)", 201 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 202 | "$(SRCROOT)/../../React/**", 203 | ); 204 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 205 | MTL_ENABLE_DEBUG_INFO = YES; 206 | ONLY_ACTIVE_ARCH = YES; 207 | SDKROOT = iphoneos; 208 | }; 209 | name = Debug; 210 | }; 211 | 832C81931AAF6DF0007FA2F7 /* Release */ = { 212 | isa = XCBuildConfiguration; 213 | buildSettings = { 214 | ALWAYS_SEARCH_USER_PATHS = NO; 215 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 216 | CLANG_CXX_LIBRARY = "libc++"; 217 | CLANG_ENABLE_MODULES = YES; 218 | CLANG_ENABLE_OBJC_ARC = YES; 219 | CLANG_WARN_BOOL_CONVERSION = YES; 220 | CLANG_WARN_CONSTANT_CONVERSION = YES; 221 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 222 | CLANG_WARN_EMPTY_BODY = YES; 223 | CLANG_WARN_ENUM_CONVERSION = YES; 224 | CLANG_WARN_INT_CONVERSION = YES; 225 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 226 | CLANG_WARN_UNREACHABLE_CODE = YES; 227 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 228 | COPY_PHASE_STRIP = NO; 229 | ENABLE_NS_ASSERTIONS = NO; 230 | ENABLE_STRICT_OBJC_MSGSEND = YES; 231 | GCC_C_LANGUAGE_STANDARD = gnu99; 232 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 233 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 234 | GCC_WARN_UNDECLARED_SELECTOR = YES; 235 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 236 | GCC_WARN_UNUSED_FUNCTION = YES; 237 | GCC_WARN_UNUSED_VARIABLE = YES; 238 | HEADER_SEARCH_PATHS = ( 239 | "$(inherited)", 240 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 241 | "$(SRCROOT)/../../React/**", 242 | ); 243 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 244 | MTL_ENABLE_DEBUG_INFO = NO; 245 | SDKROOT = iphoneos; 246 | VALIDATE_PRODUCT = YES; 247 | }; 248 | name = Release; 249 | }; 250 | 832C81951AAF6DF0007FA2F7 /* Debug */ = { 251 | isa = XCBuildConfiguration; 252 | buildSettings = { 253 | HEADER_SEARCH_PATHS = ( 254 | "$(inherited)", 255 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 256 | "$(SRCROOT)/../../node_modules/react-native/React/**", 257 | ); 258 | OTHER_LDFLAGS = "-ObjC"; 259 | PRODUCT_NAME = "$(TARGET_NAME)"; 260 | SKIP_INSTALL = YES; 261 | }; 262 | name = Debug; 263 | }; 264 | 832C81961AAF6DF0007FA2F7 /* Release */ = { 265 | isa = XCBuildConfiguration; 266 | buildSettings = { 267 | HEADER_SEARCH_PATHS = ( 268 | "$(inherited)", 269 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 270 | "$(SRCROOT)/../../node_modules/react-native/React/**", 271 | ); 272 | OTHER_LDFLAGS = "-ObjC"; 273 | PRODUCT_NAME = "$(TARGET_NAME)"; 274 | SKIP_INSTALL = YES; 275 | }; 276 | name = Release; 277 | }; 278 | /* End XCBuildConfiguration section */ 279 | 280 | /* Begin XCConfigurationList section */ 281 | 832C817B1AAF6DEF007FA2F7 /* Build configuration list for PBXProject "RCTPixivAPI" */ = { 282 | isa = XCConfigurationList; 283 | buildConfigurations = ( 284 | 832C81921AAF6DF0007FA2F7 /* Debug */, 285 | 832C81931AAF6DF0007FA2F7 /* Release */, 286 | ); 287 | defaultConfigurationIsVisible = 0; 288 | defaultConfigurationName = Release; 289 | }; 290 | 832C81941AAF6DF0007FA2F7 /* Build configuration list for PBXNativeTarget "RCTPixivAPI" */ = { 291 | isa = XCConfigurationList; 292 | buildConfigurations = ( 293 | 832C81951AAF6DF0007FA2F7 /* Debug */, 294 | 832C81961AAF6DF0007FA2F7 /* Release */, 295 | ); 296 | defaultConfigurationIsVisible = 0; 297 | defaultConfigurationName = Release; 298 | }; 299 | /* End XCConfigurationList section */ 300 | }; 301 | rootObject = 832C81781AAF6DEF007FA2F7 /* Project object */; 302 | } 303 | -------------------------------------------------------------------------------- /PixivAPI/SAPIIllust.h: -------------------------------------------------------------------------------- 1 | // 2 | // SAPIIllust.h 3 | // 4 | // Created by Zhou Hao on 14/10/19. 5 | // Copyright (c) 2014 Zhou Hao. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | #define PIXIV_ID_INVALID (0) 11 | #define MIN_PIXIV_RECORD_FIELDS_NUM (26) // AuthorModel(26) / IllustBaseInfo(30) 12 | 13 | @interface SAPIIllust : NSObject 14 | 15 | // Model to NSArray 16 | - (NSArray *)toDataArray; 17 | // Model to Object 18 | - (NSDictionary *)toObject; 19 | 20 | /** 21 | * Parse NSArray of SAPI result to SAPIIllust 22 | * 23 | * @param NSArray of SAPI result 24 | * 25 | * @return SAPIIllust object 26 | */ 27 | + (SAPIIllust *)parseDataArrayToModel:(NSArray *)data; 28 | 29 | #pragma mark - Author / Illust common 30 | 31 | @property (strong, nonatomic) NSString *raw; // raw data 32 | 33 | @property (nonatomic) NSInteger authorId; // data[1] 34 | @property (strong, nonatomic) NSString *authorName; // data[5] 35 | @property (strong, nonatomic) NSString *thumbURL; // data[6] 36 | @property (strong, nonatomic) NSString *username; // data[24] 37 | 38 | // return Referer for Image donwload 39 | - (NSString *)refererURL; 40 | 41 | #pragma mark - Illust properties 42 | 43 | @property (nonatomic) NSInteger illustId; // data[0] 44 | @property (strong, nonatomic) NSString *ext; // data[2] 45 | @property (strong, nonatomic) NSString *title; // data[3] 46 | @property (strong, nonatomic) NSString *server; // data[4] 47 | @property (strong, nonatomic) NSString *mobileURL; // data[9] 48 | @property (strong, nonatomic) NSString *date; // data[12] 49 | @property (strong, nonatomic) NSArray *tags; // data[13] of NSString 50 | @property (strong, nonatomic) NSString *tool; // data[14] 51 | @property (nonatomic) NSInteger feedbacks; // data[15] 52 | @property (nonatomic) NSInteger points; // data[16] 53 | @property (nonatomic) NSInteger views; // data[17] 54 | @property (strong, nonatomic) NSString *comment; // data[18] 55 | @property (nonatomic) NSInteger pages; // data[19] 56 | @property (nonatomic) NSInteger bookmarks; // data[22] 57 | @property (nonatomic) NSInteger r18; // data[26] 58 | 59 | @property (nonatomic) NSString *head; // data[29] (illust only) 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /PixivAPI/SAPIIllust.m: -------------------------------------------------------------------------------- 1 | // 2 | // SAPIIllust.m 3 | // 4 | // Created by Zhou Hao on 14/10/19. 5 | // Copyright (c) 2014 Zhou Hao. All rights reserved. 6 | // 7 | 8 | #import "SAPIIllust.h" 9 | 10 | // URL for page Referer 11 | #define PIXIV_PAGE_URL @"http://www.pixiv.net/" 12 | #define PIXIV_ILLUST_PAGE_URL @"http://www.pixiv.net/member_illust.php?mode=medium&illust_id=" 13 | #define PIXIV_MEMBER_PAGE_URL @"http://www.pixiv.net/member.php?id=" 14 | 15 | @implementation SAPIIllust 16 | 17 | - (NSString *)description 18 | { 19 | if (self.illustId != PIXIV_ID_INVALID) { 20 | return [NSString stringWithFormat:@"Illust: [%@(id=%lu)] %@(id=%lu): %@", 21 | self.authorName, (unsigned long)self.authorId, self.title, (unsigned long)self.illustId, self.refererURL]; 22 | } else { 23 | return [NSString stringWithFormat:@"Author: %@(id=%lu): %@", self.authorName, (unsigned long)self.authorId, self.refererURL]; 24 | } 25 | } 26 | 27 | - (NSArray *)toDataArray 28 | { 29 | NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:MIN_PIXIV_RECORD_FIELDS_NUM]; 30 | 31 | [array setObject:[NSString stringWithFormat:@"%ld", (long)self.illustId] atIndexedSubscript:0]; 32 | [array setObject:[NSString stringWithFormat:@"%ld", (long)self.authorId] atIndexedSubscript:1]; 33 | [array setObject:self.ext atIndexedSubscript:2]; 34 | [array setObject:self.title atIndexedSubscript:3]; 35 | [array setObject:self.server atIndexedSubscript:4]; 36 | [array setObject:self.authorName atIndexedSubscript:5]; 37 | [array setObject:self.thumbURL atIndexedSubscript:6]; 38 | [array setObject:@"" atIndexedSubscript:7]; 39 | [array setObject:@"" atIndexedSubscript:8]; 40 | [array setObject:self.mobileURL atIndexedSubscript:9]; 41 | [array setObject:@"" atIndexedSubscript:10]; 42 | [array setObject:@"" atIndexedSubscript:11]; 43 | [array setObject:self.date atIndexedSubscript:12]; 44 | [array setObject:[self.tags componentsJoinedByString:@" "] atIndexedSubscript:13]; 45 | [array setObject:self.tool atIndexedSubscript:14]; 46 | [array setObject:[NSString stringWithFormat:@"%ld", (long)self.feedbacks] atIndexedSubscript:15]; 47 | [array setObject:[NSString stringWithFormat:@"%ld", (long)self.points] atIndexedSubscript:16]; 48 | [array setObject:[NSString stringWithFormat:@"%ld", (long)self.views] atIndexedSubscript:17]; 49 | [array setObject:self.comment atIndexedSubscript:18]; 50 | [array setObject:[NSString stringWithFormat:@"%ld", (long)self.pages] atIndexedSubscript:19]; 51 | [array setObject:@"" atIndexedSubscript:20]; 52 | [array setObject:@"" atIndexedSubscript:21]; 53 | [array setObject:[NSString stringWithFormat:@"%ld", (long)self.bookmarks] atIndexedSubscript:22]; 54 | [array setObject:@"" atIndexedSubscript:23]; 55 | [array setObject:self.username atIndexedSubscript:24]; 56 | [array setObject:@"" atIndexedSubscript:25]; 57 | [array setObject:[NSString stringWithFormat:@"%ld", (long)self.r18] atIndexedSubscript:26]; 58 | 59 | //for (int i = ; i < MIN_PIXIV_RECORD_FIELDS_NUM; i++) 60 | // [array setObject:@"" atIndexedSubscript:i]; 61 | 62 | return array; 63 | } 64 | 65 | - (NSDictionary *)toObject 66 | { 67 | NSMutableDictionary *jsonDict = [[NSMutableDictionary alloc] init]; 68 | 69 | #define RawField(f) [jsonDict setObject:self.f forKey:@#f] 70 | #define IntegerField(f) [jsonDict setObject:[NSNumber numberWithInteger:self.f] forKey:@#f] 71 | 72 | IntegerField(illustId); 73 | IntegerField(authorId); 74 | RawField(ext); 75 | RawField(title); 76 | RawField(server); 77 | RawField(authorName); 78 | RawField(thumbURL); 79 | RawField(mobileURL); 80 | RawField(date); 81 | RawField(tags); 82 | RawField(tool); 83 | IntegerField(feedbacks); 84 | IntegerField(points); 85 | IntegerField(views); 86 | RawField(comment); 87 | IntegerField(pages); 88 | IntegerField(bookmarks); 89 | RawField(username); 90 | IntegerField(r18); 91 | RawField(head); 92 | 93 | return jsonDict; 94 | } 95 | 96 | + (SAPIIllust *)parseDataArrayToModel:(NSArray *)data 97 | { 98 | if ([data count] < MIN_PIXIV_RECORD_FIELDS_NUM) 99 | return nil; 100 | 101 | SAPIIllust *illust = [[SAPIIllust alloc] init]; 102 | 103 | illust.raw = [data componentsJoinedByString:@","]; 104 | 105 | illust.illustId = [(NSString *)data[0] intValue]; 106 | illust.authorId = [(NSString *)data[1] intValue]; 107 | illust.ext = data[2]; 108 | illust.title = data[3]; 109 | illust.server = data[4]; 110 | illust.authorName = data[5]; 111 | illust.thumbURL = data[6]; 112 | illust.mobileURL = data[9]; 113 | illust.date = data[12]; 114 | illust.tags = [data[13] componentsSeparatedByString:@" "]; 115 | illust.tool = data[14]; 116 | illust.feedbacks = [(NSString *)data[15] intValue]; 117 | illust.points = [(NSString *)data[16] intValue]; 118 | illust.views = [(NSString *)data[17] intValue]; 119 | illust.comment = data[18]; 120 | illust.pages = [(NSString *)data[19] intValue]; 121 | illust.bookmarks = [(NSString *)data[22] intValue]; 122 | illust.username = data[24]; 123 | 124 | illust.r18 = 0; 125 | if ([data count] > 26) 126 | illust.r18 = [(NSString *)data[26] intValue]; 127 | illust.head = nil; 128 | if ([data count] > 29) 129 | illust.head = data[29]; 130 | 131 | return illust; 132 | } 133 | 134 | - (NSString *)refererURL 135 | { 136 | if (self.illustId != PIXIV_ID_INVALID) 137 | return [NSString stringWithFormat:@"%@%lu", PIXIV_ILLUST_PAGE_URL, (unsigned long)self.illustId]; 138 | else if (self.authorId != PIXIV_ID_INVALID) 139 | return [NSString stringWithFormat:@"%@%lu", PIXIV_MEMBER_PAGE_URL, (unsigned long)self.authorId]; 140 | else 141 | return PIXIV_PAGE_URL; 142 | } 143 | 144 | #pragma mark - NSCoding 145 | 146 | - (id)initWithCoder:(NSCoder *)aDecoder 147 | { 148 | self = [SAPIIllust parseDataArrayToModel:[aDecoder decodeObjectForKey:@"data"]]; 149 | if (!self) { 150 | return nil; 151 | } 152 | 153 | self.raw = [aDecoder decodeObjectForKey:@"raw"]; 154 | return self; 155 | } 156 | 157 | - (void)encodeWithCoder:(NSCoder *)aCoder 158 | { 159 | [aCoder encodeObject:self.raw forKey:@"raw"]; 160 | 161 | NSArray *dataArray = [self toDataArray]; 162 | [aCoder encodeObject:dataArray forKey:@"data"]; 163 | } 164 | 165 | @end 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Pixiv API for iOS 2 | ============ 3 | 4 | Pixiv API for iOS, supported both SAPI and Public-API (with OAuth / Bearer token) 5 | 6 | * [2015/04/29] add React Native support 7 | * [2014/10/19] new NSOperationQueue for async request, add Public-API return Model 8 | 9 | ## Pixiv API Usage: 10 | 11 | 1. Simplely drag 'PixivAPI/' to your project (copy if needed); 12 | 2. Add **#import "PixivAPI/PixivAPI.h"** to your project; 13 | 3. call **login:** and **SAPI / PAPI** functions for you needed; 14 | 15 | ## API example 16 | 17 | Pixiv has two API interface: 18 | 19 | 1. **SAPI:** [http] spapi.pixiv.net/iphone/ 20 | 2. **Public-API:** [https] public-api.secure.pixiv.net/ 21 | 22 | Public-API return full json data, but check Authorization/Cookie in HTTPS header. Before use Public-API, you need call **login:** or **set_session:** to set PHPSESSID. 23 | 24 | 25 | ### Login 26 | 27 | Both loginIfNeeded: / login: are used to Pixiv OAuth, but loginIfNeeded: auto save/load auth token from NSUserDefaults. 28 | so I recommend that use loginIfNeeded: when you need login: 29 | 30 | **login** (sync): 31 | 32 | ```objective-c 33 | // sync login 34 | [[PixivAPI sharedInstance] loginIfNeeded:username password:password]; 35 | ``` 36 | 37 | **login** (async): 38 | 39 | ```objective-c 40 | // use asyncBlockingQueue: for async request 41 | [[PixivAPI sharedInstance] asyncBlockingQueue:^{ 42 | if ([[PixivAPI sharedInstance] loginIfNeeded:@"username" password:@"password"]) { 43 | NSLog(@"Login success!"); 44 | } else { 45 | NSLog(@"Login failed."); 46 | } 47 | }]; 48 | ``` 49 | 50 | For more information about asyncBlockingQueue, you can read [asyncBlockingQueue: section](https://github.com/upbit/PixivAPI_iOS#asyncblockingqueue) 51 | 52 | ### SAPI 53 | 54 | SAPI return SAPIIllust / NSArray of SAPIIllust, or nil on error: 55 | 56 | ```objective-c 57 | /** 58 | * 每日排行 59 | * ranking.php 60 | * 61 | * @param page [1-n] 62 | * @param mode [day, week, month] 63 | * @param content [all, male, female, original] 64 | * @param requireAuth NO 65 | * 66 | * @return NSArray of SAPIIllust 67 | */ 68 | - (NSArray *)SAPI_ranking:(NSUInteger)page mode:(NSString *)mode content:(NSString *)content requireAuth:(BOOL)requireAuth; 69 | 70 | /** 71 | * 过去的排行 72 | * ranking_log.php 73 | * 74 | * @param Date_Year 2014 75 | * @param Date_Month 4 76 | * @param Date_Day 15 77 | * @param mode [daily, weekly, monthly, male, female, rookie], r18[daily_r18, weekly_r18, male_r18, female_r18, r18g] 78 | * @param page [1-n] 79 | * @param requireAuth YES - for r18 set 80 | * 81 | * @return NSArray of SAPIIllust 82 | */ 83 | - (NSArray *)SAPI_ranking_log:(NSUInteger)Date_Year month:(NSUInteger)Date_Month day:(NSUInteger)Date_Day 84 | mode:(NSString *)mode page:(NSUInteger)page requireAuth:(BOOL)requireAuth; 85 | 86 | /** 87 | * 作品信息 (新版本已使用 PAPI_works: 代替) 88 | * illust.php 89 | * 90 | * @param illust_id [id for illust] 91 | * @param requireAuth YES - for r18 result 92 | * 93 | * @return SAPIIllust 94 | */ 95 | - (SAPIIllust *)SAPI_illust:(NSUInteger)illust_id requireAuth:(BOOL)requireAuth; 96 | 97 | /** 98 | * 用户作品列表 99 | * member_illust.php 100 | * 101 | * @param author_id [id for author] 102 | * @param page [1-n] 103 | * @param requireAuth YES - for r18 result 104 | * 105 | * @return NSArray of SAPIIllust 106 | */ 107 | - (NSArray *)SAPI_member_illust:(NSUInteger)author_id page:(NSUInteger)page requireAuth:(BOOL)requireAuth; 108 | 109 | /** 110 | * 用户资料 (新版本已使用 PAPI_users: 代替) 111 | * user.php 112 | * 113 | * @param user_id [id for author] 114 | * @param requireAuth NO 115 | * 116 | * @return SAPIIllust(Author) 117 | */ 118 | - (SAPIIllust *)SAPI_user:(NSUInteger)author_id requireAuth:(BOOL)requireAuth; 119 | 120 | /** 121 | * 用户收藏 (新版本已使用 PAPI_users_favorite_works: 代替) 122 | * bookmark.php (Authentication required) 123 | * 124 | * @param author_id [id for author] 125 | * @param page [1-n] 126 | * @param requireAuth YES - for r18 result 127 | * 128 | * @return NSArray of SAPIIllust 129 | */ 130 | - (NSArray *)SAPI_bookmark:(NSUInteger)author_id page:(NSUInteger)page requireAuth:(BOOL)requireAuth; 131 | 132 | /** 133 | * 标记书签的用户 134 | * illust_bookmarks.php 135 | * 136 | * @param illust_id [id for illust] 137 | * @param page [1-n] 138 | * @param requireAuth NO 139 | * 140 | * @return NSArray of SAPIIllust(Author) 141 | */ 142 | - (NSArray *)SAPI_illust_bookmarks:(NSUInteger)illust_id page:(NSUInteger)page requireAuth:(BOOL)requireAuth; 143 | 144 | /** 145 | * 关注 146 | * bookmark_user_all.php (Authentication required) 147 | * 148 | * @param author_id [id for author] 149 | * @param page [1-n] 150 | * 151 | * @return NSArray of SAPIIllust(Author) 152 | */ 153 | - (NSArray *)SAPI_bookmark_user_all:(NSUInteger)author_id page:(NSUInteger)page requireAuth:(BOOL)requireAuth; 154 | 155 | /** 156 | * 好P友 157 | * mypixiv_all.php 158 | * 159 | * @param author_id [id for author] 160 | * @param page [1-n] 161 | * 162 | * @return NSArray of SAPIIllust(Author) 163 | */ 164 | - (NSArray *)SAPI_mypixiv_all:(NSUInteger)author_id page:(NSUInteger)page requireAuth:(BOOL)requireAuth; 165 | ``` 166 | 167 | ### Public API 168 | 169 | PAPI return PAPIIllust(PAPIAuthor) / PAPIIllustList models, or nil on error: 170 | 171 | ```objective-c 172 | /** 173 | * 作品详细 174 | * works/.json 175 | * 176 | * @return PAPIIllust 177 | */ 178 | - (PAPIIllust *)PAPI_works:(NSUInteger)illust_id; 179 | 180 | /** 181 | * 用户资料 182 | * users/.json 183 | * 184 | * @return PAPIAuthor 185 | */ 186 | - (PAPIAuthor *)PAPI_users:(NSUInteger)author_id; 187 | 188 | /** 189 | * 我的订阅 190 | * me/feeds.json 191 | * 192 | * @param show_r18 NO - hide r18 illusts 193 | * 194 | * @return PAPIIllustList 195 | */ 196 | - (PAPIIllustList *)PAPI_me_feeds:(BOOL)show_r18; 197 | 198 | /** 199 | * 用户收藏 200 | * users//favorite_works.json 201 | * 202 | * @param page [1-n] 203 | * @param publicity YES - public; NO - private (only auth user) 204 | * 205 | * @return PAPIIllustList 206 | */ 207 | - (PAPIIllustList *)PAPI_users_favorite_works:(NSUInteger)author_id page:(NSUInteger)page publicity:(BOOL)publicity; 208 | ``` 209 | 210 | ### asyncBlockingQueue 211 | 212 | ```objective-c 213 | /** 214 | * Async run operation in Queue, and then call onCompletion 215 | * 216 | * @param queuePriority set 0 for NSOperationQueuePriorityNormal 217 | * @param mainOperations code block for sync(blocking) api 218 | * @param onCompletion completion block on mainQueue 219 | */ 220 | - (void)asyncBlockingQueue:(void (^)(void))mainOperations; 221 | - (void)asyncBlockingQueue:(NSOperationQueuePriority)queuePriority operations:(void (^)(void))mainOperations completion:(void (^)(void))onCompletion; 222 | ``` 223 | 224 | API use asyncBlockingQueue: for async request, for example: 225 | 226 | ```objective-c 227 | [[PixivAPI sharedInstance] asyncBlockingQueue:^{ 228 | NSArray *illusts = [[PixivAPI sharedInstance] SAPI_ranking:1 mode:@"all" content:@"day" requireAuth:NO]; 229 | 230 | // 1 - sync fetch 231 | for (SAPIIllust *illust in [illusts subarrayWithRange:NSMakeRange(0, 3)]) { 232 | NSLog(@"(%lu) %@", (unsigned long)[PixivAPI sharedInstance].operationQueue.operationCount, illust); 233 | 234 | SAPIIllust *author = [[PixivAPI sharedInstance] SAPI_user:illust.authorId requireAuth:NO]; 235 | NSLog(@"(%lu) %@", (unsigned long)[PixivAPI sharedInstance].operationQueue.operationCount, author); 236 | NSLog(@" fetch %ld complete", (long)author.authorId); 237 | } 238 | 239 | NSLog(@"1 - sync fetch illust[1,2,3] complete"); 240 | 241 | // 2 - async batch fetch 242 | for (SAPIIllust *illust in [illusts subarrayWithRange:NSMakeRange(3, 3)]) { 243 | [[PixivAPI sharedInstance] asyncBlockingQueue:^{ 244 | SAPIIllust *author = [[PixivAPI sharedInstance] SAPI_user:illust.authorId requireAuth:NO]; 245 | NSLog(@"(%lu) %@", (unsigned long)[PixivAPI sharedInstance].operationQueue.operationCount, author); 246 | NSLog(@" fetch %ld complete", (long)author.authorId); 247 | }]; 248 | } 249 | 250 | NSLog(@"2 - async illust[4,5,6] start."); 251 | }]; 252 | ``` 253 | 254 | output: 255 | 256 | ``` 257 | 21:59:29.788 (1) Illust: [なごみ@かんこれ(id=10457532)] 龍驤たちとオネエな提督(id=46597437) 258 | 21:59:29.926 (1) Author: なごみ@かんこれ(id=10457532) 259 | 21:59:29.927 fetch 10457532 complete 260 | 21:59:29.927 (1) Illust: [松竜(id=2159670)] 凛さん(id=46599697) 261 | 21:59:30.060 (1) Author: 松竜(id=2159670) 262 | 21:59:30.060 fetch 2159670 complete 263 | 21:59:30.060 (1) Illust: [柳田史太(id=1774701)] 「私はサポート役だから」とか思ってる系女子(id=46599040) 264 | 21:59:30.202 (1) Author: 柳田史太(id=1774701) 265 | 21:59:30.202 fetch 1774701 complete 266 | 21:59:33.217 1 - sync fetch illust[1,2,3] complete 267 | 21:59:33.217 2 - async illust[4,5,6] start. 268 | 21:59:33.592 (3) Author: ポ~ン(出水ぽすか)(id=33333) 269 | 21:59:33.593 fetch 33333 complete 270 | 21:59:33.593 (3) Author: JaneMere(id=49693) 271 | 21:59:33.593 fetch 49693 complete 272 | 21:59:33.765 (1) Author: ふぉぶ(id=465361) 273 | 21:59:33.765 fetch 465361 complete 274 | ``` 275 | 276 | _Tips:_ MAX_CONCURRENT_OPERATION_COUNT(2) limit only 2 operations run in queue, so operationCount=1 when "21:59:33.765 (1) Author: ふぉぶ(id=465361)" request started. 277 | 278 | **Update UI on mainQueue:** 279 | 280 | asyncBlockingQueue: run operations on operationQueue, it causing UI update delay. So use **onMainQueue:** when you reloadData for UI: 281 | 282 | ```objective-c 283 | __weak ViewController *weakSelf = self; 284 | [[PixivAPI sharedInstance] asyncBlockingQueue:^{ 285 | NSArray *illusts = [[PixivAPI sharedInstance] SAPI_ranking:page mode:@"day" content:@"all" requireAuth:NO]; 286 | 287 | [[PixivAPI sharedInstance] onMainQueue:^{ 288 | // update UI here 289 | weakSelf.illusts = [weakSelf.illusts arrayByAddingObjectsFromArray:illusts]; 290 | [weakSelf.tableView reloadData]; 291 | }]; 292 | }]; 293 | ``` 294 | --------------------------------------------------------------------------------