parent
1683f27792
commit
d5c42cc769
@ -0,0 +1,18 @@ |
||||
# Generated by Django 3.0.3 on 2020-09-22 19:51 |
||||
|
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('api', '0007_auto_20200520_1153'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AlterField( |
||||
model_name='userinfo', |
||||
name='domain_name', |
||||
field=models.CharField(blank=True, max_length=64, null=True, verbose_name='下载页面域名'), |
||||
), |
||||
] |
@ -0,0 +1,28 @@ |
||||
# Generated by Django 3.0.3 on 2020-09-25 20:41 |
||||
|
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('api', '0008_auto_20200922_1951'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AddField( |
||||
model_name='userinfo', |
||||
name='storage_active', |
||||
field=models.BooleanField(default=False, verbose_name='配置存储,默认关闭'), |
||||
), |
||||
migrations.AddField( |
||||
model_name='userinfo', |
||||
name='supersign_active', |
||||
field=models.BooleanField(default=False, verbose_name='配置超级签,默认关闭'), |
||||
), |
||||
migrations.AlterField( |
||||
model_name='userinfo', |
||||
name='download_times', |
||||
field=models.IntegerField(default=100, verbose_name='可用下载次数'), |
||||
), |
||||
] |
@ -0,0 +1,18 @@ |
||||
# Generated by Django 3.0.3 on 2021-01-07 18:59 |
||||
|
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('api', '0009_auto_20200925_2041'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AddField( |
||||
model_name='userinfo', |
||||
name='api_token', |
||||
field=models.CharField(default='', max_length=256, verbose_name='api访问密钥'), |
||||
), |
||||
] |
@ -0,0 +1,267 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding:utf-8 -*- |
||||
# project: 1月 |
||||
# author: NinEveN |
||||
# date: 2021/1/9 |
||||
import os |
||||
import sys |
||||
import plistlib |
||||
import random |
||||
import re |
||||
import json |
||||
import zipfile |
||||
from requests_toolbelt import MultipartEncoder |
||||
import oss2 |
||||
import requests |
||||
from androguard.core.bytecodes import apk |
||||
|
||||
|
||||
def upload_aliyunoss(access_key_id, access_key_secret, security_token, endpoint, bucket, file_path, file_name): |
||||
stsauth = oss2.StsAuth(access_key_id, access_key_secret, security_token) |
||||
bucket = oss2.Bucket(stsauth, endpoint, bucket) |
||||
with open(file_path, 'rb') as fileobj: |
||||
bucket.put_object(file_name, fileobj) |
||||
print("数据上传存储成功.") |
||||
|
||||
|
||||
def upload_qiniuyunoss(key, token, file_path): |
||||
from qiniu import put_file |
||||
ret, info = put_file(token, key, file_path) |
||||
if info.status_code == 200: |
||||
print("数据上传存储成功.") |
||||
else: |
||||
raise AssertionError(info.text) |
||||
|
||||
|
||||
|
||||
class FLY_CLI_SER(object): |
||||
|
||||
def __init__(self, fly_cli_domain, fly_cli_token): |
||||
self.fly_cli_domain = fly_cli_domain |
||||
self._header = { |
||||
"User-Agent": "fly-cli", |
||||
"accept": "application/json", |
||||
"APIAUTHORIZATION": fly_cli_token |
||||
} |
||||
|
||||
def get_upload_token(self, bundleid, type): |
||||
url = '%s/api/v2/fir/server/analyse' % self.fly_cli_domain |
||||
data = {"bundleid": bundleid, "type": type} |
||||
req = requests.post(url, json=data, headers=self._header) |
||||
if req.status_code == 200: |
||||
if req.json()['code'] == 1000: |
||||
return req.json()['data'] |
||||
raise AssertionError(req.text) |
||||
|
||||
def analyse(self, data): |
||||
url = '%s/api/v2/fir/server/analyse' % self.fly_cli_domain |
||||
|
||||
req = requests.put(url, json=data, headers=self._header) |
||||
if req.status_code == 200: |
||||
if req.json()['code'] == 1000: |
||||
print("APP信息更新成功" + req.text) |
||||
return |
||||
raise AssertionError(req.text) |
||||
|
||||
|
||||
def upload_local_storage(self, upload_key, upload_token, app_id, file_path): |
||||
url = '%s/api/v2/fir/server/upload' % self.fly_cli_domain |
||||
m = MultipartEncoder(fields={ |
||||
'file': (os.path.basename(file_path), open(file_path, 'rb'), 'application/octet-stream'), |
||||
'certinfo': json.dumps( |
||||
{ |
||||
'upload_key': upload_key, |
||||
'upload_token': upload_token, |
||||
'ftype': "app", |
||||
'app_id': app_id, |
||||
} |
||||
), |
||||
}) |
||||
header = { |
||||
'Content-Type': m.content_type, |
||||
} |
||||
req = requests.post(url, data=m, headers={**self._header, **header}) |
||||
if req.status_code == 200: |
||||
if req.json()['code'] == 1000: |
||||
print("数据上传存储成功." + req.text) |
||||
return |
||||
raise AssertionError(req.text) |
||||
|
||||
|
||||
def upload_app(self, app_path): |
||||
appobj = AppInfo(app_path) |
||||
appinfo = appobj.get_app_data() |
||||
icon_path = appobj.make_app_png() |
||||
bundle_id = appinfo.get("bundle_id") |
||||
upcretsdata = self.get_upload_token(bundle_id, appinfo.get("type")) |
||||
if upcretsdata['storage'] == 1: |
||||
# 七牛云存储 |
||||
upload_qiniuyunoss(upcretsdata['png_key'], upcretsdata['png_token'], icon_path) |
||||
upload_qiniuyunoss(upcretsdata['upload_key'], upcretsdata['upload_token'], app_path) |
||||
|
||||
elif upcretsdata['storage'] == 2: |
||||
# 阿里云存储 |
||||
png_auth = upcretsdata['png_token'] |
||||
|
||||
upload_aliyunoss(png_auth['access_key_id'], png_auth['access_key_secret'], png_auth['security_token'], |
||||
png_auth['endpoint'], png_auth['bucket'], icon_path, upcretsdata['png_key']) |
||||
file_auth = upcretsdata['upload_token'] |
||||
|
||||
upload_aliyunoss(file_auth['access_key_id'], file_auth['access_key_secret'], file_auth['security_token'], |
||||
file_auth['endpoint'], file_auth['bucket'], app_path, upcretsdata['upload_key']) |
||||
elif upcretsdata['storage'] == 3: |
||||
# 本地存储 |
||||
self.upload_local_storage(upcretsdata['png_key'], upcretsdata['png_token'], upcretsdata['app_uuid'], |
||||
icon_path) |
||||
self.upload_local_storage(upcretsdata['upload_key'], upcretsdata['upload_token'], upcretsdata['app_uuid'], |
||||
app_path) |
||||
|
||||
app_data = { |
||||
"filename": os.path.basename(app_path), |
||||
"filesize": os.path.getsize(app_path), |
||||
"appname": appinfo['labelname'], |
||||
"bundleid": appinfo['bundle_id'], |
||||
"version": appinfo['version'], |
||||
"buildversion": appinfo['versioncode'], |
||||
"miniosversion": appinfo['miniosversion'], |
||||
"release_type": appinfo['release_type'], |
||||
"release_type_id": 2, |
||||
"udid": appinfo.get('udid', []), |
||||
"type": appinfo['type'], |
||||
} |
||||
self.analyse({**app_data, **upcretsdata}) |
||||
|
||||
|
||||
class AppInfo(object): |
||||
def __init__(self, app_path): |
||||
self.app_path = app_path |
||||
self.result = {} |
||||
|
||||
def make_app_png(self): |
||||
zf = zipfile.ZipFile(self.app_path) |
||||
name_list = zf.namelist() |
||||
if self.app_path.endswith("apk"): |
||||
pattern = re.compile(r'res/drawable[^/]*/app_icon.png') |
||||
elif self.app_path.endswith("ipa"): |
||||
pattern = re.compile(r'Payload/[^/]*.app/AppIcon[^/]*[^(ipad)].png') |
||||
else: |
||||
raise Exception("File type error") |
||||
|
||||
size = 0 |
||||
iconfile = "" |
||||
for path in name_list: |
||||
m = pattern.match(path) |
||||
if m is not None: |
||||
filepath = m.group() |
||||
fsize = len(zf.read(filepath)) |
||||
if size == 0: |
||||
iconfile = filepath |
||||
if size < fsize: |
||||
size = fsize |
||||
iconfile = filepath |
||||
if size == 0: |
||||
raise Exception("File size error") |
||||
|
||||
filename = ''.join(random.sample( |
||||
['z', 'y', 'x', 'w', 'v', 'u', 't', 's', 'r', 'q', 'p', 'o', 'n', 'm', 'l', 'k', 'j', 'i', 'h', 'g', 'f', |
||||
'e', 'd', 'c', 'b', 'a'], 16)) |
||||
if not os.path.isdir(os.path.join("tmp", "icon")): |
||||
os.makedirs(os.path.join("tmp", "icon")) |
||||
with open(os.path.join("tmp", "icon", filename + '.png'), 'wb') as f: |
||||
f.write(zf.read(iconfile)) |
||||
|
||||
return os.path.join("tmp", "icon", filename + '.png') |
||||
|
||||
def get_app_data(self): |
||||
if self.app_path.endswith("apk"): |
||||
return self.__get_android_data() |
||||
elif self.app_path.endswith("ipa"): |
||||
return self.__get_ios_data() |
||||
else: |
||||
raise Exception("File type error") |
||||
|
||||
def __get_ios_info_path(self, ipaobj): |
||||
infopath_re = re.compile(r'Payload/[^/]*.app/Info.plist') |
||||
for i in ipaobj.namelist(): |
||||
m = infopath_re.match(i) |
||||
if m is not None: |
||||
return m.group() |
||||
|
||||
def __get_android_data(self): |
||||
try: |
||||
apkobj = apk.APK(self.app_path) |
||||
except Exception as err: |
||||
raise err |
||||
else: |
||||
if apkobj.is_valid_APK(): |
||||
versioncode = apkobj.get_androidversion_code() |
||||
bundle_id = apkobj.get_package() |
||||
labelname = apkobj.get_app_name() |
||||
version = apkobj.get_androidversion_name() |
||||
self.result = { |
||||
"labelname": labelname, |
||||
"bundle_id": bundle_id, |
||||
"versioncode": versioncode, |
||||
"version": version, |
||||
"type": "Android", |
||||
"miniosversion": apkobj.get_min_sdk_version() |
||||
} |
||||
return self.result |
||||
|
||||
def __get_ios_data(self): |
||||
if zipfile.is_zipfile(self.app_path): |
||||
ipaobj = zipfile.ZipFile(self.app_path) |
||||
info_path = self.__get_ios_info_path(ipaobj) |
||||
if info_path: |
||||
plist_data = ipaobj.read(info_path) |
||||
plist_root = plistlib.loads(plist_data) |
||||
labelname = plist_root['CFBundleDisplayName'] |
||||
versioncode = plist_root['CFBundleVersion'] |
||||
bundle_id = plist_root['CFBundleIdentifier'] |
||||
version = plist_root['CFBundleShortVersionString'] |
||||
self.result = { |
||||
"labelname": labelname, |
||||
"bundle_id": bundle_id, |
||||
"versioncode": versioncode, |
||||
"version": version, |
||||
"type": "iOS", |
||||
"miniosversion": plist_root['MinimumOSVersion'] |
||||
} |
||||
|
||||
release_type_info = self.__get_ios_release_type(ipaobj) |
||||
if info_path: |
||||
plist_data = ipaobj.read(release_type_info) |
||||
if b"ProvisionedDevices" in plist_data: |
||||
b = re.findall(rb'<key>ProvisionedDevices</key><array>(.*)</array><key>TeamIdentifier</key>', |
||||
plist_data.replace(b'\n', b'').replace(b'\r', b'').replace(b"\t", b""))[0] |
||||
clist = re.split(rb'<string>|</string>', b) |
||||
nudidlist = [] |
||||
for x in clist: |
||||
if x: |
||||
nudidlist.append(x.decode('utf-8')) |
||||
self.result["release_type"] = "Adhoc" |
||||
self.result['udid'] = nudidlist |
||||
else: |
||||
self.result["release_type"] = "Inhouse" |
||||
self.result["udid"] = [] |
||||
return self.result |
||||
|
||||
def __get_ios_release_type(self, ipaobj): |
||||
infopath_re = re.compile(r'Payload/[^/]*.app/embedded.mobileprovision') |
||||
for i in ipaobj.namelist(): |
||||
m = infopath_re.match(i) |
||||
if m is not None: |
||||
return m.group() |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
fly_cli_domain = 'https://fly.harmonygames.cn' |
||||
fly_cli_token = '4baf717aff0011ea80c000163e069e27x2gRvmf2035rP3YWITJxvF4VVE32M55AuVHxOvSbTgdwgF54UeXFzrNLkP1lgUID' |
||||
fly_obj = FLY_CLI_SER(fly_cli_domain, fly_cli_token) |
||||
if len(sys.argv) != 2: |
||||
raise ValueError('参数有误') |
||||
app_path = sys.argv[1] |
||||
if os.path.isfile(app_path): |
||||
fly_obj.upload_app(app_path) |
||||
raise FileNotFoundError(app_path) |
||||
|
@ -0,0 +1,5 @@ |
||||
from django.apps import AppConfig |
||||
|
||||
|
||||
class CliConfig(AppConfig): |
||||
name = 'cli' |
@ -0,0 +1,29 @@ |
||||
"""fir_ser URL Configuration |
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see: |
||||
https://docs.djangoproject.com/en/3.0/topics/http/urls/ |
||||
Examples: |
||||
Function views |
||||
1. Add an import: from my_app import views |
||||
2. Add a URL to urlpatterns: path('', views.home, name='home') |
||||
Class-based views |
||||
1. Add an import: from other_app.views import Home |
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') |
||||
Including another URLconf |
||||
1. Import the include() function: from django.urls import include, path |
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) |
||||
""" |
||||
from django.urls import path, re_path, include |
||||
|
||||
from cli.views.login import CliUserInfoView |
||||
from cli.views.apps import CliAppsView, CliAppInfoView, CliAppReleaseinfoView |
||||
from cli.views.uploads import CliAppAnalyseView, CliUploadView |
||||
|
||||
urlpatterns = [ |
||||
re_path("^apps$", CliAppsView.as_view()), |
||||
re_path("^apps/(?P<app_id>\w+)", CliAppInfoView.as_view()), |
||||
re_path("^appinfos/(?P<app_id>\w+)/(?P<act>\w+)", CliAppReleaseinfoView.as_view()), |
||||
re_path("^upload$", CliUploadView.as_view()), |
||||
re_path("^userinfo", CliUserInfoView.as_view()), |
||||
re_path("^analyse$", CliAppAnalyseView.as_view()), |
||||
] |
@ -0,0 +1,23 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding:utf-8 -*- |
||||
# project: 3月 |
||||
# author: liuyu |
||||
# date: 2020/3/4 |
||||
|
||||
from api.utils.auth import ApiTokenAuthentication |
||||
from api.views.apps import AppsView, AppInfoView, AppReleaseinfoView |
||||
import logging |
||||
|
||||
logger = logging.getLogger(__name__) |
||||
|
||||
|
||||
class CliAppsView(AppsView): |
||||
authentication_classes = [ApiTokenAuthentication, ] |
||||
|
||||
|
||||
class CliAppInfoView(AppInfoView): |
||||
authentication_classes = [ApiTokenAuthentication, ] |
||||
|
||||
|
||||
class CliAppReleaseinfoView(AppReleaseinfoView): |
||||
authentication_classes = [ApiTokenAuthentication, ] |
@ -0,0 +1,25 @@ |
||||
from django.contrib import auth |
||||
from api.models import Token, UserInfo, Apps |
||||
from rest_framework.response import Response |
||||
from api.utils.serializer import UserInfoSerializer |
||||
from django.core.cache import cache |
||||
from rest_framework.views import APIView |
||||
import binascii |
||||
import os, datetime, json |
||||
from api.utils.utils import get_captcha, valid_captcha, is_valid_domain, is_valid_phone, \ |
||||
get_sender_sms_token, is_valid_email, is_valid_sender_code, get_sender_email_token, get_random_username, \ |
||||
check_username_exists |
||||
from api.utils.auth import ApiTokenAuthentication |
||||
from api.utils.response import BaseResponse |
||||
from fir_ser.settings import CACHE_KEY_TEMPLATE, SERVER_DOMAIN, REGISTER, LOGIN |
||||
from api.utils.storage.caches import login_auth_failed, set_default_app_wx_easy |
||||
import logging |
||||
|
||||
from api.views.login import UserInfoView |
||||
|
||||
logger = logging.getLogger(__name__) |
||||
|
||||
|
||||
class CliUserInfoView(UserInfoView): |
||||
authentication_classes = [ApiTokenAuthentication, ] |
||||
|
@ -0,0 +1,17 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding:utf-8 -*- |
||||
# project: 3月 |
||||
# author: liuyu |
||||
# date: 2020/3/6 |
||||
|
||||
|
||||
from api.utils.auth import ApiTokenAuthentication |
||||
from api.views.uploads import AppAnalyseView, UploadView |
||||
|
||||
|
||||
class CliAppAnalyseView(AppAnalyseView): |
||||
authentication_classes = [ApiTokenAuthentication, ] |
||||
|
||||
|
||||
class CliUploadView(UploadView): |
||||
authentication_classes = [ApiTokenAuthentication, ] |
Loading…
Reference in new issue