Skip to content

Instantly share code, notes, and snippets.

@Explosion-Scratch
Created May 2, 2023 13:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Explosion-Scratch/1355457b37b2e43fdbc15794cc104268 to your computer and use it in GitHub Desktop.
Save Explosion-Scratch/1355457b37b2e43fdbc15794cc104268 to your computer and use it in GitHub Desktop.
Bard Client for use in Userscripts/Extensions
class BardBot {
/**
* The conversation context.
* @type {Object}
*/
conversationContext = undefined
/**
* The logging function.
* @type {Function}
*/
logFunc = undefined
/**
* Creates a new instance of the BardBot class.
* @param {Object} options - The options for the BardBot instance.
* @param {Function} [options.log] - The logging function.
* @param {Object} [options.context] - The conversation context.
* @param {Array} [options.context.ids] - The conversation context IDs.
* @param {Object} [options.context.session] - The conversation context session.
* @param {XMLHttpRequest} options.xmlhttprequest - The XMLHttpRequest instance.
* @returns {BardBot} - The new BardBot instance.
*/
constructor({ log, context: { ids, session } = {}, xmlhttprequest }) {
this.xmlhttprequest = xmlhttprequest;
if (!xmlhttprequest) {
this.xmlhttprequest = window.XMLHttpRequest;
}
if (ids && session) { this.setContext({ ids, session }) }
if (log) { this.logFunc = log }
return this;
}
/**
* Sets the conversation context.
* @param {Object} options - The conversation context options.
* @param {Array} options.ids - The conversation context IDs.
* @param {Object} options.session - The conversation context session.
*/
setContext({ ids, session }) {
this.conversationContext = {
ids,
requestParams: session,
}
}
/**
* Gets the conversation context.
* @returns {Object} - The conversation context.
* @property {Array} ids - The conversation context IDs.
* @property {Object} session - The conversation context session.
*/
getContext() {
return {
ids: this.conversationContext.ids,
session: this.conversationContext.requestParams,
}
}
/**
* Queries the Bard chatbot.
* @param {Object} options - The query options.
* @param {Function} [options.onEvent] - The event function.
* @param {Boolean} [options.log] - Whether to log the query.
* @param {Object} [options.context] - The conversation context.
* @param {Object} options.prompt - The prompt for the query.
* @param {AbortSignal} [options.signal] - The abort signal for the query.
* @returns {Object} - The response from the Bard chatbot.
* @property {String} text - The response text.
* @property {Array} ids - The conversation context IDs.
* @property {Array} responses - The response objects.
* @property {Array} searches - The search objects.
* @property {Array} payload - The payload objects.
*/
async query(params) {
params = {
onEvent: () => { },
...params,
}
if (params.signal) {
this.signal = params.signal
}
if (params.log) {
this.logFunc = params.log;
}
if (params.context) {
this.conversationContext = params.context;
}
if (!this.conversationContext) {
this.conversationContext = {
requestParams: await this.#getParams(),
contextIds: ['', '', ''],
}
}
const { requestParams, contextIds } = this.conversationContext
if (!(this.conversationContext.requestParams && this.conversationContext.contextIds)) {
throw new Error('Context invalid');
}
/*const resp = await fetch(
'https://bard.google.com/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate?bl=' +
requestParams.blValue +
'&_reqid=' +
generateReqId() +
'&rt=c',
{
method: 'POST',
signal: params.signal,
body: new URLSearchParams({
at: requestParams.atValue,
'f.req': JSON.stringify([null, `[[${JSON.stringify(params.prompt)}],null,${JSON.stringify(contextIds)}]`]),
}),
},
).then((res) => res.text())*/
const resp = await new Promise((resolve, reject) => {
this.xmlhttprequest({
method: 'POST',
url: 'https://bard.google.com/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate?bl=' +
requestParams.blValue +
'&_reqid=' +
this.#generateReqId() +
'&rt=c',
data: new URLSearchParams({
at: requestParams.atValue,
'f.req': JSON.stringify([null, `[[${JSON.stringify(params.prompt)}],null,${JSON.stringify(contextIds)}]`]),
}).toString(),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
responseType: 'text',
onload: function (response) {
resolve(response.responseText);
},
onerror: function (response) {
reject(new Error('Request failed'));
},
});
});
const { text, ids, ...res } = this.#parseResponse(resp)
this.conversationContext.contextIds = ids
params.onEvent({
type: 'UPDATE_ANSWER',
data: { text },
})
params.onEvent({ type: 'DONE' })
return { text, ids, ...res }
}
/**
* Sets the conversation context IDs.
* @param {Array} ids - The conversation context IDs.
*/
setIds(ids) {
this.conversationContext.ids = ids;
}
/**
* Resets the conversation context.
*/
resetConversation() {
this.conversationContext = undefined
}
/**
* Extracts a value from HTML.
* @private
* @param {String} thing - The value to extract.
* @param {String} html - The HTML to extract the value from.
* @returns {String} - The extracted value.
*/
#extractFromHTML(thing, html) {
const regex = new RegExp(`"${thing}":"([^"]+)"`)
const match = regex.exec(html)
return match?.[1]
}
/**
* Gets the parameters for the Bard chatbot.
* @private
* @returns {Object} - The parameters for the Bard chatbot.
* @property {String} atValue - The at value for the Bard chatbot (session token or something).
* @property {String} blValue - The bl value for the Bard chatbot (bot version).
*/
async #getParams() {
const html = await this.#fetchHtml('https://bard.google.com/faq')
const atValue = this.#extractFromHTML('SNlM0e', html)
const blValue = this.#extractFromHTML('cfb2h', html)
return { atValue, blValue }
}
/**
* Logs a message.
* @private
* @param {...*} args - The arguments to log.
*/
#log(...args) {
if (this.logFunc) {
this.logFunc(...a);
}
}
/**
* Parses the response from the Bard chatbot.
* @private
* @param {String} resp - The response from the Bard chatbot.
* @returns {Object} - The parsed response from the Bard chatbot.
* @property {String} text - The response text.
* @property {Array} ids - The conversation context IDs.
* @property {Array} responses - The response objects.
* @property {Array} searches - The search objects.
* @property {Array} payload - The payload objects.
*/
#parseResponse(resp) {
const data = JSON.parse(resp.split('\n')[3])
const payload = JSON.parse(data[0][2])
if (!payload) {
throw new Error('Failed to access Bard')
}
this.#log('PAYLOAD', payload)
const text = payload[0][0]
return {
text,
ids: [...payload[1], payload[4][0][0]],
responses: [{ ids: [...payload[1], payload[4][0][0]], text: payload[0][0] }, ...payload[4].map(i => ({ ids: [...payload[1], i[0]], text: i[1][0] }))],
searches: payload[2].map(i => i[0]),
payload,
}
}
/**
* Generates a request ID.
* @private
* @returns {Number} - The generated request ID.
*/
#generateReqId() {
return Math.floor(Math.random() * 900000) + 100000
}
/**
* Fetches the HTML for a given URL.
* @private
* @param {String} url - The URL to fetch the HTML from.
* @returns {Promise<String>} - A promise that resolves to the HTML for the given URL.
*/
async #fetchHtml(url) {
return new Promise((resolve, reject) => {
this.xmlhttprequest({
method: "GET",
url: url,
onload: (response) => {
if (response.status === 200) {
resolve(response.responseText);
} else {
reject(new Error(`Failed to fetch HTML from ${url}. Status code: ${response.status}`));
}
},
onerror: (error) => {
reject(new Error(`Failed to fetch HTML from ${url}. Error: ${error}`));
}
});
});
}
}
@bonelifer
Copy link

do you know of a user script that will put the copy button at the bottom of bard? too many times I have accidently hit the second entry in the drop down.

@Explosion-Scratch
Copy link
Author

do you know of a user script that will put the copy button at the bottom of bard? too many times I have accidently hit the second entry in the drop down.

@bonelifer Just made one for you =)

https://gist.github.com/Explosion-Scratch/32568b75ecee87a66234550f5f8cedbd

@bonelifer
Copy link

could you also make one to add the copy action at the bottom of chat.openai.com. I have one for the code block installed, but it would be nice to be able to copy the post without scrolling up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment