Replay stealing (excluding web version)
This commit is contained in:
parent
b7dc7d33ca
commit
eca63a5288
|
@ -728,15 +728,17 @@ class TetraLeagueAlphaRecord{
|
|||
late String replayId;
|
||||
late String ownId;
|
||||
late DateTime timestamp;
|
||||
late bool replayAvalable;
|
||||
late List<EndContextMulti> endContext;
|
||||
|
||||
TetraLeagueAlphaRecord({required this.replayId, required this.ownId, required this.timestamp, required this.endContext});
|
||||
TetraLeagueAlphaRecord({required this.replayId, required this.ownId, required this.timestamp, required this.endContext, required this.replayAvalable});
|
||||
|
||||
TetraLeagueAlphaRecord.fromJson(Map<String, dynamic> json) {
|
||||
ownId = json['_id'];
|
||||
endContext = [EndContextMulti.fromJson(json['endcontext'][0]), EndContextMulti.fromJson(json['endcontext'][1])];
|
||||
replayId = json['replayid'];
|
||||
timestamp = DateTime.parse(json['ts']);
|
||||
replayAvalable = true;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
|
|
|
@ -24,8 +24,18 @@ class P1nkl0bst3rTooManyRequests implements Exception {}
|
|||
|
||||
class P1nkl0bst3rForbidden implements Exception {}
|
||||
|
||||
class SzyTooManyRequests implements Exception {}
|
||||
|
||||
class SzyForbidden implements Exception {}
|
||||
|
||||
class SzyNotFound implements Exception {}
|
||||
|
||||
class TetrioReplayAlreadyExist implements Exception {}
|
||||
|
||||
class P1nkl0bst3rInternalProblem implements Exception {}
|
||||
|
||||
class SzyInternalProblem implements Exception {}
|
||||
|
||||
class TetrioOskwareBridgeProblem implements Exception {}
|
||||
|
||||
class TetrioInternalProblem implements Exception {}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer' as developer;
|
||||
import 'dart:io';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:tetra_stats/main.dart' show packageInfo;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:tetra_stats/services/custom_http_client.dart';
|
||||
|
@ -57,7 +59,8 @@ class TetrioService extends DB {
|
|||
final Map<String, List<News>> _newsCache = {};
|
||||
final Map<String, Map<String, double?>> _topTRcache = {};
|
||||
final Map<String, TetraLeagueAlphaStream> _tlStreamsCache = {}; // i'm trying to respect oskware api It should look something like {"cached_until": TetrioPlayer}
|
||||
final client = UserAgentClient("Tetra Stats v${packageInfo.version} (dm @dan63047 if someone abuse that software)", http.Client());
|
||||
// final client = UserAgentClient("Tetra Stats v${packageInfo.version} (dm @dan63047 if someone abuse that software)", http.Client());
|
||||
final client = UserAgentClient("Kagari-chan loves osk (Tetra Stats dev build)", http.Client());
|
||||
static final TetrioService _shared = TetrioService._sharedInstance();
|
||||
factory TetrioService() => _shared;
|
||||
late final StreamController<Map<String, List<TetrioPlayer>>> _tetrioStreamController;
|
||||
|
@ -107,6 +110,42 @@ class TetrioService extends DB {
|
|||
}
|
||||
}
|
||||
|
||||
Future<String> szyDownloadAndSaveReplay(String replayID) async {
|
||||
Uri url = Uri.https('inoue.szy.lol', '/api/replay/$replayID');
|
||||
var downloadPath = await getDownloadsDirectory();
|
||||
downloadPath ??= Platform.isAndroid ? Directory("/storage/emulated/0/Download") : await getApplicationDocumentsDirectory();
|
||||
var replayFile = File("${downloadPath.path}/$replayID.ttrm");
|
||||
if (replayFile.existsSync()) throw TetrioReplayAlreadyExist();
|
||||
try{
|
||||
final response = await client.get(url);
|
||||
|
||||
switch (response.statusCode) {
|
||||
case 200:
|
||||
await replayFile.writeAsBytes(response.bodyBytes);
|
||||
return replayFile.path;
|
||||
case 404:
|
||||
throw SzyNotFound();
|
||||
case 403:
|
||||
throw SzyForbidden();
|
||||
case 429:
|
||||
throw SzyTooManyRequests();
|
||||
case 418:
|
||||
throw TetrioOskwareBridgeProblem();
|
||||
case 500:
|
||||
case 502:
|
||||
case 503:
|
||||
case 504:
|
||||
throw SzyInternalProblem();
|
||||
default:
|
||||
developer.log("szyDownloadAndSaveReplay: Failed to download a replay", name: "services/tetrio_crud", error: response.statusCode);
|
||||
throw ConnectionIssue(response.statusCode, response.reasonPhrase??"No reason");
|
||||
}
|
||||
} on http.ClientException catch (e, s) {
|
||||
developer.log("$e, $s");
|
||||
throw http.ClientException(e.message, e.uri);
|
||||
}
|
||||
}
|
||||
|
||||
Future<double?> fetchTopTR(String id) async {
|
||||
try{
|
||||
var cached = _topTRcache.entries.firstWhere((element) => element.value.keys.first == id);
|
||||
|
@ -441,7 +480,7 @@ class TetrioService extends DB {
|
|||
List<TetraLeagueAlphaRecord> matches = [];
|
||||
final results = await db.query(tetraLeagueMatchesTable, where: '($player1id = ?) OR ($player2id = ?)', whereArgs: [playerID, playerID]);
|
||||
for (var match in results){
|
||||
matches.add(TetraLeagueAlphaRecord(ownId: match[idCol].toString(), replayId: match[replayID].toString(), timestamp: DateTime.parse(match[timestamp].toString()), endContext:[EndContextMulti.fromJson(jsonDecode(match[endContext1].toString())), EndContextMulti.fromJson(jsonDecode(match[endContext2].toString()))]));
|
||||
matches.add(TetraLeagueAlphaRecord(ownId: match[idCol].toString(), replayId: match[replayID].toString(), timestamp: DateTime.parse(match[timestamp].toString()), endContext:[EndContextMulti.fromJson(jsonDecode(match[endContext1].toString())), EndContextMulti.fromJson(jsonDecode(match[endContext2].toString()))], replayAvalable: false));
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
Future<void> launchInBrowser(Uri url) async {
|
||||
if (!await launchUrl(
|
||||
url,
|
||||
mode: LaunchMode.externalApplication,
|
||||
)) {
|
||||
throw Exception('Could not launch $url');
|
||||
}
|
||||
}
|
|
@ -159,11 +159,12 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
|
|||
List<TetrioPlayer> states = [];
|
||||
TetraLeagueAlpha? compareWith;
|
||||
var uniqueTL = <dynamic>{};
|
||||
tlMatches = tlStream.records;
|
||||
if (isTracking){
|
||||
await teto.storeState(me);
|
||||
await teto.saveTLMatchesFromStream(tlStream);
|
||||
tlMatches.addAll(await teto.getTLMatchesbyPlayerID(me.userId));
|
||||
for (var match in tlStream.records) {
|
||||
var storedRecords = await teto.getTLMatchesbyPlayerID(me.userId);
|
||||
for (var match in storedRecords) {
|
||||
if (!tlMatches.contains(match)) tlMatches.add(match);
|
||||
}
|
||||
tlMatches.sort((a, b) {
|
||||
|
@ -172,8 +173,6 @@ class _MainState extends State<MainView> with SingleTickerProviderStateMixin {
|
|||
if(a.timestamp.isAfter(b.timestamp)) return -1;
|
||||
return 0;
|
||||
});
|
||||
} else{
|
||||
tlMatches = tlStream.records;
|
||||
}
|
||||
if(fetchHistory) await teto.fetchAndsaveTLHistory(_searchFor);
|
||||
states.addAll(await teto.getPlayer(me.userId));
|
||||
|
|
|
@ -9,7 +9,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||
import 'package:tetra_stats/gen/strings.g.dart';
|
||||
import 'package:tetra_stats/services/crud_exceptions.dart';
|
||||
import 'package:tetra_stats/services/tetrio_crud.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:tetra_stats/utils/open_in_browser.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
late String oldWindowTitle;
|
||||
|
@ -43,15 +43,6 @@ class SettingsState extends State<SettingsView> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _launchInBrowser(Uri url) async {
|
||||
if (!await launchUrl(
|
||||
url,
|
||||
mode: LaunchMode.externalApplication,
|
||||
)) {
|
||||
throw Exception('Could not launch $url');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _getPreferences() async {
|
||||
prefs = await SharedPreferences.getInstance();
|
||||
_setDefaultNickname(prefs.getString("player"));
|
||||
|
@ -248,7 +239,7 @@ class SettingsState extends State<SettingsView> {
|
|||
const Divider(),
|
||||
ListTile(
|
||||
onTap: (){
|
||||
_launchInBrowser(Uri.https("github.com", "dan63047/TetraStats"));
|
||||
launchInBrowser(Uri.https("github.com", "dan63047/TetraStats"));
|
||||
},
|
||||
title: Text(t.aboutApp),
|
||||
subtitle: Text(t.aboutAppText(appName: packageInfo.appName, packageName: packageInfo.packageName, version: packageInfo.version, buildNumber: packageInfo.buildNumber)),
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:io';
|
||||
import 'package:tetra_stats/services/crud_exceptions.dart';
|
||||
|
||||
import 'main_view.dart' show teto;
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:tetra_stats/data_objects/tetrio.dart';
|
||||
import 'package:tetra_stats/gen/strings.g.dart';
|
||||
import 'package:tetra_stats/utils/open_in_browser.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
// ignore: avoid_web_libraries_in_flutter
|
||||
// import 'dart:html' show AnchorElement, document;
|
||||
|
||||
|
||||
final DateFormat dateFormat = DateFormat.yMMMd(LocaleSettings.currentLocale.languageCode).add_Hms();
|
||||
|
@ -52,6 +60,52 @@ class TlMatchResultState extends State<TlMatchResultView> {
|
|||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("${widget.record.endContext.firstWhere((element) => element.userId == widget.initPlayerId).username.toUpperCase()} ${t.vs} ${widget.record.endContext.firstWhere((element) => element.userId != widget.initPlayerId).username.toUpperCase()} ${t.inTLmatch} ${dateFormat.format(widget.record.timestamp)}"),
|
||||
actions: [
|
||||
PopupMenuButton(
|
||||
enabled: widget.record.replayAvalable,
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||
const PopupMenuItem(
|
||||
value: 1,
|
||||
child: Text("Download le replay"),
|
||||
),
|
||||
const PopupMenuItem(
|
||||
value: 2,
|
||||
child: Text("Open le replay in TETR.IO"),
|
||||
),
|
||||
],
|
||||
onSelected: (value) async {
|
||||
switch (value) {
|
||||
case 1:
|
||||
if (kIsWeb){
|
||||
// final _base64 = base64Encode([1,2,3,4,5]);
|
||||
// final anchor = AnchorElement(href: 'data:application/octet-stream;base64,$_base64')..target = 'blank';
|
||||
//final anchor = AnchorElement(href: 'https://inoue.szy.lol/api/replay/${widget.record.replayId}')..target = 'blank';
|
||||
//anchor.download = "${widget.record.replayId}.ttrm";
|
||||
//document.body!.append(anchor);
|
||||
//anchor.click();
|
||||
//anchor.remove();
|
||||
} else{
|
||||
try{
|
||||
String path = await teto.szyDownloadAndSaveReplay(widget.record.replayId);
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Replay saved to $path")));
|
||||
} on TetrioReplayAlreadyExist{
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Replay already saved")));
|
||||
} on SzyNotFound {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Replay expired (i think)")));
|
||||
} on SzyForbidden {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Request has been rejected")));
|
||||
} on SzyTooManyRequests {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(t.errors.tooManyRequests)));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
await launchInBrowser(Uri.parse("https://tetr.io/#r:${widget.record.replayId}"));
|
||||
break;
|
||||
default:
|
||||
}
|
||||
})
|
||||
]
|
||||
),
|
||||
backgroundColor: Colors.black,
|
||||
body: SafeArea(
|
||||
|
|
Loading…
Reference in New Issue