Skip to content

Commit

Permalink
(pixiv) Add pixiv extractor
Browse files Browse the repository at this point in the history
  • Loading branch information
violet-dev committed Jul 21, 2020
1 parent 7ebf6c8 commit b00efb8
Show file tree
Hide file tree
Showing 27 changed files with 1,901 additions and 103 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,5 @@ ws.dart
*.d
# *.so
# p7zip/CPP/ANDROID/7zr/obj/*
lib/component/download/*
# lib/component/download/*
test/*
18 changes: 17 additions & 1 deletion assets/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -164,5 +164,21 @@
"latestver": "This is the latest version!",

"yes": "Yes",
"no": "No"
"no": "No",

"date": "Date",
"complete": "Complete",
"waitqueue": "Waiting queue...",
"progress": "Progress",
"waitdownload": "Waiting for download...",
"extracting": "Extracting...",
"count": "%s",
"stop": "Stopped",
"unknownerr": "Unknown Error",
"urlnotsupport": "This URL does not support",
"tryagainlogin": "Try again, after login!",
"dinfo": "Info",
"state": "State",
"addurl": "Add URL",
"writeurl": "Write URL."
}
18 changes: 17 additions & 1 deletion assets/locale/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,5 +163,21 @@


"yes": "はい",
"no": "いいえ"
"no": "いいえ",

"date": "",
"complete": "完了",
"waitqueue": "キュー待機中...",
"progress": "進行",
"waitdownload": "ダウンロードを待っています...",
"extracting": "抽出中...",
"count": "%s項目",
"stop": "中断",
"unknownerr": "不明なエラーです",
"urlnotsupport": "サポートされていないURLです。",
"tryagainlogin": "ログイン後、再試行してください!",
"dinfo": "情報",
"state": "状態",
"addurl": " URLを直接追加",
"writeurl": "URL入力してください。"
}
18 changes: 17 additions & 1 deletion assets/locale/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,5 +163,21 @@
"latestver": "최신 버전입니다!",

"yes": "",
"no": "아니오"
"no": "아니오",

"date": "날짜",
"complete": "완료",
"waitqueue": "큐 대기중...",
"progress": "진행",
"waitdownload": "다운로드를 기다리는 중...",
"extracting": "추출중...",
"count": "%s개 항목",
"stop": "중단됨",
"unknownerr": "알 수 없는 오류입니다",
"urlnotsupport": "지원되지 않는 URL입니다.",
"tryagainlogin": "로그인 후 재시도 해주세요!",
"dinfo": "정보",
"state": "상태",
"addurl": "URL 직접 추가",
"writeurl": "URL을 입력해주세요."
}
Empty file.
Empty file.
280 changes: 280 additions & 0 deletions lib/component/download/pixiv.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
// This source code is a part of Project Violet.
// Copyright (C) 2020. violet-team. Licensed under the MIT License.

// Reference https://github.com/rollrat/downloader/blob/master/Koromo_Copy.Framework/Extractor/PixivExtractor.cs
import 'package:intl/intl.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:violet/component/downloadable.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:path/path.dart' as path;
import 'package:crypto/crypto.dart';

class PixivAPI {
static Future<String> getAccessToken(String username, String password) async {
const client_id = "MOBrBDS8blbauoSck0ZfDbtuzpyT";
const client_secret = "lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj";
const hash_secret =
"28c1fdd170a5204386cb1313c7077b34f83e4aaf4aa829ce78c231e05b0bae2c";
var localTime =
DateFormat('yyyy-MM-ddTHH:MM:ss+00:00').format(DateTime.now().toUtc());

var result =
await http.post('https://oauth.secure.pixiv.net/auth/token', headers: {
'UserAgent': 'PixivAndroidApp/5.0.64 (Android 6.0)',
'Referer': 'http://www.pixiv.net/',
'X-Client-Time': localTime,
'X-Client-Hash': md5
.convert(utf8.encode(localTime + hash_secret))
.toString()
.toLowerCase(),
}, body: {
'grant_type': 'password',
'username': username,
'password': password,
'get_secure_url': '1',
'client_id': client_id,
'client_secret': client_secret
});

var decode = jsonDecode(result.body)['response'];
if (decode == null) return null;

return decode['access_token'].toString();
}

static Future<Map<String, dynamic>> getUser(
String accessToken, int authorId) async {
var url = "https://public-api.secure.pixiv.net/v1/users/" +
authorId.toString() +
".json";

var param = {
"profile_image_sizes": "px_170x170,px_50x50",
"image_sizes": "px_128x128,small,medium,large,px_480mw",
"include_stats": "1",
"include_profile": "1",
"include_workspace": "1",
"include_contacts": "1",
};

var result = await http.get(
url +
'?' +
param.entries.toList().map((e) => '${e.key}=${e.value}').join('&'),
headers: {
'Referer': 'http://spapi.pixiv.net/',
'UserAgent': 'PixivIOSApp/5.8.0',
'Authorization': 'Bearer ' + accessToken,
});

return jsonDecode(utf8.decode(result.bodyBytes))['response'][0];
}

static Future<List<dynamic>> getUserWorks(String accessToken, int authorId,
[int page = 1,
int perPage = 30,
String publicity = "public",
bool includeSanityLevel = true]) async {
var url = "https://public-api.secure.pixiv.net/v1/users/" +
authorId.toString() +
"/works.json";

var param = {
"page": page.toString(),
"per_page": perPage.toString(),
"publicity": publicity,
"include_stats": "1",
"include_sanity_level": includeSanityLevel ? 1 : 0,
"image_sizes": "px_128x128,small,medium,large,px_480mw",
"profile_image_sizes": "px_170x170,px_50x50",
};

var result = await http.get(
url +
'?' +
param.entries.toList().map((e) => '${e.key}=${e.value}').join('&'),
headers: {
'Referer': 'http://spapi.pixiv.net/',
'UserAgent': 'PixivIOSApp/5.8.0',
'Authorization': 'Bearer ' + accessToken,
});

return jsonDecode(result.body)['response'];
}

static Future<List<dynamic>> getWorks(
String accessToken, int illustId) async {
var url = "https://public-api.secure.pixiv.net/v1/works/" +
illustId.toString() +
".json";

var param = {
"image_sizes": "px_128x128,small,medium,large,px_480mw",
"profile_image_sizes": "px_170x170,px_50x50",
"include_stats": "true"
};

var result = await http.get(
url +
'?' +
param.entries.toList().map((e) => '${e.key}=${e.value}').join('&'),
headers: {
'Referer': 'http://spapi.pixiv.net/',
'UserAgent': 'PixivIOSApp/5.8.0',
'Authorization': 'Bearer ' + accessToken,
});

return jsonDecode(result.body)['response'];
}

static Future<dynamic> getUgoira(String accessToken, String illustId) async {
var url = "https://app-api.pixiv.net/v1/ugoira/metadata";

var param = {
"illust_id": illustId,
};

var result = await http.get(
url +
'?' +
param.entries.toList().map((e) => '${e.key}=${e.value}').join('&'),
headers: {
'Referer': 'http://spapi.pixiv.net/',
'UserAgent': 'PixivIOSApp/5.8.0',
'Authorization': 'Bearer ' + accessToken,
});

return jsonDecode(result.body)['ugoira_metadata'];
}
}

class PixivManager extends Downloadable {
RegExp urlMatcher;
String accessToken;

PixivManager() {
urlMatcher = RegExp(
r'^https?://(www\.)?pixiv\.net/(member(?:_illust)?\.php\?id\=|artworks/|users/)(?<id>.*?)/?$');
}

@override
String fav() {
return 'https://www.pixiv.net/favicon.ico';
}

@override
bool acceptURL(String url) {
return urlMatcher.stringMatch(url) == url;
}

@override
String defaultFormat() {
return "%(extractor)s/%(artist)s (%(account)s)/%(file)s.%(ext)s";
}

@override
Future<List<DownloadTask>> createTask(
String url, GeneralDownloadProgress gdp) async {
var match = urlMatcher.allMatches(url);
if (match.first[2].startsWith('member') ||
match.first[2].startsWith('users')) {
var user = await PixivAPI.getUser(
accessToken, int.parse(match.first.namedGroup('id')));
var works = await PixivAPI.getUserWorks(
accessToken, int.parse(match.first.namedGroup('id')), 1, 10000000);

if (gdp.simpleInfoCallback != null)
gdp.simpleInfoCallback.call('${user['name']} (${user['account']})');
if (gdp.thumbnailCallback != null)
gdp.thumbnailCallback.call(user['profile_image_urls']['px_170x170'],
jsonEncode({'Referer': url}));

var result = List<DownloadTask>();
for (int i = 0; i < works.length; i++) {
var e = works[i];
if (e['page_count'] > 1) {
var x = await PixivAPI.getWorks(accessToken, e['id']);

x.forEach((element) {
element['metadata']['pages'].forEach((e) {
result.add(
DownloadTask(
url: e['image_urls']['large'],
filename: e['image_urls']['large'].split('/').last,
referer: url,
format: FileNameFormat(
artist: user['name'],
account: user['account'],
id: user['id'].toString(),
filenameWithoutExtension: path.basenameWithoutExtension(
e['image_urls']['large'].split('/').last),
extension: path
.extension(e['image_urls']['large'].split('/').last)
.replaceAll(".", ""),
extractor: 'pixiv',
),
),
);
});
});
}

if (e['type'] == null || e['type'] == 'illustration') {
result.add(
DownloadTask(
url: e['image_urls']['large'],
filename: e['image_urls']['large'].split('/').last,
referer: url,
format: FileNameFormat(
artist: user['name'],
account: user['account'],
id: user['id'].toString(),
filenameWithoutExtension: path.basenameWithoutExtension(
e['image_urls']['large'].split('/').last),
extension: path
.extension(e['image_urls']['large'].split('/').last)
.replaceAll(".", ""),
extractor: 'pixiv',
),
),
);
} else if (e['type'] == 'ugoira') {
// TODO: Pixiv Ugoira
}

gdp.progressCallback.call(result.length, 0);
}
return result;
}
}

@override
Future<void> setSession(String id, String pwd) async {
accessToken = await PixivAPI.getAccessToken(id, pwd);
}

@override
bool loginRequire() {
return true;
}

@override
String name() {
return 'Pixiv';
}

@override
bool logined() {
return !(accessToken == null || accessToken.length == 0);
}

@override
Future<bool> tryLogin() async {
var id = (await SharedPreferences.getInstance()).getString('pixiv_id');
var pwd = (await SharedPreferences.getInstance()).getString('pixiv_pwd');
if (id == null || pwd == null || id == '' || pwd == '') return false;
await setSession(id, pwd);
return logined();
}
}
Empty file.

0 comments on commit b00efb8

Please sign in to comment.