huggingface-chat
Version:
A lightweight and powerful Node.js API client for Hugging Face Chat. Interact with open-source LLMs like Llama 3, Mixtral, and Gemma for conversational AI, text generation, and more. Supports ESM and CJS modules.
352 lines (351 loc) • 14.4 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
Object.defineProperty(exports, "__esModule", { value: true });
const promises_1 = require("fs/promises");
/**
* Login class for managing authentication and user sessions.
*/
class Login {
/**
* Constructs a new instance of the Login class.
* @param {string} email - huggingface email address.
* @param {string} password - huggingface password.
*/
constructor(email, password) {
this.email = "";
this.password = "";
this.cookies = {};
this.email = email;
this.password = password;
this.headers = {
Referer: "https://huggingface.co/",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 Edg/112.0.1722.64",
};
}
/**
* Parses cookies into a formatted string.
* @returns {string} A formatted string containing parsed cookies.
*/
parseCookies() {
let res = "";
if (!this.cookies)
return res;
if ("token" in this.cookies)
res += `token=${this.cookies["token"]};`;
if ("hf-chat" in this.cookies)
res += `hf-chat=${this.cookies["hf-chat"]}; `;
return res;
}
/**
* Sends an HTTP GET request.
* @param {string} url - The URL to send the GET request to.
* @param {Record<string, any>} _params - Optional query parameters for the request.
* @returns {Promise<{ status: number, headers: Headers, data: string }>} A Promise that resolves to the HTTP response.
*/
get(url, _params) {
return __awaiter(this, void 0, void 0, function* () {
const headers = Object.assign(Object.assign({}, this.headers), { Cookie: this.parseCookies() });
const params = _params ? new URLSearchParams(_params).toString() : "";
const fullUrl = params ? `${url}?${params}` : url;
const response = yield fetch(fullUrl, {
method: "GET",
headers: headers,
credentials: "include",
redirect: "manual",
});
const data = yield response.text();
this.refreshCookies(response);
return {
status: response.status,
headers: response.headers,
data: data,
};
});
}
/**
* Sends an HTTP POST request.
* @param {string} url - The URL to send the POST request to.
* @param {Record<string, any>} data - Data to include in the request body.
* @param {Record<string, any>} _headers - Optional additional headers for the request.
* @returns {Promise<{ status: number, headers: Headers, data: string }>} A Promise that resolves to the HTTP response.
*/
post(url_1) {
return __awaiter(this, arguments, void 0, function* (url, data = {}, _headers = {}) {
const headers = Object.assign(Object.assign({}, _headers), { Cookie: this.parseCookies() });
const body = new URLSearchParams(data);
const response = yield fetch(url, {
method: "POST",
headers: headers,
body: body,
credentials: "include",
redirect: "manual",
});
const responseData = yield response.text();
this.refreshCookies(response);
return {
status: response.status,
headers: response.headers,
data: responseData,
};
});
}
/**
* Refreshes cookies based on the response headers.
* @param {Headers} headers - The HTTP response headers.
*/
refreshCookies(response) {
const setCookieHeaders = response.headers.getSetCookie
? response.headers.getSetCookie()
: [];
let jsonCookie = {};
for (const cookie of setCookieHeaders) {
for (const value of cookie.trim().split(";")) {
const temp = value.trim().split("=");
const key = temp[0];
if (!jsonCookie[key]) {
jsonCookie[key] = temp[1] || "";
}
}
}
for (const cookie in jsonCookie) {
if (cookie === "token")
this.cookies["token"] = jsonCookie["token"];
if (cookie === "hf-chat")
this.cookies["hf-chat"] = jsonCookie["hf-chat"];
}
}
/**
* Attempts to sign in with the provided email and password.
* @throws {Error} If the sign-in fails.
*/
signinWithEmail() {
return __awaiter(this, void 0, void 0, function* () {
const url = "https://huggingface.co/login";
const data = {
username: this.email,
password: this.password,
};
const res = yield this.post(url, data, this.headers);
if (res.status == 400) {
throw new Error("wrong username or password");
}
});
}
/**
* Retrieves the authentication URL for a chat.
* @returns {Promise<string>} A Promise that resolves to the authentication URL.
* @throws {Error} If the URL retrieval fails.
*/
getAuthUrl() {
return __awaiter(this, void 0, void 0, function* () {
const url = "https://huggingface.co/chat/login";
const headers = {
Referer: "https://huggingface.co/chat/login",
"User-Agent": this.headers["User-Agent"],
origin: "https://huggingface.co",
"Content-Type": "application/x-www-form-urlencoded",
};
const res = yield this.post(url, {}, headers);
if (res.status == 200) {
try {
const data = JSON.parse(res.data);
if (data.location) {
return data.location;
}
else {
throw new Error("No authorize url found, please check your email or password.");
}
}
catch (_a) {
throw new Error("Invalid response");
}
}
else if (res.status == 303) {
const location = res.headers.get("location");
if (location) {
return location;
}
else {
throw new Error("No authorize url found, please check your email or password.");
}
}
else {
throw new Error("Something went wrong!");
}
});
}
/**
* Extracts CSRF token from a string.
* @param {string} input - The input string containing CSRF information.
* @returns {string | null} The extracted CSRF token or null if not found.
*/
getCrpf(input) {
const startIndex = input.indexOf("csrf");
if (startIndex === -1) {
return null; // Start string not found in input
}
const endIndex = input.indexOf("}", startIndex + "csrf".length);
if (endIndex === -1) {
return null; // End string not found after the start string
}
const str = input
.substring(startIndex + "csrf".length, endIndex)
.replace('/"/g', "");
return str.substring(1);
}
/**
* Grants authorization by following redirects.
* @param {string} url - The URL to grant authorization for.
* @returns {Promise<number>} A Promise that resolves to a status code.
* @throws {Error} If the authorization process fails.
*/
grantAuth(url) {
return __awaiter(this, void 0, void 0, function* () {
let res = yield this.get(url);
if (res.status >= 300 && res.status < 400) {
const location = res.headers.get("location");
if (location) {
res = yield this.get(location);
const setCookies = res.headers.getSetCookie
? res.headers.getSetCookie()
: [];
if (setCookies.some((cookie) => cookie.includes("hf-chat"))) {
return 1;
}
}
}
if (res.status != 200) {
throw new Error("grant auth fatal!");
}
const csrf = this.getCrpf(res.data);
if (!csrf) {
throw new Error("No csrf found!");
}
const data = {
csrf: csrf,
};
res = yield this.get(url, data);
if (res.status != 303) {
throw new Error(`get hf-chat cookies fatal! - ${res.status}`);
}
else {
const location = res.headers.get("location");
if (location) {
res = yield this.get(location);
if (res.status != 302) {
throw new Error(`get hf-chat cookie fatal! - ${res.status}`);
}
else {
return 1;
}
}
else {
throw new Error("No location found");
}
}
});
}
/**
* Initiates the login process.
* @param {string} cache_path - Optional path for caching login data.
* @returns {Promise<string>} A Promise that resolves to the parsed cookies.
* @throws {Error} If the login process fails.
*/
login(cache_path_1) {
return __awaiter(this, arguments, void 0, function* (cache_path, force = false) {
let cookies = "";
const defaultCookiePath = cache_path || "./login_cache/";
if (!force) {
cookies = yield this.loadLoginCache(defaultCookiePath);
}
if (cookies != "") {
console.error(`Using cache from path: '${defaultCookiePath}${this.email}.txt'`);
return cookies;
}
else {
yield this.signinWithEmail();
const location = yield this.getAuthUrl();
if (yield this.grantAuth(location)) {
this.cacheLogin(defaultCookiePath);
return this.parseCookies();
}
else {
throw new Error(`Grant auth fatal, please check your email or password\ncookies gained: \n${this.cookies}`);
}
}
});
}
/**
* Caches login data to a file.
* @param {string} path - The path where login data will be cached.
*/
cacheLogin(path) {
return __awaiter(this, void 0, void 0, function* () {
try {
yield (0, promises_1.access)(path);
yield (0, promises_1.writeFile)(`${path}${this.email}.txt`, this.parseCookies());
console.error(`Cache already exists at path '${path}${this.email}.txt', updating cache with ${this.parseCookies()}`);
}
catch (error) {
try {
yield (0, promises_1.mkdir)(path);
yield (0, promises_1.writeFile)(`${path}${this.email}.txt`, this.parseCookies());
}
catch (error) {
console.error(`Error creating cache:`, error);
}
}
});
}
/**
* Loads cached login data from a file.
* @param {string} path - The path to the cached login data file.
* @returns {Promise<string>} A Promise that resolves to the cached login data.
*/
loadLoginCache(path) {
return __awaiter(this, void 0, void 0, function* () {
var _a, e_1, _b, _c;
try {
const file = yield (0, promises_1.open)(`${path}${this.email}.txt`, "r");
const lines = [];
try {
for (var _d = true, _e = __asyncValues(file.readLines()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
_c = _f.value;
_d = false;
const line = _c;
lines.push(line.toString());
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
}
finally { if (e_1) throw e_1.error; }
}
return lines.join("");
}
catch (error) {
console.error(`Error loading cache:`, error);
console.error("\nCreating new session.");
return "";
}
});
}
}
exports.default = Login;