Here it is! Beta 1.0 source code!

This commit is contained in:
R.G 2023-02-24 19:30:23 +01:00 committed by GitHub
commit a4a52b233b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 1413 additions and 0 deletions

0
api/__init__.py Normal file
View file

3
api/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
api/apps.py Normal file
View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class ApiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'api'

View file

3
api/models.py Normal file
View file

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

3
api/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

37
api/urls.py Normal file
View file

@ -0,0 +1,37 @@
from django.urls import path
from . import views
urlpatterns = [
path('service_hosts', views.service_hosts, name='servicehosts'),
path('country/<str:country>', views.country, name='country'),
path('my/session/!open', views.open, name='opensession'),
path('my/session/!open_without_nna', views.open, name='opensession_withoutnna'),
path('my/session/!close', views.close, name="endsession"),
path('my/balance/current', views.balance, name="balance"),
path('my/wishlist/notice', views.dummy_wishlist, name="wishlist"),
path('my/wishlist/!put', views.put_wishlist, name="wishlist"),
path('my/wishlist/<int:tid>/!delete', views.delete_wishlist, name="wishlist"),
path('my/wishlist', views.wishlist, name='mywishlist'),
path('my/shared_titles', views.shared_titles, name='shared_titles'),
path('my/owned_coupons', views.owned_coupons, name="ownedcoupons"),
path('my/shared_title_ids', views.ownedtitles, name="sharedtitleids"),
path('my/language', views.language, name='language'),
path('my/account/ctr/!migrate_without_nna', views.empty, name="migrate"),
path('my/npns_status', views.empty, name="npns_status"),
path('my/parental_control/!put', views.empty, name="parental_control"),
path('<str:country>/titles/online_prices', views.online_price, name="online_prices"),
path('<str:country>/title/<int:tid>/ec_info', views.ec_info, name="ecinfo"),
path('<str:country>/title/<int:tid>/prepurchase_info', views.prepurchase_info, name="prepurchase"),
path('<str:country>/title/<int:tid>/!purchase', views.purcahse_title, name="purchase"),
path('<str:country>/title/<int:tid>/!redeem', views.redeem_title, name="redeemtitle"),
path('<str:country>/title/public_status', views.public_status, name="publicstatus"),
path('redeemable_card/!check', views.check_redeemable, name="checkredeem"),
path('my/balance/prereplenish_info', views.pretransac_redeem, name="testredeem"),
path('my/balance/current/!add_prepaid', views.add_money_prepaid, name="prepaid"),
path('my/tax_location', views.tax_location, name="taxloc"),
path('my/transactions', views.transactions, name="transactions"),
path('my/votes', views.votes, name="votes"),
path('my/votable_titles/!put', views.votable_titles, name="votable_titles"),
path('titles/id_pair', views.id_pair, name="id_pair"),
]

341
api/views.py Normal file
View file

