diff --git a/.gitignore b/.gitignore
index 86f7387..277b196 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,4 +32,6 @@ build/
test/session_token.dart
example/lib/session_token.dart
test/clearance_token.dart
-example/lib/clearance_token.dart
\ No newline at end of file
+example/lib/clearance_token.dart
+test/user_agent.dart
+example/lib/user_agent.dart
\ No newline at end of file
diff --git a/README.md b/README.md
index 96b58ed..0e6fefe 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ This version have been updated to use Puppeteer to log in to ChatGPT and extract
- [Demo](#demo)
- [Installation](#installation)
- [Usage](#usage)
-- [SessionToken and ClearanceToken](#sessiontoken)
+- [SessionToken, ClearanceToken and UserAgent](#sessiontoken)
- [License](#license)
:warning: Be Careful!
@@ -36,6 +36,7 @@ import 'package:flutter_chatgpt_api/flutter_chatgpt_api.dart';
_api = ChatGPTApi(
sessionToken: SESSION_TOKEN,
clearanceToken: CLEARANCE_TOKEN,
+ userAgent: USER_AGENT,
);
setState(() {
@@ -84,6 +85,9 @@ Copy the value for __Secure-next-auth.session-token and save it to your environm
Copy the value for cf_clearance and save it to your environment.
`example/lib/clearance_token.dart`
+Copy the value for user_agent and save it to your environment.
+`example/lib/user_agent.dart`
+
Should look something like this:
```dart
const SESSION_TOKEN = '__Secure-next-auth.session-token from https://chat.openai.com/chat';
@@ -92,6 +96,10 @@ const SESSION_TOKEN = '__Secure-next-auth.session-token from https://chat.openai
```dart
const CLEARANCE_TOKEN = 'cf_clearance token from https://chat.openai.com/chat';
```
+
+```dart
+const USER_AGENT = 'user_agent from https://chat.openai.com/chat';
+```
## Credit
- Huge thanks to Travis Fischer for creating [Node.js ChatGPT API](https://github.com/transitive-bullshit/chatgpt-api) (unofficial) 💪
diff --git a/example/lib/main.dart b/example/lib/main.dart
index e4816bf..600f286 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -1,5 +1,7 @@
-import 'package:example/clearance_token.dart';
-import 'package:example/session_token.dart';
+import 'session_token.dart';
+import 'clearance_token.dart';
+import 'user_agent.dart';
+
import 'package:flutter/material.dart';
import 'package:flutter_chatgpt_api/flutter_chatgpt_api.dart';
@@ -46,6 +48,8 @@ class _ChatPageState extends State {
_api = ChatGPTApi(
sessionToken: SESSION_TOKEN,
clearanceToken: CLEARANCE_TOKEN,
+ userAgent: USER_AGENT,
+ debug: true,
);
isLoading = false;
}
@@ -122,6 +126,7 @@ class _ChatPageState extends State {
_textController.clear();
Future.delayed(const Duration(milliseconds: 50))
.then((_) => _scrollDown());
+ await _api.sendModeration(input);
var newMessage = await _api.sendMessage(
input,
conversationId: _conversationId,
diff --git a/example/pubspec.lock b/example/pubspec.lock
index b24b7d0..3f70f79 100644
--- a/example/pubspec.lock
+++ b/example/pubspec.lock
@@ -5,64 +5,56 @@ packages:
dependency: transitive
description:
name: async
- sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
- version: "2.10.0"
+ version: "2.9.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
- sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.1"
+ version: "2.1.0"
characters:
dependency: transitive
description:
name: characters
- sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
version: "1.2.1"
clock:
dependency: transitive
description:
name: clock
- sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
collection:
dependency: transitive
description:
name: collection
- sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
- version: "1.17.0"
+ version: "1.16.0"
crypto:
dependency: transitive
description:
name: crypto
- sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
version: "3.0.2"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
- sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
fake_async:
dependency: transitive
description:
name: fake_async
- sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
flutter:
@@ -81,8 +73,7 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
- sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
flutter_test:
@@ -94,64 +85,49 @@ packages:
dependency: transitive
description:
name: http
- sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482"
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
version: "0.13.5"
http_parser:
dependency: transitive
description:
name: http_parser
- sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
version: "4.0.2"
- js:
- dependency: transitive
- description:
- name: js
- sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7"
- url: "https://pub.dev"
- source: hosted
- version: "0.6.5"
lints:
dependency: transitive
description:
name: lints
- sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593"
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
matcher:
dependency: transitive
description:
name: matcher
- sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72"
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
- version: "0.12.13"
+ version: "0.12.12"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
- sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
- version: "0.2.0"
+ version: "0.1.5"
meta:
dependency: transitive
description:
name: meta
- sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42"
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
path:
dependency: transitive
description:
name: path
- sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
version: "1.8.2"
sky_engine:
@@ -163,74 +139,65 @@ packages:
dependency: transitive
description:
name: source_span
- sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
- version: "1.9.1"
+ version: "1.9.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
- sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
- version: "1.11.0"
+ version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
- sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.1"
+ version: "2.1.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
- sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
- version: "1.2.0"
+ version: "1.1.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
- sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
- sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
- version: "0.4.16"
+ version: "0.4.12"
typed_data:
dependency: transitive
description:
name: typed_data
- sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5"
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
uuid:
dependency: transitive
description:
name: uuid
- sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313"
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
version: "3.0.7"
vector_math:
dependency: transitive
description:
name: vector_math
- sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
- url: "https://pub.dev"
+ url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.4"
+ version: "2.1.2"
sdks:
- dart: ">=2.19.0-406.0.dev <4.0.0"
+ dart: ">=2.18.2 <3.0.0"
flutter: ">=1.17.0"
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index 9f8404e..1590857 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
- sdk: '>=2.19.0-406.0.dev <3.0.0'
+ sdk: '>=2.18.2'
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
diff --git a/lib/flutter_chatgpt_api.dart b/lib/flutter_chatgpt_api.dart
index b47fbda..74eff5d 100644
--- a/lib/flutter_chatgpt_api.dart
+++ b/lib/flutter_chatgpt_api.dart
@@ -16,6 +16,7 @@ class ChatGPTApi {
String? apiBaseUrl;
String backendApiBaseUrl;
String userAgent;
+ bool debug;
final ExpiryMap _accessTokenCache =
ExpiryMap();
@@ -23,25 +24,12 @@ class ChatGPTApi {
ChatGPTApi({
required this.sessionToken,
required this.clearanceToken,
+ required this.userAgent,
this.apiBaseUrl = 'https://chat.openai.com/api',
this.backendApiBaseUrl = 'https://chat.openai.com/backend-api',
- this.userAgent = defaultUserAgent,
+ this.debug = false,
});
- Map defaultHeaders = {
- 'user-agent': defaultUserAgent,
- 'x-openai-assistant-app-id': '',
- 'accept-language': 'en-US,en;q=0.9',
- HttpHeaders.accessControlAllowOriginHeader: 'https://chat.openai.com',
- HttpHeaders.refererHeader: 'https://chat.openai.com/chat',
- 'sec-ch-ua':
- '"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"',
- 'sec-ch-ua-platform': '"Windows"',
- 'sec-fetch-dest': 'empty',
- 'sec-fetch-mode': 'cors',
- 'sec-fetch-site': 'same-origin',
- };
-
Future sendMessage(
String message, {
String? conversationId,
@@ -66,31 +54,47 @@ class ChatGPTApi {
final url = '$backendApiBaseUrl/conversation';
+ var headersConv = {
+ 'cookie': 'cf_clearance=$clearanceToken',
+ 'user-agent': userAgent,
+ 'x-openai-assistant-app-id': '',
+ 'accept-language': 'en-US,en;q=0.9',
+ HttpHeaders.accessControlAllowOriginHeader: 'https://chat.openai.com',
+ HttpHeaders.refererHeader: 'https://chat.openai.com/chat',
+ 'sec-ch-ua':
+ '"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"',
+ 'sec-ch-ua-platform': '"Mac"',
+ 'sec-fetch-dest': 'empty',
+ 'sec-fetch-mode': 'cors',
+ 'sec-fetch-site': 'same-origin',
+ 'authorization': 'Bearer $accessToken',
+ 'content-Type': 'application/json',
+ 'accept': 'text/event-stream',
+ };
+
+ if (debug) {
+ print('== REQUEST ==');
+ print('POST $url');
+ print('Headers : $headersConv');
+ print('Body : ${body.toJson()}');
+ }
+
final response = await http.post(
Uri.parse(url),
- headers: {
- 'user-agent': defaultUserAgent,
- 'x-openai-assistant-app-id': '',
- 'accept-language': 'en-US,en;q=0.9',
- HttpHeaders.accessControlAllowOriginHeader: 'https://chat.openai.com',
- HttpHeaders.refererHeader: 'https://chat.openai.com/chat',
- 'sec-ch-ua':
- '"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"',
- 'sec-ch-ua-platform': '"Windows"',
- 'sec-fetch-dest': 'empty',
- 'sec-fetch-mode': 'cors',
- 'sec-fetch-site': 'same-origin',
- 'Authorization': 'Bearer $accessToken',
- 'Content-Type': 'application/json',
- 'Accept': 'text/event-stream',
- 'Cookie': 'cf_clearance=$clearanceToken'
- },
+ headers: headersConv,
body: body.toJson(),
);
+ if (debug) {
+ print('== RESPONSE ==');
+ print('HTTP code : ${response.statusCode}');
+ print('Headers : ${response.headers}');
+ print('Body : ${response.body}');
+ }
+
if (response.statusCode != 200) {
if (response.statusCode == 429) {
- throw Exception('Rate limited');
+ throw Exception(response.body);
} else {
throw Exception('Failed to send message');
}
@@ -118,28 +122,111 @@ class ChatGPTApi {
}
}
+ Future isAuthenticated() async {
+ try {
+ await _refreshAccessToken();
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+
+ Future sendModeration(String input) async {
+ final accessToken = await _refreshAccessToken();
+
+ final body = ModerationsBody(
+ input: input,
+ model: 'text-moderation-playground',
+ );
+
+ final url = '$backendApiBaseUrl/moderations';
+
+ var headersConv = {
+ 'user-agent': userAgent,
+ 'x-openai-assistant-app-id': '',
+ 'accept-language': 'en-US,en;q=0.9',
+ HttpHeaders.accessControlAllowOriginHeader: 'https://chat.openai.com',
+ HttpHeaders.refererHeader: 'https://chat.openai.com/chat',
+ 'sec-ch-ua':
+ '"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"',
+ 'sec-ch-ua-platform': '"Mac"',
+ 'sec-fetch-dest': 'empty',
+ 'sec-fetch-mode': 'cors',
+ 'sec-fetch-site': 'same-origin',
+ 'authorization': 'Bearer $accessToken',
+ 'content-Type': 'application/json',
+ 'accept': '*/*',
+ 'cookie': 'cf_clearance=$clearanceToken',
+ };
+
+ if (debug) {
+ print('== REQUEST ==');
+ print('POST $url');
+ print('Headers : $headersConv');
+ print('Body : ${body.toJson()}');
+ }
+
+ final response = await http.post(
+ Uri.parse(url),
+ headers: headersConv,
+ body: body.toJson(),
+ );
+
+ if (debug) {
+ print('== RESPONSE ==');
+ print('HTTP code : ${response.statusCode}');
+ print('Headers : ${response.headers}');
+ print('Body : ${response.body}');
+ }
+
+ if (response.statusCode != 200) {
+ if (response.statusCode == 429) {
+ throw Exception('Rate limited');
+ } else {
+ throw Exception('Failed to send message');
+ }
+ } else if (_errorMessages.contains(response.body)) {
+ throw Exception('OpenAI returned an error');
+ }
+
+ return response.body;
+ }
+
Future _refreshAccessToken() async {
final cachedAccessToken = _accessTokenCache['KEY_ACCESS_TOKEN'];
if (cachedAccessToken != null) {
return cachedAccessToken;
}
+ var headersSession = {
+ 'cookie':
+ 'cf_clearance=$clearanceToken;__Secure-next-auth.session-token=$sessionToken',
+ 'user-agent': userAgent,
+ 'accept': '*/*',
+ };
+
+ if (debug) {
+ print('POST $apiBaseUrl/auth/session');
+ print('Headers : $headersSession');
+ }
try {
- final res = await http.get(
+ final response = await http.get(
Uri.parse('$apiBaseUrl/auth/session'),
- headers: {
- 'cookie':
- 'cf_clearance=$clearanceToken;__Secure-next-auth.session-token=$sessionToken',
- 'accept': '*/*',
- ...defaultHeaders,
- },
+ headers: headersSession,
);
- if (res.statusCode != 200) {
+ if (debug) {
+ print('== RESPONSE ==');
+ print('HTTP code : ${response.statusCode}');
+ print('Headers : ${response.headers}');
+ print('Body : ${response.body}');
+ }
+
+ if (response.statusCode != 200) {
throw Exception('Failed to refresh access token');
}
- final accessToken = jsonDecode(res.body)['accessToken'];
+ final accessToken = jsonDecode(response.body)['accessToken'];
if (accessToken == null) {
throw Exception(
@@ -154,9 +241,6 @@ class ChatGPTApi {
}
}
-const defaultUserAgent =
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36';
-
const _errorMessages = [
"{\"detail\":\"Hmm...something seems to have gone wrong. Maybe try me again in a little bit.\"}",
];
diff --git a/lib/src/models/moderation_body.model.dart b/lib/src/models/moderation_body.model.dart
index a83896f..dd51055 100644
--- a/lib/src/models/moderation_body.model.dart
+++ b/lib/src/models/moderation_body.model.dart
@@ -1,13 +1,20 @@
-enum AvailableModerationModels {
- textModerationPlayground,
-}
+import 'dart:convert';
class ModerationsBody {
final String input;
- final AvailableModerationModels model;
+ final String model;
ModerationsBody({
required this.input,
required this.model,
});
+
+ Map toMap() {
+ return {
+ 'input': input,
+ 'model': model,
+ };
+ }
+
+ String toJson() => json.encode(toMap());
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 8b76b5d..fd37a46 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -5,7 +5,7 @@ homepage: https://github.com/coskuncay/flutter_chatgpt_api
repository: https://github.com/coskuncay/flutter_chatgpt_api
environment:
- sdk: '>=2.18.2 <3.0.0'
+ sdk: '>=2.18.2'
flutter: ">=1.17.0"
dependencies:
diff --git a/test/flutter_chatgpt_test.dart b/test/flutter_chatgpt_test.dart
index 9b13c20..0e89903 100644
--- a/test/flutter_chatgpt_test.dart
+++ b/test/flutter_chatgpt_test.dart
@@ -3,11 +3,15 @@ import 'package:flutter_test/flutter_test.dart';
import 'clearance_token.dart';
import 'session_token.dart';
+import 'user_agent.dart';
void main() {
test('do basic prompt', () async {
final api = ChatGPTApi(
- sessionToken: SESSION_TOKEN, clearanceToken: CLEARANCE_TOKEN);
+ sessionToken: SESSION_TOKEN,
+ clearanceToken: CLEARANCE_TOKEN,
+ userAgent: USER_AGENT,
+ );
const prompt =
'Write a python version of bubble sort. Do not include example usage.';