Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ firebase.json
**/ios/Flutter/flutter_assets/
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
**/ios/Flutter/ephemeral/

# Windows
**/windows/flutter/ephemeral/
Expand Down
4 changes: 4 additions & 0 deletions assets/userscripts/apis/GM.getValue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// https://wiki.greasespot.net/GM.getValue
GM.getValue = function(key, defaultValue) {
return Promise.resolve(localStorage.getItem(key) ?? defaultValue);
}
5 changes: 5 additions & 0 deletions assets/userscripts/apis/GM.setValue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// https://wiki.greasespot.net/GM.setValue
GM.setValue = function(key, value) {
localStorage.setItem(key, value)
return Promise.resolve();
}
3 changes: 3 additions & 0 deletions assets/userscripts/apis/GM_getValue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
let GM_getValue = function(key, defaultValue) {
return localStorage.getItem(key) ?? defaultValue;
}
3 changes: 3 additions & 0 deletions assets/userscripts/apis/GM_setValue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
let GM_setValue = function(key, value) {
localStorage.setItem(key, value);
}
22 changes: 22 additions & 0 deletions assets/userscripts/apis/default.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// > All scripts always get GM.info even without specifically requesting it.
// per https://wiki.greasespot.net/@grant
let GM = {
info: {
// https://wiki.greasespot.net/GM.info
scriptHandler: 'TornPDA',
scriptMetaStr: '',
scriptWillUpdate: false,
version: '0.0.0',
script: {
name: 'Default Script',
namespace: 'default',
description: 'A default userscript.',
excludes: [],
includes: [],
matches: [],
resources: {},
'run-at': "document-start",
version: '0.0.0'
}
}
};
2 changes: 2 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import 'package:torn_pda/providers/theme_provider.dart';
import 'package:torn_pda/providers/trades_provider.dart';
import 'package:torn_pda/providers/user_controller.dart';
import 'package:torn_pda/providers/user_details_provider.dart';
import 'package:torn_pda/providers/userscripts_apis_provider.dart';
import 'package:torn_pda/providers/userscripts_provider.dart';
import 'package:torn_pda/providers/war_controller.dart';
import 'package:torn_pda/providers/webview_provider.dart';
Expand Down Expand Up @@ -158,6 +159,7 @@ Future<void> main() async {
// START ## Force splash screen to stay on until we get essential start-up data
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
await _shouldSyncDeviceTheme(widgetsBinding);
await UserscriptApisProvider.initialize();
FlutterNativeSplash.remove();
// END ## Release splash screen

Expand Down
87 changes: 61 additions & 26 deletions lib/models/userscript_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,20 @@ enum UserScriptUpdateStatus {
}

class UserScriptModel {
UserScriptModel({
this.enabled = true,
this.matches = const ["*"],
required this.name,
this.version = "0.0.0",
this.edited = false,
required this.source,
this.time = UserScriptTime.end,
this.url,
this.updateStatus = UserScriptUpdateStatus.noRemote,
required this.isExample,
this.customApiKey = "",
this.customApiKeyCandidate = false,
});
UserScriptModel(
{this.enabled = true,
this.matches = const ["*"],
required this.name,
this.version = "0.0.0",
this.edited = false,
required this.source,
this.time = UserScriptTime.end,
this.url,
this.updateStatus = UserScriptUpdateStatus.noRemote,
required this.isExample,
this.customApiKey = "",
this.customApiKeyCandidate = false,
this.grants = const []});

bool enabled;
List<String> matches;
Expand All @@ -49,6 +49,7 @@ class UserScriptModel {
bool isExample;
String customApiKey;
bool customApiKeyCandidate;
List<String> grants;

factory UserScriptModel.fromJson(Map<String, dynamic> json) {
// First check if is old model
Expand All @@ -67,17 +68,18 @@ class UserScriptModel {
final updateStatus =
UserScriptUpdateStatus.values.byName(json["updateStatus"] ?? (url is String ? "upToDate" : "noRemote"));
return UserScriptModel(
enabled: enabled,
matches: matches,
name: name,
version: version,
edited: edited,
source: source,
time: time,
url: url,
updateStatus: updateStatus,
isExample: isExample,
);
enabled: enabled,
matches: matches,
name: name,
version: version,
edited: edited,
source: source,
time: time,
url: url,
updateStatus: updateStatus,
isExample: isExample,
grants: <String>[] // Old model does not have grants
);
} else {
return UserScriptModel(
enabled: json["enabled"],
Expand All @@ -92,6 +94,7 @@ class UserScriptModel {
isExample: json["isExample"] ?? (json["exampleCode"] ?? 0) > 0,
customApiKey: json["customApiKey"] ?? "",
customApiKeyCandidate: json["customApiKeyCandidate"] ?? false,
grants: json["grants"] is List<dynamic> ? json["grants"].cast<String>() : const [],
);
}
}
Expand Down Expand Up @@ -123,6 +126,7 @@ class UserScriptModel {
isExample: isExample ?? false,
customApiKey: customApiKey ?? "",
customApiKeyCandidate: customApiKeyCandidate ?? false,
grants: metaMap["grant"] ?? <String>[],
);
}

Expand Down Expand Up @@ -179,6 +183,7 @@ class UserScriptModel {
"url": url,
"updateStatus": updateStatus.name,
"isExample": isExample,
"grants": grants,
"time": time == UserScriptTime.start ? "start" : "end",
"customApiKey": customApiKey,
"customApiKeyCandidate": customApiKeyCandidate,
Expand All @@ -192,7 +197,8 @@ class UserScriptModel {
throw Exception("No header found in userscript.");
}
Iterable<RegExpMatch> metaMatches = RegExp(r"^(?:^|\n)\s*\/\/\x20(@\S+)(.*)$", multiLine: true).allMatches(meta);
Map<String, dynamic> metaMap = {"@match": <String>[]};
Map<String, dynamic> metaMap = {"@match": <String>[], "@grant": <String>[]};
bool foundNoneGrant = false;
for (final match in metaMatches) {
if (match.groupCount < 2) {
continue;
Expand All @@ -202,10 +208,25 @@ class UserScriptModel {
}
if (match.group(1)?.toLowerCase() == "@match") {
metaMap["@match"].add(match.group(2)!.trim());
} else if (match.group(1)?.toLowerCase() == "@grant") {
if (match.group(2)?.trim() == "none") {
// see note below (after the loop) on this behavior
foundNoneGrant = true;
continue;
}
metaMap["@grant"].add(match.group(2)!.trim());
} else {
metaMap[match.group(1)!.trim().toLowerCase()] = match.group(2)!.trim();
}
}

if (foundNoneGrant) {
// per https://wiki.greasespot.net/@grant
// > If you specify none and something else, none takes precedence. This can be confusing.
// > Check for a none to remove if you're adding APIs you intend to use.
metaMap["@grant"] = <String>[];
}

return {
"name": metaMap["@name"],
"version": metaMap["@version"],
Expand All @@ -214,6 +235,7 @@ class UserScriptModel {
"injectionTime": metaMap["@run-at"] ?? "document-end",
"downloadURL": metaMap["@downloadurl"],
"updateURL": metaMap["@updateurl"],
"grant": metaMap["@grant"],
"source": source,
};
}
Expand All @@ -234,6 +256,7 @@ class UserScriptModel {
String? url,
String? customApiKey,
bool? customApiKeyCandidate,
List<String>? grants,
required UserScriptUpdateStatus updateStatus,
}) {
if (source != null) {
Expand All @@ -246,6 +269,9 @@ class UserScriptModel {
if (metaMap["matches"] != null) {
this.matches = metaMap["matches"];
}
if (metaMap["grant"] != null) {
this.grants = metaMap["grant"];
}
if (metaMap["name"] != null) {
this.name = metaMap["name"];
}
Expand Down Expand Up @@ -319,6 +345,15 @@ class UserScriptModel {
}
}

static List<String> tryGetGrants(String source) {
try {
final metaMap = UserScriptModel.parseHeader(source);
return metaMap["grant"] ?? <String>[];
} catch (e) {
return const [];
}
}

static String? tryGetUrl(String source) {
try {
final metaMap = UserScriptModel.parseHeader(source);
Expand Down
45 changes: 45 additions & 0 deletions lib/providers/userscripts_apis_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import 'dart:async' show Future;
import 'dart:convert';
import 'package:flutter/services.dart' show rootBundle;

class UserscriptApisProvider {
static Map<String, String> _apis = {};

/// Initializes the provider by loading the API scripts from the assets.
/// Returns a [Future] if someone wants to check for errors.
static Future<Map<String, String>> initialize() async {
_apis = await _getApisMap(_getApis());
return _apis;
}

static Map<String, String> get apis {
return _apis;
}

static Future<Map<String, String>> _getApisMap(
Future<List<String>> apiEntries) async {
return apiEntries.then((fileList) async {
return {
for (var file in fileList)
_mapFileNameToApiName(file): await rootBundle.loadString(file)
};
});
}

static String _mapFileNameToApiName(String fileName) {
return fileName
.replaceFirst('assets/userscripts/apis/', '')
.replaceFirst('.js', '');
}

static Future<List<String>> _getApis() async {
final manifestContent = await rootBundle.loadString('AssetManifest.json');

final Map<String, dynamic> manifestMap = json.decode(manifestContent);

return manifestMap.keys
.where((String key) => key.startsWith('assets/userscripts/apis/'))
.where((String key) => key.endsWith('.js'))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly a stupid question - why not just .where((String key) => key.startsWith("...") && key.endsWith("..."))?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh, just my habit of breaking things up like this. I like to think about it this way, though I guess technically it is less efficient (depending on the implementation) because you need to iterate twice. Happy to change it.

.toList();
}
}
Loading