@ -0,0 +1,341 @@
#CRAPPY CODE WARNING(!)
from django.shortcuts import render
from django.http import JsonResponse, HttpResponse
from django.views.decorators.csrf import csrf_exempt
from shopdeck import settings
from shopdeckdb.models import *
from django.core.exceptions import ObjectDoesNotExist
import time, random, string
def id_generator(size=32, chars=string.ascii_uppercase + string.digits):
return ''.join(random.choice(chars) for _ in range(size))
@csrf_exempt
def service_hosts(request):
res = {"services":{"service":[{"name":"CCIF","origin_fqdn":settings.SOAP_URL},{"name":"SAMURAI_CTR","origin_fqdn":settings.METADATA_API_URL,"cdn_fqdn":settings.METADATA_API_URL},{"name":"EOU","origin_fqdn":settings.SOAP_URL,"cdn_fqdn":settings.SOAP_URL}]}}
return JsonResponse(res)
@csrf_exempt
def country(request, country):
res = {"country_detail":{"region_code":"LOL","max_cash":{"amount":"99999,00 Credit","currency":"CREDIT","raw_value":"99999"},"loyalty_system_available":False,"legal_payment_message_required":False,"legal_business_message_required":False,"tax_excluded_country":False,"tax_free_country":False,"prepaid_card_available":True,"credit_card_available":False,"credit_card_store_available":False,"jcb_security_code_available":False,"nfc_available":False,"coupon_available":False,"my_coupon_available":True,"price_format":{"positive_prefix":"","positive_suffix":" Credit","negative_prefix":"- ","negative_suffix":" Credit","formats":{"format":[{"value":"# ### ### ###,##","digit":"#"}],"pattern_id":"5"}},"default_timezone":"+00:00","eshop_available":True,"name":country,"iso_code":country,"default_language_code":"en","language_selectable":False}}
return JsonResponse(res)
@csrf_exempt
def open(request):
data = request.POST
if data.get("device_id")==None:
return JsonResponse({"error": True})
try:
ds = Client3DS.objects.get(consoleid=data.get("device_id"))
except ObjectDoesNotExist:
return JsonResponse({"error": True})
if ds.is_terminated:
return JsonResponse({"error": {"code": "8008", "message": "Your account has been terminated. Sorry."}}, status=400)
request.session['deviceid'] = data.get("device_id")
currenttime = int(round(time.time()*1000))
id = id_generator()
res = {"session_config":{"country":ds.country,"saved_lang":ds.language,"shop_account_initialized":False,"device_link_updated":False,"owned_titles_modified":currenttime,"shared_titles_last_modified":currenttime,"server_time":currenttime,"devices":{"device":[{"name":"CTR","id":4}]},"auto_billing_contracted":False,"id":id}}
return JsonResponse(res)
@csrf_exempt
def close(request):
request.session.flush()
return HttpResponse()
@csrf_exempt
def balance(request):
try:
ds = Client3DS.objects.get(consoleid=request.session["deviceid"])
except:
return JsonResponse({"error": True})
res = {"balance":{"amount":str(ds.balance)+",00 Credit","currency":"EUR","raw_value":str(ds.balance)}}
return JsonResponse(res)
#This is due to how eShop servers handle my/wishlist/notice: its a empty json, even if you have wishlisted titles
@csrf_exempt
def dummy_wishlist(request):
res = {"wishlist_notice":{"wished_title_id":[],"total":0}}
return JsonResponse(res)
@csrf_exempt
def wishlist(request):
try:
ds = Client3DS.objects.get(consoleid=request.session["deviceid"])
except:
return JsonResponse({"error": True})
wishlisted_titles = wishlistedTitle.objects.filter(owner=ds)
wishlisted = []
for title in wishlisted_titles:
wishlisted.append({"platform": {"name": title.title.platform.name, "id": title.title.platform.id, "device": "CTR", "category": title.title.genre.id}, "publisher": {"name": title.title.publisher.publisher_name, "id": title.title.publisher.id}, "display_genre": title.title.genre.name, "release_date_on_eshop": str(title.title.date), "release_date_on_original": str(title.title.date), "retail_sales": False, "eshop_sales": True, "in_app_purchase": title.title.in_app_purchase, "name": title.title.name, "id": title.title.id, "icon_url": title.title.icon_url, "banner_url": title.title.banner_url})
res = {"wishlist":{"wished_title":wishlisted,"total":len(wishlisted)}}
return JsonResponse(res)
#FIXME: Make this not just a static JSON response.
@csrf_exempt
def owned_coupons(request):
res = {"coupons":{}}
return JsonResponse(res)
@csrf_exempt
def ownedtitles(request):
try:
ds = Client3DS.objects.get(consoleid=request.session["deviceid"])
except:
return JsonResponse({"error": True})
owned_titles = ownedTitle.objects.filter(owner=ds)
owned = []
i = 0
for title in owned_titles:
owned.append({"title_id": title.title.tid, "id": title.title.id, "index": i})
i+1
res = {"owned_titles":{"owned_title":owned}}
return JsonResponse(res)
@csrf_exempt
def language(request):
try:
ds = Client3DS.objects.get(consoleid=request.session["deviceid"])
except:
return JsonResponse({"error": True})
res = {"session_config":{"saved_lang":ds.language}}
return JsonResponse(res)
@csrf_exempt
def empty(request):
return JsonResponse({})
@csrf_exempt
def online_price(request, country):
if request.GET.get('title[]') == None:
return JsonResponse({"error": True})
try:
ds = Client3DS.objects.get(consoleid=request.session["deviceid"])
except:
return JsonResponse({"error": True})
tlist = list(request.GET.get('title[]').split(","))
titles = []
for ind_title in tlist:
try:
title = Title.objects.get(id=int(ind_title))
try:
titleowned = ownedTitle.objects.get(title=title, owner=ds)
is_title_owned = True
except ObjectDoesNotExist:
is_title_owned = False
if title.price == 0:
titleprice = "Free"
else:
titleprice = str(title.price)+" Credit"
titles.append({"title_id": int(ind_title), "eshop_sales_status": "onsale", "price": {"regular_price": {"amount": titleprice, "currency": "CREDIT", "raw_value": str(title.price), "id": 2172116800}}, "title_owned": is_title_owned})
except ObjectDoesNotExist:
return JsonResponse({"error": True})
res = {"online_prices": {"online_price": titles}}
return JsonResponse(res)
@csrf_exempt
def ec_info(request, country, tid):
try:
title = Title.objects.get(id=tid)
except:
return JsonResponse({"error": True})
res = {"title_ec_info":{"title_id":title.tid,"content_size":title.size,"title_version":title.version,"disable_download":title.is_not_downloadable}}
return JsonResponse(res)
@csrf_exempt
def put_wishlist(request):
try:
title = Title.objects.get(id=int(request.POST.get("id")))
except:
return JsonResponse({"error": True})
try:
ds = Client3DS.objects.get(consoleid=request.session["deviceid"])
except:
return JsonResponse({"error": True})
try:
wishlisted = wishlistedTitle.objects.get(title=title, owner=ds)
return JsonResponse({"error": True})
except ObjectDoesNotExist:
pass
wishlisted = wishlistedTitle.objects.create(title=title, owner=ds)
wishlisted.save()
return JsonResponse({})
@csrf_exempt
def delete_wishlist(request, tid):
try:
title = Title.objects.get(id=int(tid))
except:
return JsonResponse({"error": True})
try:
ds = Client3DS.objects.get(consoleid=request.session["deviceid"])
except:
return JsonResponse({"error": True})
try:
wishlisted = wishlistedTitle.objects.get(title=title, owner=ds)
except ObjectDoesNotExist:
return JsonResponse({"error": True})
wishlisted.delete()
return JsonResponse({})
@csrf_exempt
def check_redeemable(request):
try:
card = redeemableCard.objects.get(code=request.POST.get("card_number"))
except:
return JsonResponse({"error": {"code": "6561", "message": "This code is incorrect.\nPlease check up your code and try again."}}, status=400)
if card.used:
return JsonResponse({"error": {"code": "3101", "message": "This code is already used.\nSorry!"}}, status=400)
if card.is_money:
res = {"redeemable_card": {"number": request.POST.get("card_number"),"cash": {"amount": card.content+" Credit","currency": "CREDIT","raw_value": card.content}}}
return JsonResponse(res)
else:
try:
title = Title.objects.get(tid=card.content)
except ObjectDoesNotExist:
return JsonResponse({"error": {"code": "5615", "message": "The corresponding title was not found.\nContact an administrator."}})
if request.POST.get("tin") != None:
return JsonResponse({"error": {"code": "6969", "message": "This is a title download code.\nPlease use the right tool."}})
res = {"redeemable_card": {"number": request.POST.get("card_number"), "contents": {"content": [{"title": {"name": title.name, "id": title.id}}]},"title_ec_info": {"title_id": title.tid, "content_size": title.size, "title_version": title.version}}}
return JsonResponse(res)
@csrf_exempt
def pretransac_redeem(request):
try:
ds = Client3DS.objects.get(consoleid=request.session["deviceid"])
except:
return JsonResponse({"error": True})
res = {"prereplenish_info": {"current_balance": {"amount": str(ds.balance)+" Credit", "currency": "CREDIT", "raw_value": str(ds.balance)},"replenish_amount": {"amount": str(request.GET.get("replenish_amount"))+" Credit", "currency": "CREDIT", "raw_value": str(request.GET.get("replenish_amount"))},"post_balance": {"amount": str(int(float(request.GET.get("replenish_amount")))+ds.balance)+" Credit", "currency": "CREDIT", "raw_value": str(int(float(request.GET.get("replenish_amount")))+ds.balance)}}}
return JsonResponse(res)
@csrf_exempt
def add_money_prepaid(request):
try:
ds = Client3DS.objects.get(consoleid=request.session["deviceid"])
except:
return JsonResponse({"error": True})
try:
card = redeemableCard.objects.get(code=request.POST.get("card_number"))
except:
return JsonResponse({"error": {"code": "6561", "message": "This code is incorrect.\nPlease check up your code and try again."}})
if card.used:
res = {"error": {"code": "4626", "message": "This code is already used.\nSorry!"}}
res = {"transaction_result": {"transaction_id": 1,"post_balance": {"amount": str(int(card.content)+ds.balance)+" Credit","currency": "CREDIT","raw_value": str(int(card.content)+ds.balance)},"integrated_account": True}}
card.used = True
card.save()
ds.balance = int(card.content)+ds.balance
ds.save()
return JsonResponse(res)
@csrf_exempt
def prepurchase_info(request, country, tid):
try:
title = Title.objects.get(id=int(tid))
except:
return JsonResponse({"error": True})
try:
ds = Client3DS.objects.get(consoleid=request.session["deviceid"])
except:
return JsonResponse({"error": True})
res = {"prepurchase_info":{"tax_excluded":False,"purchasing_content":[{"eshop_sales_status":"onsale","content_size":title.size,"payment_amount":{"price":{"regular_price":{"amount":str(title.price)+",00 Credit","currency":"CREDIT","raw_value":str(title.price),"id":2172116800}},"total_amount":{"amount":str(title.price)+",00 Credit","currency":"CREDIT","raw_value":str(title.price)}}}],"current_balance":{"amount":str(ds.balance)+",00 Credit","currency":"CREDIT","raw_value":str(ds.balance)},"post_balance":{"amount":str(ds.balance-title.price)+",00 Credit","currency":"EUR","raw_value":str(ds.balance-title.price)},"total_amount":{"price":{"regular_price":{"amount":str(title.price)+",00 Credit","currency":"CREDIT","raw_value":str(title.price)}},"total_amount":{"amount":str(title.price)+",00 Credit","currency":"CREDIT","raw_value":str(title.price)}}}}
return JsonResponse(res)
@csrf_exempt
def purcahse_title(request, country, tid):
try:
title = Title.objects.get(id=int(tid))
except:
return JsonResponse({"error": True})
try:
ds = Client3DS.objects.get(consoleid=request.session["deviceid"])
except:
return JsonResponse({"error": True})
if not title.public:
return JsonResponse({"error": {"code": "6265", "message": "This title is not public.\nYou cannot download it."}}, status=400)
if ds.balance - title.price < 0:
return JsonResponse({"error": {"code": "8167", "message": "You don't have enough money for that."}})
try:
owned = ownedTitle.objects.get(title=title, owner=ds)
except ObjectDoesNotExist:
ds.balance = ds.balance - title.price
ds.save()
owned = ownedTitle.objects.create(title=title, owner=ds)
owned.save()
res = {"transaction_result":{"transaction_id":1,"title_id":title.tid,"ticket_id":int(title.ticket_id),"post_balance":{"amount":str(ds.balance)+",00 Credit","currency":"CREDIT","raw_value":str(ds.balance)},"business_type":"NCL_DIST","integrated_account":True}}
return JsonResponse(res)
@csrf_exempt
def tax_location(request):
try:
ds = Client3DS.objects.get(consoleid=request.session["deviceid"])
except:
return JsonResponse({"error": True})
res = {"tax_location": {"state": "uwuland", "state_code": ds.country, "id": 71647}}
return JsonResponse(res)
@csrf_exempt
def redeem_title(request, country, tid):
try:
ds = Client3DS.objects.get(consoleid=request.session["deviceid"])
except:
return JsonResponse({"error": True})
try:
title = Title.objects.get(id=int(tid))
except:
return JsonResponse({"error": True})
try:
card = redeemableCard.objects.get(code=request.POST.get("card_number"))
except:
return JsonResponse({"error": {"code": "6561", "message": "This code is incorrect.\nPlease check up your code and try again."}})
if card.content != title.tid:
return JsonResponse({"error": {"code": "9468", "message": "Invalid title ID.\nPlease check up your code and try again."}}, status=400)
card.used = True
card.save()
owned = ownedTitle.objects.create(title=title, owner=ds)
owned.save()
res = {"transaction_result":{"transaction_id":1,"title_id":title.tid,"ticket_id":int(title.ticket_id),"post_balance":{"amount":str(ds.balance)+",00 Credit","currency":"CREDIT","raw_value":str(ds.balance)},"business_type":"NCL_DIST","integrated_account":True}}
return JsonResponse(res)
@csrf_exempt
def transactions(request):
return JsonResponse({"error": {"code": "8458", "message": "Useless shit.\nWe will never implement that.\nSorry."}}, status=400)
@csrf_exempt
def shared_titles(request):
try:
ds = Client3DS.objects.get(consoleid=request.session["deviceid"])
except:
return JsonResponse({"error": True})
ownedtitles = ownedTitle.objects.filter(owner=ds)
wishlisted = []
for title in ownedtitles:
wishlisted.append({"platform": {"name": title.title.platform.name, "id": title.title.platform.id, "device": "CTR", "category": title.title.genre.id}, "publisher": {"name": title.title.publisher.publisher_name, "id": title.title.publisher.id}, "display_genre": title.title.genre.name, "release_date_on_eshop": str(title.title.date), "release_date_on_original": str(title.title.date), "retail_sales": False, "eshop_sales": True, "in_app_purchase": title.title.in_app_purchase, "name": title.title.name, "id": title.title.id, "icon_url": title.title.icon_url, "banner_url": title.title.banner_url})
res = {"owned_titles":{"owned_title":wishlisted,"total":len(wishlisted)}}
return JsonResponse(res)
@csrf_exempt
def id_pair(request):
return JsonResponse({"error": {"code": "6569", "message": "Due to technical limitations,\nthis functionnality is not available."}}, status=401)
@csrf_exempt
def public_status(request, country):
if request.GET.get("ns_uid") == None:
return JsonResponse({"error": True})
try:
title = Title.objects.get(id=int(request.GET.get("ns_uid")))
except:
return JsonResponse({"error": {"code": "5546", "message": "Title not found."}}, status=400)
if title.public:
type = "PUBLIC"
else:
type = "PRIVATE"
res = {"title_public_status":{"public_status":type,"type":"T","ns_uid":title.id,"title_id":title.tid}}
return JsonResponse(res)
@csrf_exempt
def votable_titles(request):
return JsonResponse({"error": {"code": "5626", "message": "WIP. Not ready yet."}}, status=400)
@csrf_exempt
def votes(request):
return JsonResponse({"error": {"code": "5626", "message": "WIP. Not ready yet."}}, status=400)

27
cas.py Normal file
View file

@ -0,0 +1,27 @@
from flask import Blueprint, request, render_template, make_response
import xmltodict, time, os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'shopdeck.settings')
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
from shopdeckdb.models import *
from django.core.exceptions import ObjectDoesNotExist
cas = Blueprint("cas", "cas")
@cas.route("/cas/services/CatalogingSOAP", methods=['POST'])
def soap():
if request.get_data() == b"IwI":
return "TwT"
try:
parsed = xmltodict.parse(request.get_data())
except:
return "Error"
if request.headers.get('User-Agent') != "CTR EC 040600 Mar 14 2012 13:32:39":
return "Error"
if "cas:ListTitlesEx" in parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']:
try:
title = Title.objects.get(tid=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['cas:ListTitlesEx']['cas:TitleId'])
except ObjectDoesNotExist:
return "Error"
r = make_response(render_template("cas/listTitlesEx.xml", id=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['cas:ListTitlesEx']['cas:DeviceId'], message=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['cas:ListTitlesEx']['cas:MessageId'],time=int(round(time.time()*1000)), t=title))
r.headers.set("Content-Type", "text/xml; charset=utf-8")
return r

40
cdn.py Normal file
View file

@ -0,0 +1,40 @@
from flask import Blueprint, make_response, request
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'shopdeck.settings')
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
from shopdeckdb.models import *
from django.core.exceptions import ObjectDoesNotExist
import os.path
ccs = Blueprint("ccs", "ccs")
@ccs.route("/ccs/download/<tid>/tmd.<version>", methods=['GET'])
def download_tmd(tid, version):
try:
title = Title.objects.get(tid=str(tid))
except ObjectDoesNotExist:
return "Error"
try:
ds = Client3DS.objects.get(consoleid=request.args.get('deviceId'))
except ObjectDoesNotExist:
return "Error"
try:
owned = ownedTitle.objects.get(title=title, owner=ds)
except ObjectDoesNotExist:
return "error"
path = str(os.path.dirname(__file__))+"/cdn/"+str(tid)+"/tmd.bin"
if not os.path.isfile(path):
return "error"
f = open(path, mode="rb")
r = make_response(f.read())
r.headers.set("Content-Type", "application/octet-stream")
return r
@ccs.route("/ccs/download/<tid>/<app>", methods=['GET'])
def download_app(tid, app):
path = str(os.path.dirname(__file__))+"/cdn/"+str(tid)+"/"+str(app)+".app"
if not os.path.isfile(path):
return "error"
f = open(path, mode="rb")
r = make_response(f.read())
r.headers.set("Content-Type", "application/octet-stream")
return r

1
cdn/README Normal file
View file

@ -0,0 +1 @@
Put the following files from a CIA file in a new folder with its title id: tmd.bin & the .app file (all uppercase)

48
cia-helper.py Normal file
View file

@ -0,0 +1,48 @@
#!/usr/bin/python3
# By Let's Shop team
# Tool to parse cias easily
# Heavily inspired by one of the examples
# Created with help from ZeroSkill
from pyctr.type.cia import CIAReader, CIASection, InvalidCIAError
from pyctr.crypto.engine import BootromNotFoundError
import sys, base64
if len(sys.argv) == 1:
exit('Usage: ./cia-helper.py [cia file]')
try:
try:
try:
try:
with CIAReader(sys.argv[1]) as cia:
print("Welcome to CIA Helper! This tool is designed to help you adding a homebrew to Let's Shop.")
print("If the title needs to be downloaded with a redeemable card, please make the id like: 50000000000000")
print("For the ticket id, put anything you want (in numbers), but be sure it starts by: 12")
print('Title ID:', cia.tmd.title_id.upper())
print('Title Version:',cia.tmd.title_version.__index__())
app = cia.contents[CIASection.Application]
app_title = app.exefs.icon.get_app_title('English')
print('Application Title:', app_title.short_desc)
print('Application Description:', app_title.long_desc)
print('Application Publisher:', app_title.publisher)
print('Product code:', app.product_code)
print('Size:', cia.total_size)
chunks = [ chunk for chunk in cia.tmd.chunk_records if chunk.cindex in cia.contents ]
filename = chunks[0].id.upper()
with open(filename+".app", 'wb') as file:
file.write(cia.open_raw_section(CIASection.Application).read())
with open("tmd.bin", 'wb') as file:
file.write(cia.open_raw_section(CIASection.TitleMetadata).read())
with open("ticket.txt", 'wb') as file:
file.write(base64.b64encode(cia.open_raw_section(CIASection.Ticket).read()))
print('Finished! You can now put the tmd.bin and the .app file in cdn/'+cia.tmd.title_id.upper()+' (create the folder)')
print('Your base64-encoded ticket is available in ticket.txt.')
print('Have a great day!')
except BootromNotFoundError:
print("Necessary files were not found. Please create a '3ds' directory in your home folder and put boot9.bin in it.")
except FileNotFoundError:
print("Your file does not exists.")
except InvalidCIAError:
print("Your file is not a CIA.")
except:
print("Something wrong happened. Sorry! (try again)")

81
ecs.py Normal file
View file

@ -0,0 +1,81 @@
from flask import Blueprint, request, render_template, make_response
import xmltodict, os, random, string, time
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'shopdeck.settings')
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
from shopdeckdb.models import *
from django.core.exceptions import ObjectDoesNotExist
from shopdeck import settings
#generate totally-genuine-devicetoken
def id_generator(size=21, chars=string.ascii_uppercase + string.ascii_lowercase + string.digits):
return ''.join(random.choice(chars) for _ in range(size))
ecs = Blueprint("ecs", "ecs")
@ecs.route("/ecs/services/ECommerceSOAP", methods=['POST'])
def soap():
if request.get_data() == b"OwO":
return "UwU"
try:
parsed = xmltodict.parse(request.get_data())
except:
return "Error"
if request.headers.get('User-Agent') != "CTR EC 040600 Mar 14 2012 13:32:39":
return "Error"
if 'ecs:GetAccountStatus' in parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']:
print("Some 3DS wants to connect")
try:
ds = Client3DS.objects.get(consoleid=str(parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ecs:GetAccountStatus']['ecs:DeviceId']))
except ObjectDoesNotExist:
#Create new Client3DS object, and save it!
print("New 3DSClient: "+str(parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ecs:GetAccountStatus']['ecs:DeviceId']))
ds = Client3DS.objects.create(consoleid=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ecs:GetAccountStatus']['ecs:DeviceId'], devicetoken=id_generator(), is_terminated=False, balance=0, language=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ecs:GetAccountStatus']['ecs:Language'], region=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ecs:GetAccountStatus']['ecs:Region'], country=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ecs:GetAccountStatus']['ecs:Country'])
if 'ecs:DeviceToken' not in parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ecs:GetAccountStatus']:
r = make_response(render_template("ecs/getAccountStatusError.xml", id=ds.consoleid, message=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ecs:GetAccountStatus']['ecs:MessageId'], time=int(round(time.time()*1000)), accountid=ds.id, country=ds.country, region=ds.region, setting=settings))
r.headers.set("Content-Type", "text/xml; charset=utf-8")
return r
try:
ctid = customTitleID.objects.filter(related_to=ds)
except ObjectDoesNotExist:
ctid = []
r = make_response(render_template("ecs/getAccountStatus.xml", id=ds.consoleid, message=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ecs:GetAccountStatus']['ecs:MessageId'], time=int(round(time.time()*1000)), accountid=ds.id, balance=ds.balance, country=ds.country, region=ds.region, customtid=ctid, setting=settings))
r.headers.set("Content-Type", "text/xml; charset=utf-8")
return r
if 'ecs:AccountListETicketIds' in parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']:
try:
ds = Client3DS.objects.get(consoleid=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ecs:AccountListETicketIds']['ecs:DeviceId'])
except ObjectDoesNotExist:
return "Error"
try:
ctid = customTitleID.objects.filter(related_to=ds)
except ObjectDoesNotExist:
ctid = []
r = make_response(render_template("ecs/accountListETicketIds.xml", id=ds.consoleid, message=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ecs:AccountListETicketIds']['ecs:MessageId'],time=int(round(time.time()*1000)), customtid=ctid))
r.headers.set("Content-Type", "text/xml; charset=utf-8")
return r
if 'ecs:DeleteSavedCard' in parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']:
print("Synchronizing")
try:
ds = Client3DS.objects.get(consoleid=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ecs:DeleteSavedCard']['ecs:DeviceId'])
except ObjectDoesNotExist:
return "Error"
r = make_response(render_template("ecs/deleteSavedCard.xml", id=ds.consoleid, message=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ecs:DeleteSavedCard']['ecs:MessageId'],time=int(round(time.time()*1000))))
r.headers.set("Content-Type", "text/xml; charset=utf-8")
return r
if 'ecs:AccountGetETickets' in parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']:
print("Sending ticket...")
try:
title = Title.objects.get(ticket_id=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ecs:AccountGetETickets']['ecs:TicketId'])
except ObjectDoesNotExist:
return "Error"
try:
ds = Client3DS.objects.get(consoleid=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ecs:AccountGetETickets']['ecs:DeviceId'])
except ObjectDoesNotExist:
return "Error"
try:
owned = ownedTitle.objects.get(title=title, owner=ds)
except ObjectDoesNotExist:
return "error"
r = make_response(render_template("ecs/getAccountETickets.xml", id=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ecs:AccountGetETickets']['ecs:DeviceId'], message=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ecs:AccountGetETickets']['ecs:MessageId'],time=int(round(time.time()*1000)), t=title))
r.headers.set("Content-Type", "text/xml; charset=utf-8")
return r

57
ias.py Normal file
View file

@ -0,0 +1,57 @@
from flask import Blueprint, request, render_template, make_response
import xmltodict, time, os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'shopdeck.settings')
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
from shopdeckdb.models import *
from django.core.exceptions import ObjectDoesNotExist
ias = Blueprint("ias", "ias")
@ias.route("/ias/services/IdentityAuthenticationSOAP", methods=['POST'])
def soap():
if request.get_data() == b"UwU":
return "OwO"
try:
parsed = xmltodict.parse(request.get_data())
except:
return "Error"
if request.headers.get('User-Agent') != "CTR EC 040600 Mar 14 2012 13:32:39":
return "Error"
if 'ias:SetIVSData' in parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']:
try:
ds = Client3DS.objects.get(consoleid=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ias:SetIVSData']['ias:DeviceId'])
except ObjectDoesNotExist:
return "Error"
r = make_response(render_template("ias/setIVSData.xml", id=ds.consoleid, message=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ias:SetIVSData']['ias:MessageId'],time=int(round(time.time()*1000))))
r.headers.set("Content-Type", "text/xml; charset=utf-8")
return r
if 'ias:GetChallenge' in parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']:
try:
ds = Client3DS.objects.get(consoleid=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ias:GetChallenge']['ias:DeviceId'])
except ObjectDoesNotExist:
return "Error"
r = make_response(render_template("ias/getChallenge.xml", id=ds.consoleid, message=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ias:GetChallenge']['ias:MessageId'],time=int(round(time.time()*1000))))
r.headers.set("Content-Type", "text/xml; charset=utf-8")
return r
if 'ias:Register' in parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']:
if str(parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ias:Register']['ias:Challenge']) != '526726942':
print(parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ias:Register']['ias:Challenge'])
return "Error"
try:
ds = Client3DS.objects.get(consoleid=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ias:Register']['ias:DeviceId'])
except ObjectDoesNotExist:
return "Error"
r = make_response(render_template("ias/register.xml", id=ds.consoleid, message=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ias:Register']['ias:MessageId'],time=int(round(time.time()*1000)), accountid=ds.id, devicetoken=ds.devicetoken, country=ds.country))
r.headers.set("Content-Type", "text/xml; charset=utf-8")
return r
if 'ias:Unregister' in parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']:
try:
ds = Client3DS.objects.get(consoleid=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ias:Unregister']['ias:DeviceId'])
except ObjectDoesNotExist:
return "Error"
if int(parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ias:Unregister']['ias:AccountId']) != int(ds.id):
return "Error"
ds.delete()
r = make_response(render_template("ias/unregister.xml", id=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ias:Unregister']['ias:DeviceId'], message=parsed['SOAP-ENV:Envelope']['SOAP-ENV:Body']['ias:Unregister']['ias:MessageId'],time=int(round(time.time()*1000))))
r.headers.set("Content-Type", "text/xml; charset=utf-8")
return r

12
main.py Normal file
View file

@ -0,0 +1,12 @@
'''
SOAP Server
Made by Let's Shop! 2023
'''
from flask import Flask
import ecs, ias, cas, cdn
app = Flask(__name__)
app.register_blueprint(ecs.ecs)
app.register_blueprint(ias.ias)
app.register_blueprint(cas.cas)
app.register_blueprint(cdn.ccs)

25
manage.py Normal file
View file

@ -0,0 +1,25 @@
#!/usr/bin/env python
'''
API & Metadata server
Made by Let's Shop! 2023
'''
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'shopdeck.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

0
metadata/__init__.py Normal file
View file

3
metadata/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
metadata/apps.py Normal file
View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class MetadataConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'metadata'

View file

3
metadata/models.py Normal file
View file

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

3
metadata/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

22
metadata/urls.py Normal file
View file

@ -0,0 +1,22 @@
from django.urls import path
from . import views
urlpatterns = [
path('news', views.news, name='news'),
path('telops', views.telops, name="telops"),
path('languages', views.language, name="language"),
path('eshop_message/about', views.eshop_message, name="about"),
path('eshop_message/agreement_send_info', views.agreement_send_info, name="agreementuseless"),
path('directories', views.directories, name="directories"),
path('directory/<int:cid>', views.directory, name="categoryview"),
path('title/<int:tid>', views.title, name="titleview"),
path('movie/<int:mid>', views.viewmovie, name="movie"),
path('searchcategory', views.searchcategory, name="searchcategory"),
path('genres', views.genres, name="genres"),
path('publishers', views.publishers, name="publishers"),
path('contents', views.contents, name="contents"),
path('titles', views.titles, name="titles"),
path('movies', views.movies_content, name="movies"),
path('rankings', views.rankings, name="rankings")
]

331
metadata/views.py Normal file
View file

@ -0,0 +1,331 @@
from django.shortcuts import render
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from shopdeck import settings
from shopdeckdb.models import *
from django.core.exceptions import ObjectDoesNotExist
from urllib.parse import unquote
from dateutil import relativedelta
import datetime
@csrf_exempt
def news(request, region):
news = announcement.objects.all().order_by("-date")
allnews = []
for ann in news:
ann.content = ann.content.replace("\\n", "\n")
result = {"headline": ann.title, "description": ann.content, "date":int(ann.date.timestamp())}
if ann.is_banner:
result.update(images={"image":[{"index": 1, "type": "banner", "url": ann.banner_url, "height": 126, "width": 300}]})
result.update(id=ann.id)
allnews.append(result)
res = {"news": {"news_entry": allnews, "length": len(allnews)}}
return JsonResponse(res)
@csrf_exempt
def telops(request, region):
motds = motd.objects.all().order_by('order')
allmotds = []
for ann in motds:
allmotds.append(ann.content)
res = {"telops": {"telop": allmotds, "length": len(allmotds)}}
return JsonResponse(res)
@csrf_exempt
def language(request, region):
lang = request.GET.get('lang', None)
if lang == None:
return JsonResponse({"error": True})
res = {"languages":{"language":[{"iso_code":lang,"name":"Unknown"}]}}
return JsonResponse(res)
#this should be named just tos but nintendo named it eshop message idfk why
@csrf_exempt
def eshop_message(request, region):
res = {"text": {"type": "html", "body": settings.TOS_ESHOP}}
return JsonResponse(res)
@csrf_exempt
def directories(request, region):
dirs = category.objects.all().order_by('order')
alldirectories = []
for directory in dirs:
alldirectories.append({"name": directory.name, "icon_url": directory.icon_url, "icon_width": 128, "icon_height": 96, "banner_url": directory.banner_url, "index": directory.index, "id": directory.id, "type": "search", "standard": directory.standard, "new": directory.new})
res = {"directories": {"directory": alldirectories, "length": len(alldirectories), "catalog_id": 1}}
return JsonResponse(res)
@csrf_exempt
def directory(request, region, cid):
try:
dir = category.objects.get(id=cid)
except ObjectDoesNotExist:
return JsonResponse({"error": True})
if request.GET.get("platform[]") == None and request.GET.get("genre[]") == None and request.GET.get("publisher[]") == None and request.GET.get("price_max")==None and request.GET.get("price_min")==None:
titles = Title.objects.filter(category=dir, public=True).order_by('-date')[int(request.GET.get("offset")):25+25]
total = titles.count()
movies = movie.objects.filter(category=dir).order_by('-date')
total_movie = movies.count()
total = total+total_movie
else:
#over complicated but at least works
if request.GET.get("platform[]") == None:
platforms = []
all_platforms = platform.objects.all()
for aplatform in all_platforms:
platforms.append(aplatform.id)
else:
platforms = request.GET.get("platform[]").split(",")
if request.GET.get("genre[]") == None:
genrel = []
all_genre = genre.objects.all()
for agenre in all_genre:
genrel.append(agenre.id)
else:
genrel = request.GET.get("genre[]").split(",")
if request.GET.get("publisher[]") == None:
publisherl = []
all_publisher = publisher.objects.all()
for apublisher in all_publisher:
publisherl.append(apublisher.id)
else:
publisherl = request.GET.get("publisher[]").split(",")
if request.GET.get("price_max") != None and request.GET.get("price_min") == None:
titles = Title.objects.filter(price__lte=int(request.GET.get("price_max")),category=dir, platform__in=platforms, genre__in=genrel, publisher__in=publisherl, public=True).order_by("-date")[int(request.GET.get("offset")):25+25]
if request.GET.get("price_min") != None and request.GET.get("price_max") == None:
titles = Title.objects.filter(price__gte=int(request.GET.get("price_min")),category=dir, platform__in=platforms, genre__in=genrel, publisher__in=publisherl, public=True).order_by("-date")[int(request.GET.get("offset")):25+25]
if request.GET.get("price_min") != None and request.GET.get("price_max") == None:
titles = Title.objects.filter(price__gte=int(request.GET.get("price_min")),price__lte=int(request.GET.get("price_max")),category=dir, platform__in=platforms, genre__in=genrel, publisher__in=publisherl, public=True).order_by("-date")[int(request.GET.get("offset")):25+25]
if request.GET.get("price_max") == None and request.GET.get("price_min")==None:
titles = Title.objects.filter(category=dir, platform__in=platforms, genre__in=genrel, publisher__in=publisherl, public=True).order_by("-date")[int(request.GET.get("offset")):25+25]
total = titles.count()
alltitles = []
i = 0
for title in titles:
if title.is_not_downloadable:
is_downloadable = False
else:
is_downloadable = True
alltitles.append({"title": {"platform": {"name": title.platform.name, "id": title.platform.id, "device": "CTR", "category": title.genre.id}, "publisher": {"name": title.publisher.publisher_name, "id": title.publisher.id}, "display_genre": title.genre.name, "release_date_on_eshop": str(title.date), "retail_sales": False, "eshop_sales": is_downloadable, "demo_available": False, "aoc_available": False, "in_app_purchase": title.in_app_purchase, "release_date_on_original": str(title.date), "name": title.name, "id": title.id, "product_code": title.product_code, "icon_url": title.icon_url, "banner_url": title.banner_url, "new": title.new}, "index": i})
i = i + 1
try:
for Movie in movies:
if i > 25:
continue
if Movie.is_3d:
dimension = "3d"
else:
dimension = "2d"
alltitles.append({"movie": {"name": Movie.name, "banner_url": Movie.banner_url, "thumbnail_url": Movie.thumbnail_url, "files": {"file": [{"format": "moflex", "movie_url": Movie.moflex_url, "width": 400, "height": 200, "dimension": dimension, "play_time_sec": Movie.time_in_sec}]}, "id": Movie.id, "new": Movie.new}, "index": i})
i = i + 1
except:
pass
res = {"directory": {"name": dir.name, "icon_url": dir.icon_url, "icon_width": 128, "icon_height": 96, "banner_url": dir.banner_url, "contents": {"content": alltitles, "length": len(alltitles), "offset": int(request.GET.get("offset")), "total": total}, "id": dir.id, "type": "search", "component": "title"}}
return JsonResponse(res)
@csrf_exempt
def title(request, region, tid):
try:
title = Title.objects.get(id=tid)
except ObjectDoesNotExist:
return JsonResponse({"error": True})
title.desc = title.desc.replace("\\n", "\n")
if title.is_not_downloadable:
is_downloadable = False
else:
is_downloadable = True
res = {"title": {"formal_name": title.name, "description": title.desc, "genres": {"genre": [{"name": title.genre.name, "id": title.genre.id}], "length": 1}, "keywords": {}, "ticket_available": title.ticket_available, "title_size": title.size, "download_code_sales": False, "download_card_sales": {"available": False}, "name": title.name, "thumbnails": {"thumbnail": [{"url": title.thumbnail_url, "height": 112, "width": 112, "type": "small"}]}, "id": title.id, "platform": {"name": title.platform.name, "id": title.platform.id, "device": "CTR", "category": title.genre.id }, "publisher": {"name": title.publisher.publisher_name, "id": title.publisher.id}, "product_code": title.product_code, "icon_url": title.icon_url, "banner_url": title.banner_url, "display_genre": title.genre.name, "release_date_on_eshop": str(title.date), "retail_sales": False, "eshop_sales": is_downloadable, "web_sales": is_downloadable, "demo_available": False, "aoc_available": False, "in_app_purchase": title.in_app_purchase, "new": title.new, "public": title.public}}
return JsonResponse(res)
@csrf_exempt
def agreement_send_info(request, region):
return JsonResponse({"text":{"type":"html","body":"This is useless shit that could break your\naccess to Let's Shop! in the future\ndon't accept plz"}})
@csrf_exempt
def searchcategory(request, region):
all_category = searchCategory.objects.all().order_by("-id")
categories = []
for category in all_category:
categories.append({"name": category.name, "params": {"param": [{"key": "platform[]", "value": category.platform_list}]}, "id": category.id})
res = {"search_categories":{"search_category_group":[{"name":"Search categories","search_category":categories}]}}
return JsonResponse(res)
@csrf_exempt
def genres(request, region):
all_genres = genre.objects.all().order_by("-id")
genres = []
for agenre in all_genres:
genres.append({"name": agenre.name, "id": agenre.id})
res = {"genres": {"genre": genres}}
return JsonResponse(res)
@csrf_exempt
def publishers(request, region):
all_publishers = publisher.objects.all().order_by("-id")
publishers = []
for apublisher in all_publishers:
publishers.append({"name": apublisher.publisher_name, "id": apublisher.id})
res = {"publishers": {"publisher": publishers}}
return JsonResponse(res)
@csrf_exempt
def contents(request, region):
search_term = unquote(request.GET.get("freeword"))
all_titles = Title.objects.filter(name__icontains=search_term, public=True).order_by("-date")[int(request.GET.get("offset")):25+25]
total = all_titles.count()
movies = movie.objects.filter(name__icontains=search_term).order_by('-date')
total_movie = movies.count()
total = total+total_movie
titles = []
i = 0
for title in all_titles:
if title.is_not_downloadable:
is_downloadable = False
else:
is_downloadable = True
titles.append({"title": {"platform": {"name": title.platform.name, "id": title.platform.id, "device": "CTR", "category": title.genre.id}, "publisher": {"name": title.publisher.publisher_name, "id": title.publisher.id}, "display_genre": title.genre.name, "release_date_on_eshop": str(title.date), "retail_sales": False, "eshop_sales": is_downloadable, "demo_available": False, "aoc_available": False, "in_app_purchase": title.in_app_purchase, "release_date_on_original": str(title.date), "name": title.name, "id": title.id, "product_code": title.product_code, "icon_url": title.icon_url, "banner_url": title.banner_url, "new": title.new}, "index": i})
i = i + 1
for Movie in movies:
if i > 25:
continue
if Movie.is_3d:
dimension = "3d"
else:
dimension = "2d"
titles.append({"movie": {"name": Movie.name, "banner_url": Movie.banner_url, "thumbnail_url": Movie.thumbnail_url, "files": {"file": [{"format": "moflex", "movie_url": Movie.moflex_url, "width": 400, "height": 200, "dimension": dimension, "play_time_sec": Movie.time_in_sec}]}, "id": Movie.id, "new": Movie.new}, "index": i})
i = i + 1
res = {"contents": {"content": titles, "length": len(titles), "offset": int(request.GET.get("offset")), "total": total}}
return JsonResponse(res)
@csrf_exempt
def titles(request, region):
if request.GET.get("platform[]") == None and request.GET.get("genre[]") == None and request.GET.get("publisher[]") == None and request.GET.get("price_max")==None and request.GET.get("price_min")==None and request.GET.get("title[]") == None:
if request.GET.get("freeword") != None:
search_term = unquote(request.GET.get("freeword"))
all_titles = Title.objects.filter(name__icontains=search_term, public=True).order_by("-date")[int(request.GET.get("offset")):25+25]
if request.GET.get("release_date_after") == None:
movies = movie.objects.filter(name__icontains=search_term).order_by('-date')
if request.GET.get('title[]') == None:
all_titles = Title.objects.filter(public=True).order_by("-date")[int(request.GET.get("offset")):25+25]
if request.GET.get("release_date_after") == None:
movies = movie.objects.all().order_by('-date')
total = all_titles.count()
if request.GET.get("release_date_after") == None:
total_movie = movies.count()
total = total+total_movie
if request.GET.get("platform[]") != None or request.GET.get("genre[]") != None or request.GET.get("publisher[]") != None or request.GET.get("price_max")!=None or request.GET.get("price_min")!=None:
#over complicated but at least works
if request.GET.get("platform[]") == None:
platforms = []
all_platforms = platform.objects.all()
for aplatform in all_platforms:
platforms.append(aplatform.id)
else:
platforms = request.GET.get("platform[]").split(",")
if request.GET.get("genre[]") == None:
genrel = []
all_genre = genre.objects.all()
for agenre in all_genre:
genrel.append(agenre.id)
else:
genrel = request.GET.get("genre[]").split(",")
if request.GET.get("publisher[]") == None:
publisherl = []
all_publisher = publisher.objects.all()
for apublisher in all_publisher:
publisherl.append(apublisher.id)
else:
publisherl = request.GET.get("publisher[]").split(",")
if request.GET.get("freeword") != None:
search_term = unquote(request.GET.get("freeword"))
if request.GET.get("price_max") != None and request.GET.get("price_min") == None:
all_titles = Title.objects.filter(price__lte=int(request.GET.get("price_max")), name__icontains=search_term,platform__in=platforms, genre__in=genrel, publisher__in=publisherl, public=True).order_by("-date")[int(request.GET.get("offset")):25+25]
if request.GET.get("price_min") != None and request.GET.get("price_max") == None:
all_titles = Title.objects.filter(price__gte=int(request.GET.get("price_min")),name__icontains=search_term,platform__in=platforms, genre__in=genrel, publisher__in=publisherl, public=True).order_by("-date")[int(request.GET.get("offset")):25+25]
if request.GET.get("price_max") != None and request.GET.get("price_min") != None:
all_titles = Title.objects.filter(price__lte=int(request.GET.get("price_max")),price__gte=int(request.GET.get("price_min")),name__icontains=search_term,platform__in=platforms, genre__in=genrel, publisher__in=publisherl, public=True).order_by("-date")[int(request.GET.get("offset")):25+25]
if request.GET.get("price_max") == None and request.GET.get("price_min")==None:
all_titles = Title.objects.filter(name__icontains=search_term,platform__in=platforms, genre__in=genrel, publisher__in=publisherl, public=True).order_by("-date")[int(request.GET.get("offset")):25+25]
else:
if request.GET.get("price_max") != None and request.GET.get("price_min") == None:
all_titles = Title.objects.filter(price__lte=int(request.GET.get("price_max")), platform__in=platforms, genre__in=genrel, publisher__in=publisherl, public=True).order_by("-date")[int(request.GET.get("offset")):25+25]
if request.GET.get("price_min") != None and request.GET.get("price_max") == None:
all_titles = Title.objects.filter(price__gte=int(request.GET.get("price_min")), platform__in=platforms, genre__in=genrel, publisher__in=publisherl, public=True).order_by("-date")[int(request.GET.get("offset")):25+25]
if request.GET.get("price_max") != None and request.GET.get("price_min") != None:
all_titles = Title.objects.filter(price__lte=int(request.GET.get("price_max")),price__gte=int(request.GET.get("price_min")),platform__in=platforms, genre__in=genrel, publisher__in=publisherl, public=True).order_by("-date")[int(request.GET.get("offset")):25+25]
if request.GET.get("price_max") == None and request.GET.get("price_min")==None:
all_titles = Title.objects.filter(platform__in=platforms, genre__in=genrel, publisher__in=publisherl, public=True).order_by("-date")[int(request.GET.get("offset")):25+25]
total = all_titles.count()
if request.GET.get("title[]") != None:
title_ids = request.GET.get("title[]").split(",")
all_titles = Title.objects.filter(id__in=title_ids)
total = all_titles.count()
titles = []
i = 0
for title in all_titles:
#check date
if request.GET.get("release_date_after"):
now = datetime.date.today()
time_between_insertion = now - title.date
if int(time_between_insertion.days) > 90:
continue
if title.is_not_downloadable:
is_downloadable = False
else:
is_downloadable = True
titles.append({"title": {"platform": {"name": title.platform.name, "id": title.platform.id, "device": "CTR", "category": title.genre.id}, "publisher": {"name": title.publisher.publisher_name, "id": title.publisher.id}, "display_genre": title.genre.name, "release_date_on_eshop": str(title.date), "retail_sales": False, "eshop_sales": is_downloadable, "demo_available": False, "aoc_available": False, "in_app_purchase": title.in_app_purchase, "release_date_on_original": str(title.date), "name": title.name, "id": title.id, "product_code": title.product_code, "icon_url": title.icon_url, "banner_url": title.banner_url, "new": title.new}, "index": i})
i = i + 1
try:
for Movie in movies:
if i > 25:
continue
if Movie.is_3d:
dimension = "3d"
else:
dimension = "2d"
titles.append({"movie": {"name": Movie.name, "banner_url": Movie.banner_url, "thumbnail_url": Movie.thumbnail_url, "files": {"file": [{"format": "moflex", "movie_url": Movie.moflex_url, "width": 400, "height": 200, "dimension": dimension, "play_time_sec": Movie.time_in_sec}]}, "id": Movie.id, "new": Movie.new}, "index": i})
i = i + 1
except:
pass
if request.GET.get("offset") == None:
offset = 0
else:
offset = int(request.GET.get("offset"))
res = {"contents": {"content": titles, "length": len(titles), "offset": offset, "total": total}}
return JsonResponse(res)
@csrf_exempt
def viewmovie(request, region, mid):
try:
Movie = movie.objects.get(id=mid)
except ObjectDoesNotExist:
return JsonResponse({"error": True})
if Movie.is_3d:
dimension = "3d"
else:
dimension = "2d"
res = {"movie": {"name": Movie.name, "banner_url": Movie.banner_url, "thumbnail_url": Movie.thumbnail_url, "files": {"file": [{"format": "moflex", "movie_url": Movie.moflex_url, "width": 400, "height": 200, "dimension": dimension, "play_time_sec": Movie.time_in_sec}]}, "id": Movie.id, "new": Movie.new}}
return JsonResponse(res)
@csrf_exempt
def movies_content(request, region):
movies = movie.objects.all().order_by('-date')[int(request.GET.get("offset")):25+25]
total = movies.count()
all_movies = []
i = 0
for Movie in movies:
if request.GET.get("release_date_after"):
now = datetime.date.today()
time_between_insertion = now - Movie.date
if int(time_between_insertion.days) > 7:
continue
if Movie.is_3d:
dimension = "3d"
else:
dimension = "2d"
all_movies.append({"movie": {"name": Movie.name, "banner_url": Movie.banner_url, "thumbnail_url": Movie.thumbnail_url, "files": {"file": [{"format": "moflex", "movie_url": Movie.moflex_url, "width": 400, "height": 200, "dimension": dimension, "play_time_sec": Movie.time_in_sec}]}, "id": Movie.id, "new": Movie.new}, "index": i})
i = i + 1
res = {"contents": {"content": all_movies, "length": len(all_movies), "offset": int(request.GET.get("offset")), "total": total}}
return JsonResponse(res)
@csrf_exempt
def rankings(request, region):
return JsonResponse({"error": {"code": "5626", "message": "WIP. Not ready yet."}}, status=400)

3
requirements.txt Normal file
View file

@ -0,0 +1,3 @@
django==4.1.6
pyctr==0.6.0
Flask=2.2.2

0
shopdeck/__init__.py Normal file
View file

16
shopdeck/asgi.py Normal file
View file

@ -0,0 +1,16 @@
"""
ASGI config for shopdeck project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'shopdeck.settings')
application = get_asgi_application()

133
shopdeck/settings.py Normal file
View file

@ -0,0 +1,133 @@
"""
Django settings for shopdeck project.
Generated by 'django-admin startproject' using Django 4.1.6.
For more information on this file, see
https://docs.djangoproject.com/en/4.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.1/ref/settings/
"""
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'somestring'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['*']
# Application definition
INSTALLED_APPS = [
'shopdeckdb.apps.ShopdeckdbConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_extensions',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'shopdeck.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'shopdeck.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.django.project.com/en/4.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.1/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Server URLS (dont include a trailing slash at the end)
SOAP_URL = "soap.example.com"
METADATA_API_URL = "api.example.com"
# TOS
TOS_ESHOP = "This is YOUR own custom shop!\nStart customizing it!\n(change this message in shopdeck/settings.py)"
#dont touch
SESSION_COOKIE_NAME = "JSESSIONID"

23
shopdeck/urls.py Normal file
View file

@ -0,0 +1,23 @@
"""shopdeck URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.1/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.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('ninja/ws/', include('api.urls')),
path('samurai/ws/<str:region>/', include('metadata.urls')),
]

16
shopdeck/wsgi.py Normal file
View file

@ -0,0 +1,16 @@
"""
WSGI config for shopdeck project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'shopdeck.settings')
application = get_wsgi_application()

0
shopdeckdb/__init__.py Normal file
View file

18
shopdeckdb/admin.py Normal file
View file

@ -0,0 +1,18 @@
from django.contrib import admin
from .models import *
# Register your models here.
admin.site.register(Client3DS)
admin.site.register(customTitleID)
admin.site.register(Title)
admin.site.register(ownedTitle)
admin.site.register(wishlistedTitle)
admin.site.register(announcement)
admin.site.register(motd)
admin.site.register(category)
admin.site.register(publisher)
admin.site.register(genre)
admin.site.register(platform)
admin.site.register(redeemableCard)
admin.site.register(searchCategory)
admin.site.register(movie)

6
shopdeckdb/apps.py Normal file
View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class ShopdeckdbConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'shopdeckdb'

View file

135
shopdeckdb/models.py Normal file
View file

@ -0,0 +1,135 @@
from django.db import models
# Create your models here.
class Client3DS(models.Model):
id = models.IntegerField(primary_key=True, blank=True)
consoleid = models.CharField(max_length=12, null=False)
devicetoken = models.CharField(max_length=21, null=False)
is_terminated = models.BooleanField(default=False)
balance = models.IntegerField(default=0)
language = models.CharField(max_length=3, null=False)
region = models.CharField(max_length=3, null=False)
country = models.CharField(max_length=3, null=False)
def __str__(self):
return "3DS "+self.consoleid
class customTitleID(models.Model):
tid = models.CharField(max_length=18, null=False)
related_to = models.ForeignKey(Client3DS, null=False, on_delete=models.CASCADE)
def __str__(self):
return "Title "+self.tid+" for user "+self.related_to.consoleid
class publisher(models.Model):
id = models.IntegerField(primary_key=True, blank=True)
publisher_name = models.CharField(max_length=20)
def __str__(self):
return self.publisher_name
class category(models.Model):
id = models.IntegerField(primary_key=True, blank=True)
index = models.IntegerField(default=0, null=False)
name = models.CharField(max_length=25)
standard = models.BooleanField(default=False)
icon_url = models.TextField(null=False)
banner_url = models.TextField(null=False)
new = models.BooleanField(default=True)
order = models.IntegerField(default=0)
def __str__(self):
return "Category "+self.name
class genre(models.Model):
id = models.IntegerField(primary_key=True, blank=True)
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class platform(models.Model):
id = models.IntegerField(primary_key=True, blank=True)
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Title(models.Model):
id = models.IntegerField(primary_key=True, blank=True)
tid = models.CharField(max_length=16, null=False)
name = models.CharField(max_length=25)
desc = models.TextField(default="", null=False)
thumbnail_url = models.TextField(null=False)
icon_url = models.TextField(null=False)
banner_url = models.TextField(null=False)
publisher = models.ForeignKey(publisher, on_delete=models.CASCADE)
date = models.DateField(auto_now_add=True, null=False)
product_code = models.CharField(max_length=30)
new = models.BooleanField(default=True)
public = models.BooleanField(default=True)
category = models.ForeignKey(category, on_delete=models.DO_NOTHING, null=True, blank=True)
genre = models.ForeignKey(genre, null=False, on_delete=models.CASCADE)
in_app_purchase = models.BooleanField(default=False)
platform = models.ForeignKey(platform, null=False, on_delete=models.CASCADE)
price = models.IntegerField(default=0, null=False)
version = models.IntegerField(default=1024)
is_not_downloadable = models.BooleanField(default=False)
ticket_id = models.CharField(max_length=16, null=False)
ticket = models.TextField(null=False)
size = models.IntegerField(default=0)
ticket_available = models.BooleanField(default=True)
def __str__(self):
return self.name+" by "+self.publisher.publisher_name+" published on "+str(self.date)
class movie(models.Model):
id = models.IntegerField(primary_key=True, blank=True)
name = models.CharField(max_length=25)
thumbnail_url = models.TextField(null=False)
banner_url = models.TextField(null=False)
is_3d = models.BooleanField(default=False)
moflex_url = models.TextField(null=False)
time_in_sec = models.IntegerField(null=False)
date = models.DateField(auto_now_add=True, null=False)
category = models.ForeignKey(category, on_delete=models.DO_NOTHING, null=True, blank=True)
new = models.BooleanField(default=True, null=False)
def __str__(self):
return self.name
class ownedTitle(models.Model):
title = models.ForeignKey(Title, null=False, on_delete=models.CASCADE)
owner = models.ForeignKey(Client3DS, null=False, on_delete=models.CASCADE)
def __str__(self):
return "Title "+self.title.name+" owned by "+self.owner.consoleid
class wishlistedTitle(models.Model):
title = models.ForeignKey(Title, null=False, on_delete=models.CASCADE)
owner = models.ForeignKey(Client3DS, null=False, on_delete=models.CASCADE)
def __str__(self):
return "Wishlisted title "+self.title.name+" wanted by "+self.owner.consoleid
class announcement(models.Model):
id = models.IntegerField(primary_key=True, blank=True)
title = models.CharField(max_length=50, null=False)
content = models.TextField(null=False)
date = models.DateTimeField(auto_now_add=True, null=False)
is_banner = models.BooleanField(default=False)
banner_url = models.TextField(null=True, blank=True)
def __str__(self):
return "Announcement "+self.title
class motd(models.Model):
content = models.TextField(null=False)
order = models.IntegerField(default=0)
def __str__(self):
return self.content
class redeemableCard(models.Model):
code = models.CharField(max_length=16, null=False)
used = models.BooleanField(default=False)
is_money = models.BooleanField(default=True)
content = models.CharField(max_length=16, null=False)
def __str__(self):
return self.code
class searchCategory(models.Model):
id = models.IntegerField(primary_key=True, blank=True)
name = models.CharField(max_length=35)
platform_list = models.TextField()
def __str__(self):
return self.name

View file

@ -0,0 +1 @@
{% extends 'header.xml' %}{% block main %}<ListTitlesExResponse xmlns="urn:cas.wsapi.broadon.com"><Version>2.0</Version><DeviceId>{{ id }}</DeviceId><MessageId>{{ message }}</MessageId><TimeStamp>{{ time }}</TimeStamp><ErrorCode>0</ErrorCode><ListResultTotalSize>1</ListResultTotalSize><Titles><TitleId>{{ t.tid }}</TitleId><Attributes><Name>MaxUserFileSize</Name><Value>{{ t.size }}</Value></Attributes><Attributes><Name>Version</Name><Value>{{ t.version }}</Value></Attributes></Titles></ListTitlesExResponse>{% endblock %}

View file

@ -0,0 +1 @@
{% extends 'header.xml' %}{% block main %}<AccountListETicketIdsResponse xmlns="urn:ecs.wsapi.broadon.com"><Version>2.0</Version><DeviceId>{{ id }}</DeviceId><MessageId>{{ message }}</MessageId><TimeStamp>{{ time }}</TimeStamp><ErrorCode>0</ErrorCode><ServiceStandbyMode>false</ServiceStandbyMode>{% for custom in customtid %}<TIV>{{ custom.tid }}</TIV>{% endfor %}</AccountListETicketIdsResponse>{% endblock %}

View file

@ -0,0 +1 @@
{% extends 'header.xml' %}{% block main %}<DeleteSavedCardResponse xmlns="urn:ecs.wsapi.broadon.com"><Version>2.0</Version><DeviceId>{{ id }}</DeviceId><MessageId>{{ message }}</MessageId><TimeStamp>{{ time }}</TimeStamp><ErrorCode>0</ErrorCode><ServiceStandbyMode>false</ServiceStandbyMode></DeleteSavedCardResponse>{% endblock %}

View file

@ -0,0 +1 @@
{% extends 'header.xml' %}{% block main %}<AccountGetETicketsResponse xmlns="urn:ecs.wsapi.broadon.com"><Version>2.0</Version><DeviceId>{{ id }}</DeviceId><MessageId>{{ message }}</MessageId><TimeStamp>{{ time }}</TimeStamp><ErrorCode>0</ErrorCode><ServiceStandbyMode>false</ServiceStandbyMode><ETickets>{{ t.ticket }}</ETickets><Certs>AAEAA3BBOO+7vaFqmH3ZATJtHJRZSEyIooYbkaMSWHrnDvYjfsUOEDLcOd3ompao6FnXapim5+NqDP41LKiTBYI0/4M/yzsDgR6fDcDZpS+ARbSy+UEbZ6UcRLXvjOd71tVrp1c0oYVt5tS+1tOiQsfIeRs0IjdeXHeavwcvdpXvoPdby4N4n8MOP+TMg5IgeEBjiUnH9ohWX2SbdNY9jVj/rdpXHpVUQmsTGPxGiYPUyKViiwa2/F1QfBPnoYrBUR621i6lRI+DUBRHqa+z7MKQPJ3VL5Iqyazb71jGAhhI2W4ghzLT0dnZ6kQNkWIcepnbiEPFnB8uLH2bV31RLBZtb34arUp3SjdEfnj+ICHhSpXREqBoraAZ9GPHpVaFqrtoiLkkZIPRi5yAb0dJGDMXgjRKS4UxM0smMDJj2dLrT0u5lgKzUvauQEbGml5+jkoY75vAot7WExBBcBL9gkzBFs+3xMH37HF3oXRGy96W8+3Yj80FLwuIikX9rytjE1T0DRbl+pwsTtqY55jRXmBG3FNj8wlrLGB6nY3VWxUCpqx9PMjYxXWZjn15aRDIBMSVI1BX6R7NJjfJwYRRUaxrmgSQrj7G9HdAoNsLo20HWVbO5zVOo+mk8nILJlUMfTlDJLwMt+kxfYqGYfQhkf8QsIJWzj/SW3ReUZSQa01hy0wuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUm9vdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFDQTAwMDAwMDAzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAe+jvbLJ5yeLu4SHG6vRP9jn4jweLS3ftn5VgsDWCgbUOVatyERWhd3A8ejD+OunvHGC8HZdGdrI6aMwEsZhSW8lo8R3i21Dk2efwceVi2uIJIjPp02P2HdfBn/OkqR6PZVPUcd17hLnxuM5zNfD1VAVjoeq4OWPgm+kBAR+ZVGNhKHAg6cwNq0h/FA1mJqGDbScRHyBo3kdyFJFRz2nGG6YO+dlJoPcfVJny05rSjHAFNIKTxDH/vTP2vKYNxxleorzFbSALr20G0JxB243pxyAVTKSDK2nAjGnNOwc6AGNgL0YtM4BhpepskVzVYjV5w+tkzkTvWG0UuqqINAGbPuvu03kAAQABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==</Certs><Certs>AAEABJGevkZK0PVSzRty54hJEM9VqfAuUHiWQdiWaD3ABb0K6ocHnYrChMZ1Bl90yL83yIBEQJUCoCKYC7itSDg/bSinneOWJsyysioPGeQQMvCUs5/wEzFG3sj2wanVXNKNnhxHs9EfT1QmwseAE1onddPKZ5vH6DTw4PtY5ohgpxMw/JV5F5PI+6k1p6aQjyKd7ioMprmyOxLUlab+GdDXJkghaHhgWmZTjb83aJmQXTRF/Fxyeg4T4OLIlxyc+mxgZ4h1cypOdVI9L1YvEqq9FXO/BslAVK76gacUF6+aSgZtD/xa1kurKLH/YGYfRDfUnh4NlBLrS8rPTP1qNAiEeYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSb290LUNBMDAwMDAwMDMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVhTMDAwMDAwMGMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATegiUrVBbtsZ+LlvdajvsQ9kQx3LpzCkNpYWIt33MEWgLs+KfTqu7JumMJgGYXAQbsUN45okYGq13BWjpKKK5gWfuPhDQcr7vH6Ivoqo+E/EeGDapKkKB73Cq9ORimYIhxvu5vdAX5qxZBJTpzqmFnOstKkwXZvLDORLFjxSoA+NvzNzM3BP9eud8enjZl+asw1VX4NPp62S0PJL0xQ1npgLes5GwZmHNMogL1kkSrxy8txYqBvAlZdOw7OT87N2uikk0247mfzAXmGIhFV0THGw/CasZRcIGrHDJQrNvSaEYO814tuS0fGxcrA+NYviXxpU90S8otwxbffdRgZqYNGUmJQABAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</Certs></AccountGetETicketsResponse>{% endblock %}

View file

@ -0,0 +1 @@
{% extends "header.xml" %}{% block main %}<GetAccountStatusResponse xmlns="urn:ecs.wsapi.broadon.com"><Version>2.0</Version><DeviceId>{{ id }}</DeviceId><MessageId>{{ message }}</MessageId><TimeStamp>{{ time }}</TimeStamp><ErrorCode>0</ErrorCode><ServiceStandbyMode>false</ServiceStandbyMode><AccountId>{{ accountid }}</AccountId><AccountStatus>R</AccountStatus><Balance><Amount>{{ balance }}</Amount><Currency>CREDIT</Currency></Balance><EulaVersion>0</EulaVersion><Country>{{ country }}</Country><Region>{{ region }}</Region><AccountAttributes><Name>LOYALTY_LOGIN_NAME</Name><Value></Value></AccountAttributes>{% for custom in customtid %}<TIV>{{ custom.tid }}</TIV>{% endfor %}<ServiceURLs><Name>ContentPrefixURL</Name><URI>https://{{ setting.SOAP_URL }}/ccs/download</URI></ServiceURLs><ServiceURLs><Name>UncachedContentPrefixURL</Name><URI>https://{{ setting.SOAP_URL }}/ccs/download</URI></ServiceURLs><ServiceURLs><Name>SystemContentPrefixURL</Name><URI>https://{{ setting.SOAP_URL }}/ccs/download</URI></ServiceURLs><ServiceURLs><Name>SystemUncachedContentPrefixURL</Name><URI>https://{{ setting.SOAP_URL }}/ccs/download</URI></ServiceURLs><ServiceURLs><Name>EcsURL</Name><URI>https://{{ setting.SOAP_URL }}/ecs/services/ECommerceSOAP</URI></ServiceURLs><ServiceURLs><Name>IasURL</Name><URI>https://{{ setting.SOAP_URL }}/ias/services/IdentityAuthenticationSOAP</URI></ServiceURLs><ServiceURLs><Name>CasURL</Name><URI>https://{{ setting.SOAP_URL }}/cas/services/CatalogingSOAP</URI></ServiceURLs><ServiceURLs><Name>NusURL</Name><URI>https://nus.c.shop.nintendowifi.net/nus/services/NetUpdateSOAP</URI></ServiceURLs><IVSSyncFlag>false</IVSSyncFlag><CountryAttribits>15</CountryAttribits></GetAccountStatusResponse>{% endblock %}

View file

@ -0,0 +1 @@
{% extends 'header.xml' %}{% block main %}<GetAccountStatusResponse xmlns="urn:ecs.wsapi.broadon.com"><Version>2.0</Version><DeviceId>{{ id }}</DeviceId><MessageId>{{ message }}</MessageId><TimeStamp>{{ time }}</TimeStamp><ErrorCode>903</ErrorCode><ErrorMessage>IAS - Invalid device token</ErrorMessage><ServiceStandbyMode>false</ServiceStandbyMode><AccountId>{{ accountid }}</AccountId><AccountStatus>R</AccountStatus><EulaVersion>0</EulaVersion><Country>{{ country }}</Country><Region>{{ region }}</Region><ServiceURLs><Name>ContentPrefixURL</Name><URI>https://{{ setting.SOAP_URL }}/ccs/download</URI></ServiceURLs><ServiceURLs><Name>UncachedContentPrefixURL</Name><URI>https://{{ setting.SOAP_URL }}/ccs/download</URI></ServiceURLs><ServiceURLs><Name>SystemContentPrefixURL</Name><URI>https://{{ setting.SOAP_URL }}/ccs/download</URI></ServiceURLs><ServiceURLs><Name>SystemUncachedContentPrefixURL</Name><URI>https://{{ setting.SOAP_URL }}/ccs/download</URI></ServiceURLs><ServiceURLs><Name>EcsURL</Name><URI>https://{{ setting.SOAP_URL }}/ecs/services/ECommerceSOAP</URI></ServiceURLs><ServiceURLs><Name>IasURL</Name><URI>https://{{ setting.SOAP_URL }}/ias/services/IdentityAuthenticationSOAP</URI></ServiceURLs><ServiceURLs><Name>CasURL</Name><URI>https://{{ setting.SOAP_URL }}/cas/services/CatalogingSOAP</URI></ServiceURLs><ServiceURLs><Name>NusURL</Name><URI>https://nus.c.shop.nintendowifi.net/nus/services/NetUpdateSOAP</URI></ServiceURLs><IVSSyncFlag>false</IVSSyncFlag></GetAccountStatusResponse>{% endblock %}

1
templates/header.xml Normal file
View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><soapenv:Body>{% block main %}{% endblock %}</soapenv:Body></soapenv:Envelope>

View file

@ -0,0 +1 @@
{% extends 'header.xml' %}{% block main %}<GetChallengeResponse xmlns="urn:ias.wsapi.broadon.com"><Version>2.0</Version><DeviceId>{{ id }}</DeviceId><MessageId>{{ message }}</MessageId><TimeStamp>{{ time }}</TimeStamp><ErrorCode>0</ErrorCode><ServiceStandbyMode>false</ServiceStandbyMode><Challenge>526726942</Challenge></GetChallengeResponse>{% endblock %}

View file

@ -0,0 +1 @@
{% extends 'header.xml' %}{% block main %}<RegisterResponse xmlns="urn:ias.wsapi.broadon.com"><Version>2.0</Version><DeviceId>{{ id }}</DeviceId><MessageId>{{ message }}</MessageId><TimeStamp>{{ time }}</TimeStamp><ErrorCode>0</ErrorCode><ServiceStandbyMode>false</ServiceStandbyMode><AccountId>{{ accountid }}</AccountId><DeviceToken>{{ devicetoken }}</DeviceToken><Country>{{ country }}</Country><ExtAccountId></ExtAccountId></RegisterResponse>{% endblock %}

View file

@ -0,0 +1 @@
{% extends 'header.xml' %}{% block main %}<SetIVSDataResponse xmlns="urn:ias.wsapi.broadon.com"><Version>2.0</Version><DeviceId>{{ id }}</DeviceId><MessageId>{{ message }}</MessageId><TimeStamp>{{ time }}</TimeStamp><ErrorCode>0</ErrorCode><ServiceStandbyMode>false</ServiceStandbyMode></SetIVSDataResponse>{% endblock %}

View file

@ -0,0 +1 @@
{% extends 'header.xml' %}{% block main %}<UnregisterResponse xmlns="urn:ias.wsapi.broadon.com"><Version>2.0</Version><DeviceId>{{ id }}</DeviceId><MessageId>{{ message }}</MessageId><TimeStamp>{{ time }}</TimeStamp><ErrorCode>0</ErrorCode><ServiceStandbyMode>false</ServiceStandbyMode></UnregisterResponse>{% endblock %}