Ações em massa úteis com a API do Octo Browser

Ações em massa úteis com a API do Octo Browser
Alex Phillips
Alex Phillips

Customer Service Specialist

API (Application Programming Interface) é uma interface que permite que vários aplicativos interajam entre si. No caso do Octo Browser, a API permite que você envie solicitações HTTP para os servidores da Octo e automatize o gerenciamento de perfis de navegador. 

Anteriormente, explicamos em detalhes o que é uma API, cobrimos maneiras de executar scripts e explicamos a estrutura das requisições. Agora, vamos apresentar vários exemplos úteis que são os mais procurados entre nossos usuários. Depois de ler este artigo, você aprenderá como recuperar e salvar nomes de perfis, adicionar extensões em massa, páginas iniciais e favoritos aos perfis criados, e exportar dados de perfil para uma planilha. Abaixo você encontrará instruções detalhadas e código pronto para uso.

Índice

Preparação

  1. Baixe e instale o VS Code.

  2. Baixe e instale o Node.js.

  3. Crie uma pasta em um local conveniente e nomeie-a, por exemplo, octo_api.

  4. Abra esta pasta no VS Code.

  5. Crie um arquivo com extensão .js. É melhor nomeá-lo de acordo com a ação que o código irá executar para evitar confusão: por exemplo, get_titles.js.

  6. Abra o terminal e execute o comando npm install axios para instalar uma dependência para o Node.js.

  7. Se o VS Code mostrar um erro, abra o Windows PowerShell como administrador, insira o comando Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned e confirme. Em seguida, repita a etapa anterior.

  8. Inicie o Octo Browser.

Onde encontrar seu token de API

Para interagir com o Octo Browser via API, você precisará de um token de API. Está disponível para usuários com uma assinatura Base e superior. O token de API é exibido no Octo Browser nas configurações da conta principal, na guia "Adicional" (outros membros da equipe não podem ver o token de API).

API token is displayed in Octo Browser

Limites de API (limites de taxa)

Lembre-se de que a API do Octo Browser tem limites de requisição. Os trechos fornecidos usam a função check_limits, que verifica os cabeçalhos de limites de taxa da API e pausa automaticamente se o número de requisições restantes for baixo. Isso ajuda a evitar erros 429 Too Many Requests.

Ao fazer requisições para a API Pública, 1 RPM e 1 RPH são consumidos por requisição. Quando usa a API Local, RPM/RPH não são consumidos, exceto para POST Start Profile (1 RPM e 1 RPH) e POST One-time profile (4 RPM e 4 RPH).

O número exato de perfis que você pode iniciar, criar e excluir sob uma assinatura específica depende totalmente de seus scripts e sua lógica. Você pode calcular de forma independente o número de requisições necessárias para seu fluxo de trabalho com base em quais requisições afetam os limites de RPH e RPM.

Algumas requisições de API podem exigir um processamento mais complexo, o que significa que sua execução pode custar mais do que uma única requisição. Isso é necessário para balancear a carga do servidor e garantir desempenho otimizado para todos os usuários.

RPM significa requisições por minuto.
RPH significa requisições por hora.
Os valores de limite de requisição dependem da sua assinatura.

Com a configuração completa, vamos passar para trechos de código úteis.

Recuperando nomes de perfil e salvando-os em um arquivo .txt

Este trecho é útil quando você precisa coletar rapidamente uma lista de todos os perfis criados em um único lugar. Ele ajuda a simplificar o rastreamento de perfis, verificar sua existência real e realizar outras ações com eles.

  1. Crie um arquivo get_titles.js na pasta octo_api e cole o código do trecho nele.

  2. Substitua OCTO_API_TOKEN no campo octo_token pelo seu token de API do Octo Browser.

OCTO_API_TOKEN
  1. Salve o arquivo.

  2. Insira o comando node get_titles.js no terminal e execute o script.

saving profile names to a .txt file

Os nomes dos perfis serão salvos linha por linha no arquivo profiles_titles.txt na mesma pasta que o script.

Trecho para salvar nomes de perfis em um arquivo .txt

const fs = require('fs').promises;
const axios = require('axios');
//This is the configuration. Specify your Octo API Token here
const config = {
    octo_token: "OCTO_API_TOKEN", // Specify your API token here
    octo_api_base_url: "https://app.octobrowser.net/api/v2/automation/",
}
const OCTO_REMOTE_API = axios.create({
    baseURL: config.octo_api_base_url, 
    timeout: 10000,
    headers: {
        'X-Octo-Api-Token': config.octo_token,
        'Content-Type': "application/json"
    }
});
async function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms * 1000));
}
async function check_limits(response) {
    function parse_int_safe(value) {
        const parsed = parseInt(value, 10);
        return isNaN(parsed) ? 0 : parsed;
    }
    const ratelimit_header = response.headers.ratelimit;
    if (!ratelimit_header) {
        console.warn('No ratelimit header found!');
        return;
    }
    const limit_entries = ratelimit_header.split(',').map(entry => entry.trim());
    for (const entry of limit_entries) {
        const name_match = entry.match(/^([^;]+)/);
        const r_match = entry.match(/;r=(\d+)/);
        const t_match = entry.match(/;t=(\d+)/);
        if (!r_match || !t_match) {
            console.warn(`Invalid ratelimit format: ${entry}`);
            continue;
        }
        const limit_name = name_match ? name_match[1] : 'unknown_limit';
        const remaining_quantity = parse_int_safe(r_match[1]);
        const window_seconds = parse_int_safe(t_match[1]);
        if (remaining_quantity < 5) {
            const wait_time = window_seconds + 1;
            console.log(`Waiting ${wait_time} seconds due to ${limit_name} limit`);
            await sleep(wait_time);
        }
    }
}
async function get_total_pages() {
    const response = await OCTO_REMOTE_API.get('/profiles?page=0&page_len=10');
    const total_profiles = response.data.total_count;
    const total_pages = Math.ceil(response.data.total_count / 100);
    console.log(`Total Profiles: ${total_profiles}\nTotal Pages: ${total_pages}`);
    await check_limits(response);
    return total_pages;
}
async function get_all_profiles_titles(total_pages) {
    let profiles = [];
    for (let i = 0; i < total_pages; i++) {
        let response = await OCTO_REMOTE_API.get(`/profiles?page=${i}&page_len=100&fields=title`);
        await check_limits(response);
        profiles.push(...response.data.data);
    }
    return profiles;
}
async function write_file(profiles) {
    const titles = profiles.map(item => item.title).filter(Boolean);
    try {
        await fs.writeFile('./profiles_titles.txt', titles.join('\n'), 'utf-8');
    } catch (error) {
        console.error(`ERROR: While trying writing the file some error occured:\n${error}`);
    }
}
(async () => {
    const total_pages = await get_total_pages();
    const profiles = await get_all_profiles_titles(total_pages);
    await write_file(profiles);
    console.log('Finished. Check profiles_titles.txt file...');
})()
const fs = require('fs').promises;
const axios = require('axios');
//This is the configuration. Specify your Octo API Token here
const config = {
    octo_token: "OCTO_API_TOKEN", // Specify your API token here
    octo_api_base_url: "https://app.octobrowser.net/api/v2/automation/",
}
const OCTO_REMOTE_API = axios.create({
    baseURL: config.octo_api_base_url, 
    timeout: 10000,
    headers: {
        'X-Octo-Api-Token': config.octo_token,
        'Content-Type': "application/json"
    }
});
async function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms * 1000));
}
async function check_limits(response) {
    function parse_int_safe(value) {
        const parsed = parseInt(value, 10);
        return isNaN(parsed) ? 0 : parsed;
    }
    const ratelimit_header = response.headers.ratelimit;
    if (!ratelimit_header) {
        console.warn('No ratelimit header found!');
        return;
    }
    const limit_entries = ratelimit_header.split(',').map(entry => entry.trim());
    for (const entry of limit_entries) {
        const name_match = entry.match(/^([^;]+)/);
        const r_match = entry.match(/;r=(\d+)/);
        const t_match = entry.match(/;t=(\d+)/);
        if (!r_match || !t_match) {
            console.warn(`Invalid ratelimit format: ${entry}`);
            continue;
        }
        const limit_name = name_match ? name_match[1] : 'unknown_limit';
        const remaining_quantity = parse_int_safe(r_match[1]);
        const window_seconds = parse_int_safe(t_match[1]);
        if (remaining_quantity < 5) {
            const wait_time = window_seconds + 1;
            console.log(`Waiting ${wait_time} seconds due to ${limit_name} limit`);
            await sleep(wait_time);
        }
    }
}
async function get_total_pages() {
    const response = await OCTO_REMOTE_API.get('/profiles?page=0&page_len=10');
    const total_profiles = response.data.total_count;
    const total_pages = Math.ceil(response.data.total_count / 100);
    console.log(`Total Profiles: ${total_profiles}\nTotal Pages: ${total_pages}`);
    await check_limits(response);
    return total_pages;
}
async function get_all_profiles_titles(total_pages) {
    let profiles = [];
    for (let i = 0; i < total_pages; i++) {
        let response = await OCTO_REMOTE_API.get(`/profiles?page=${i}&page_len=100&fields=title`);
        await check_limits(response);
        profiles.push(...response.data.data);
    }
    return profiles;
}
async function write_file(profiles) {
    const titles = profiles.map(item => item.title).filter(Boolean);
    try {
        await fs.writeFile('./profiles_titles.txt', titles.join('\n'), 'utf-8');
    } catch (error) {
        console.error(`ERROR: While trying writing the file some error occured:\n${error}`);
    }
}
(async () => {
    const total_pages = await get_total_pages();
    const profiles = await get_all_profiles_titles(total_pages);
    await write_file(profiles);
    console.log('Finished. Check profiles_titles.txt file...');
})()

Adicionar extensões, páginas iniciais e favoritos aos perfis

Este script de automação permite que você padronize rapidamente as configurações de perfil e evite adicionar manualmente os mesmos parâmetros.

Para adicionar extensões, você precisará dos UUIDs delas:

  1. Abra um perfil com a extensão instalada.

  2. Na barra de endereços do perfil, insira chrome://extensions/ e pressione Enter.

  3. Na extensão desejada, clique no botão “Detalhes”.

“Details” button
  1. No final da página, copie o UUID da extensão junto com sua versão.

copy the extension UUID

Você pode encontrar métodos para recuperar UUIDs via API na documentação.

Em seguida, você precisa editar o código:

  1. Crie um arquivo nomeado adding_info.js na pasta octo_api.

  2. Cole o trecho nele.

  3. Insira seu token de API no campo octo_token.

  4. Adicione os UUIDs das extensões necessárias ao campo extensions.

  5. Adicione o nome (título do favorito) e o URL (endereço do site) ao campo bookmarks.

  6. Adicione os URLs dos sites que você deseja abrir automaticamente ao iniciar o perfil no campo start_pages.

Se você só precisa adicionar extensões, favoritos ou páginas iniciais, pode remover os parâmetros desnecessários do trecho:

data: {

        extensions: ["nkbihfbeogaeaoehlefnkodbefgpgknn@12.17.2"], //remove if you don’t need to add extensions

        bookmarks: [

            { name: "google", url: "https://google.com" },

            { name: "facebook", url: "https://facebook.com" }

        ],//remove if you don’t need bookmarks

        start_pages: ["https://google.com", "https://facebook.com"] //remove if you don’t need start pages

    }
data: {

        extensions: ["nkbihfbeogaeaoehlefnkodbefgpgknn@12.17.2"], //remove if you don’t need to add extensions

        bookmarks: [

            { name: "google", url: "https://google.com" },

            { name: "facebook", url: "https://facebook.com" }

        ],//remove if you don’t need bookmarks

        start_pages: ["https://google.com", "https://facebook.com"] //remove if you don’t need start pages

    }
adding_info.js
  1. Salve o arquivo adding_info.js.

  2. Execute-o usando o comando node adding_info.js.

As extensões, favoritos e páginas iniciais necessários serão adicionados a todos os seus perfis. Se você precisar aplicar alterações apenas a perfis específicos, consulte o exemplo na documentação.

The required extensions, bookmarks, and start pages will be added to all your profiles.

Antes de executar o script, certifique-se de que os perfis que deseja atualizar não estejam em execução. Perfis em execução não serão atualizados.

Trecho para adicionar extensões, páginas iniciais e favoritos aos perfis

const axios = require('axios');
// This is the configuration. Specify your Octo API Token here; you can modify this for your personal needs
const config = {
    octo_token: "OCTO_API_TOKEN", // Specify your API token here
    octo_api_base_url: "https://app.octobrowser.net/api/v2/automation/",
    data: {
        "extensions": ["aapfglkgnhmiaabhiijkjpfmhllaodgp@4.2.3"],
        "bookmarks": [
            { "name": "google", "url": "https://google.com" },
            { "name": "facebook", "url": "https://facebook.com" }
        ],
        "start_pages": ["https://google.com", "https://facebook.com"]
    }
}
const OCTO_REMOTE_API = axios.create({
    baseURL: config.octo_api_base_url,
    timeout: 10000,
    headers: {
        'X-Octo-Api-Token': config.octo_token,
        'Content-Type': "application/json"
    }
});
//functions
async function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms * 1000));
}
async function check_limits(response) {
    function parse_int_safe(value) {
        const parsed = parseInt(value, 10);
        return isNaN(parsed) ? 0 : parsed;
    }
    const ratelimit_header = response.headers.ratelimit;
    if (!ratelimit_header) {
        console.warn('No ratelimit header found!');
        return;
    }
    const limit_entries = ratelimit_header.split(',').map(entry => entry.trim());
    for (const entry of limit_entries) {
        const name_match = entry.match(/^([^;]+)/);
        const r_match = entry.match(/;r=(\d+)/);
        const t_match = entry.match(/;t=(\d+)/);
        if (!r_match || !t_match) {
            console.warn(`Invalid ratelimit format: ${entry}`);
            continue;
        }
        const limit_name = name_match ? name_match[1] : 'unknown_limit';
        const remaining_quantity = parse_int_safe(r_match[1]);
        const window_seconds = parse_int_safe(t_match[1]);
        if (remaining_quantity < 5) {
            const wait_time = window_seconds + 1;
            console.log(`Waiting ${wait_time} seconds due to ${limit_name} limit`);
            await sleep(wait_time);
        }
    }
}
async function get_total_profiles() {
    const response = await OCTO_REMOTE_API.get('/profiles?page=0&page_len=10');
    const total_profiles = response.data.total_count;
    const total_pages = Math.ceil(response.data.total_count / 100);
    console.log(`Total Profiles: ${total_profiles}\nTotal Pages: ${total_pages}`);
    await check_limits(response);
    return total_pages;
}
async function get_all_profiles_uuids(total_pages) {
    let profiles = [];
    for (let i = 0; i < total_pages; i++) {
        let response = await OCTO_REMOTE_API.get(`/profiles?page=${i}&page_len=100`);
        await check_limits(response);
        profiles.push(...response.data.data);
    }
    return profiles;
}
async function patch_all_profiles(profiles) {
    let updated = [];
    let not_updated = [];
    for (let profile of profiles) {
        try {
            const response = await OCTO_REMOTE_API.patch(`/profiles/${profile.uuid}`, config.data);
            await check_limits(response);
            updated.push(profile);
            console.log(`Successfully updated ${profile.uuid}`);
        } catch (error) {
            not_updated.push(profile);
            console.error(`ERROR: Can't patch profile ${profile.uuid}`);
        }
    }
    return [updated, not_updated];
}
(async () => {
    const total = await get_total_profiles();
    const profiles = await get_all_profiles_uuids(total);
    const [updated, not_updated] = await patch_all_profiles(profiles);
    console.log(`Finished process:\nUpdated: ${updated.length}\nNot updated: ${not_updated.length}`);
})();
const axios = require('axios');
// This is the configuration. Specify your Octo API Token here; you can modify this for your personal needs
const config = {
    octo_token: "OCTO_API_TOKEN", // Specify your API token here
    octo_api_base_url: "https://app.octobrowser.net/api/v2/automation/",
    data: {
        "extensions": ["aapfglkgnhmiaabhiijkjpfmhllaodgp@4.2.3"],
        "bookmarks": [
            { "name": "google", "url": "https://google.com" },
            { "name": "facebook", "url": "https://facebook.com" }
        ],
        "start_pages": ["https://google.com", "https://facebook.com"]
    }
}
const OCTO_REMOTE_API = axios.create({
    baseURL: config.octo_api_base_url,
    timeout: 10000,
    headers: {
        'X-Octo-Api-Token': config.octo_token,
        'Content-Type': "application/json"
    }
});
//functions
async function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms * 1000));
}
async function check_limits(response) {
    function parse_int_safe(value) {
        const parsed = parseInt(value, 10);
        return isNaN(parsed) ? 0 : parsed;
    }
    const ratelimit_header = response.headers.ratelimit;
    if (!ratelimit_header) {
        console.warn('No ratelimit header found!');
        return;
    }
    const limit_entries = ratelimit_header.split(',').map(entry => entry.trim());
    for (const entry of limit_entries) {
        const name_match = entry.match(/^([^;]+)/);
        const r_match = entry.match(/;r=(\d+)/);
        const t_match = entry.match(/;t=(\d+)/);
        if (!r_match || !t_match) {
            console.warn(`Invalid ratelimit format: ${entry}`);
            continue;
        }
        const limit_name = name_match ? name_match[1] : 'unknown_limit';
        const remaining_quantity = parse_int_safe(r_match[1]);
        const window_seconds = parse_int_safe(t_match[1]);
        if (remaining_quantity < 5) {
            const wait_time = window_seconds + 1;
            console.log(`Waiting ${wait_time} seconds due to ${limit_name} limit`);
            await sleep(wait_time);
        }
    }
}
async function get_total_profiles() {
    const response = await OCTO_REMOTE_API.get('/profiles?page=0&page_len=10');
    const total_profiles = response.data.total_count;
    const total_pages = Math.ceil(response.data.total_count / 100);
    console.log(`Total Profiles: ${total_profiles}\nTotal Pages: ${total_pages}`);
    await check_limits(response);
    return total_pages;
}
async function get_all_profiles_uuids(total_pages) {
    let profiles = [];
    for (let i = 0; i < total_pages; i++) {
        let response = await OCTO_REMOTE_API.get(`/profiles?page=${i}&page_len=100`);
        await check_limits(response);
        profiles.push(...response.data.data);
    }
    return profiles;
}
async function patch_all_profiles(profiles) {
    let updated = [];
    let not_updated = [];
    for (let profile of profiles) {
        try {
            const response = await OCTO_REMOTE_API.patch(`/profiles/${profile.uuid}`, config.data);
            await check_limits(response);
            updated.push(profile);
            console.log(`Successfully updated ${profile.uuid}`);
        } catch (error) {
            not_updated.push(profile);
            console.error(`ERROR: Can't patch profile ${profile.uuid}`);
        }
    }
    return [updated, not_updated];
}
(async () => {
    const total = await get_total_profiles();
    const profiles = await get_all_profiles_uuids(total);
    const [updated, not_updated] = await patch_all_profiles(profiles);
    console.log(`Finished process:\nUpdated: ${updated.length}\nNot updated: ${not_updated.length}`);
})();

Exportando dados de perfil para o Google Sheets

Você pode exportar seus dados de perfil para uma planilha. Isso facilita a estruturação das informações, bem como classificá-las e analisá-las. Você pode exportar os seguintes parâmetros de perfil:

  • UUID,

  • tags,

  • nome,

  • descrição.

  1. Crie um arquivo nomeado google_sheets.js na pasta octo_api.

  2. Adicione o código do trecho ao arquivo.

  3. Preencha o campo octo_token. O token de API pode ser encontrado no Octo Browser.

  4. Preencha o campo table_id. Este é o ID do seu documento Google Sheets.

  5. Preencha o campo table_sheet_name. Este é o nome da folha dentro do documento Google Sheets onde você deseja exportar os dados.

google_sheets.js
  1. Salve as alterações.

  2. Adicione os arquivos credentials.json e token.json à pasta octo_api.

  3. Execute o script no terminal do VS Code usando o comando node google_sheets.js.

  4. Uma vez que o script termine, os dados serão carregados na planilha especificada.

    google_sheets.js

Trecho para exportar dados de perfil para o Google Sheets

const axios = require('axios');
const { google } = require('googleapis');
const token = require('./token.json');
const credentials = require('./credentials.json');


const oAuth2Client = new google.auth.OAuth2(
    credentials.installed.client_id,
    credentials.installed.client_secret,
    credentials.installed.redirect_uris[0]
);


oAuth2Client.setCredentials(token);


//configuration
//Paste your Octo API token, spreadsheet ID and sheet name here
const config = {
    octo_token: "OCTO_API_TOKEN",
    table_id: "1pqnVysHymZSI5Lzx6odasdasAqE022fqUEf6q5LnOqAhylLSM",
    table_sheet_name: "Profiles",
    octo_api_base_url: "https://app.octobrowser.net/api/v2/automation/",
}


const OCTO_REMOTE_API = axios.create({
    baseURL: config.octo_api_base_url,
    timeout: 10000,
    headers: {
        'X-Octo-Api-Token': config.octo_token,
        'Content-Type': "application/json"
    }
});


//functions
async function check_limits(response) {
    function parse_int_safe(value) {
        const parsed = parseInt(value, 10);
        return isNaN(parsed) ? 0 : parsed;
    }
    const ratelimit_header = response.headers.ratelimit;
    if (!ratelimit_header) {
        console.warn('No ratelimit header found!');
        return;
    }
    const limit_entries = ratelimit_header.split(',').map(entry => entry.trim());
    for (const entry of limit_entries) {
        const name_match = entry.match(/^([^;]+)/);
        const r_match = entry.match(/;r=(\d+)/);
        const t_match = entry.match(/;t=(\d+)/);
        if (!r_match || !t_match) {
            console.warn(`Invalid ratelimit format: ${entry}`);
            continue;
        }
        const limit_name = name_match ? name_match[1] : 'unknown_limit';
        const remaining_quantity = parse_int_safe(r_match[1]);
        const window_seconds = parse_int_safe(t_match[1]);
        if (remaining_quantity < 5) {
            const wait_time = window_seconds + 1;
            console.log(`Waiting ${wait_time} seconds due to ${limit_name} limit`);
            await sleep(wait_time);
        }
    }
}


async function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms * 1000));
}




async function get_total_pages() {
    const response = await OCTO_REMOTE_API.get('/profiles?page=0&page_len=10');
    const total_profiles = response.data.total_count;
    const total_pages = Math.ceil(response.data.total_count / 100);
    console.log(`Total Profiles: ${total_profiles}\nTotal Pages: ${total_pages}`);
    await check_limits(response);
    return total_pages;
}


async function get_all_profiles_data(total_pages) {
    let profiles = [];
    for (let i = 0; i < total_pages; i++) {
        let response = await OCTO_REMOTE_API.get(`/profiles?page=${i}&page_len=100&fields=title,description,tags`);
        await check_limits(response);
        profiles.push(...response.data.data);
    }
    return profiles;
}


async function fill_sheet_with_data(oAuth2Client, spreadsheet_id, list_name, data) {
    const sheets = google.sheets({ version: 'v4', auth: oAuth2Client });


    try {
        const sheetInfo = await sheets.spreadsheets.get({
            spreadsheetId: spreadsheet_id,
        });


        const sheetExists = sheetInfo.data.sheets.some(
            sheet => sheet.properties.title === list_name
        );


        if (!sheetExists) {
            await sheets.spreadsheets.batchUpdate({
                spreadsheetId: spreadsheet_id,
                resource: {
                    requests: [{
                        addSheet: {
                            properties: {
                                title: list_name
                            }
                        }
                    }]
                }
            });
            console.log(`Создан новый лист "${list_name}".`);
        }


        const headers = ['Uuid', 'Tags', 'Title', 'Description'];
        const values = data.map(item => [
            item.uuid,
            Array.isArray(item.tags) ? item.tags.join(', ') : item.tags,
            item.title,
            item.description || ''
        ]);


        await sheets.spreadsheets.values.update({
            spreadsheetId: spreadsheet_id,
            range: `${list_name}!A1:D1`,
            valueInputOption: 'RAW',
            resource: {
                values: [headers]
            }
        });


        await sheets.spreadsheets.values.update({
            spreadsheetId: spreadsheet_id,
            range: `${list_name}!A2:D${values.length + 1}`,
            valueInputOption: 'RAW',
            resource: {
                values: values
            }
        });


        console.log(`Данные успешно записаны в лист "${list_name}".`);


    } catch (err) {
        console.error('Ошибка при обновлении таблицы:', err);
        throw err;
    }
}


(async () => {
    const total_pages = await get_total_pages();
    const profiles_data = await get_all_profiles_data(total_pages);
    await fill_sheet_with_data(oAuth2Client, config.table_id, config.table_sheet_name, profiles_data);
})()

const axios = require('axios');
const { google } = require('googleapis');
const token = require('./token.json');
const credentials = require('./credentials.json');


const oAuth2Client = new google.auth.OAuth2(
    credentials.installed.client_id,
    credentials.installed.client_secret,
    credentials.installed.redirect_uris[0]
);


oAuth2Client.setCredentials(token);


//configuration
//Paste your Octo API token, spreadsheet ID and sheet name here
const config = {
    octo_token: "OCTO_API_TOKEN",
    table_id: "1pqnVysHymZSI5Lzx6odasdasAqE022fqUEf6q5LnOqAhylLSM",
    table_sheet_name: "Profiles",
    octo_api_base_url: "https://app.octobrowser.net/api/v2/automation/",
}


const OCTO_REMOTE_API = axios.create({
    baseURL: config.octo_api_base_url,
    timeout: 10000,
    headers: {
        'X-Octo-Api-Token': config.octo_token,
        'Content-Type': "application/json"
    }
});


//functions
async function check_limits(response) {
    function parse_int_safe(value) {
        const parsed = parseInt(value, 10);
        return isNaN(parsed) ? 0 : parsed;
    }
    const ratelimit_header = response.headers.ratelimit;
    if (!ratelimit_header) {
        console.warn('No ratelimit header found!');
        return;
    }
    const limit_entries = ratelimit_header.split(',').map(entry => entry.trim());
    for (const entry of limit_entries) {
        const name_match = entry.match(/^([^;]+)/);
        const r_match = entry.match(/;r=(\d+)/);
        const t_match = entry.match(/;t=(\d+)/);
        if (!r_match || !t_match) {
            console.warn(`Invalid ratelimit format: ${entry}`);
            continue;
        }
        const limit_name = name_match ? name_match[1] : 'unknown_limit';
        const remaining_quantity = parse_int_safe(r_match[1]);
        const window_seconds = parse_int_safe(t_match[1]);
        if (remaining_quantity < 5) {
            const wait_time = window_seconds + 1;
            console.log(`Waiting ${wait_time} seconds due to ${limit_name} limit`);
            await sleep(wait_time);
        }
    }
}


async function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms * 1000));
}




async function get_total_pages() {
    const response = await OCTO_REMOTE_API.get('/profiles?page=0&page_len=10');
    const total_profiles = response.data.total_count;
    const total_pages = Math.ceil(response.data.total_count / 100);
    console.log(`Total Profiles: ${total_profiles}\nTotal Pages: ${total_pages}`);
    await check_limits(response);
    return total_pages;
}


async function get_all_profiles_data(total_pages) {
    let profiles = [];
    for (let i = 0; i < total_pages; i++) {
        let response = await OCTO_REMOTE_API.get(`/profiles?page=${i}&page_len=100&fields=title,description,tags`);
        await check_limits(response);
        profiles.push(...response.data.data);
    }
    return profiles;
}


async function fill_sheet_with_data(oAuth2Client, spreadsheet_id, list_name, data) {
    const sheets = google.sheets({ version: 'v4', auth: oAuth2Client });


    try {
        const sheetInfo = await sheets.spreadsheets.get({
            spreadsheetId: spreadsheet_id,
        });


        const sheetExists = sheetInfo.data.sheets.some(
            sheet => sheet.properties.title === list_name
        );


        if (!sheetExists) {
            await sheets.spreadsheets.batchUpdate({
                spreadsheetId: spreadsheet_id,
                resource: {
                    requests: [{
                        addSheet: {
                            properties: {
                                title: list_name
                            }
                        }
                    }]
                }
            });
            console.log(`Создан новый лист "${list_name}".`);
        }


        const headers = ['Uuid', 'Tags', 'Title', 'Description'];
        const values = data.map(item => [
            item.uuid,
            Array.isArray(item.tags) ? item.tags.join(', ') : item.tags,
            item.title,
            item.description || ''
        ]);


        await sheets.spreadsheets.values.update({
            spreadsheetId: spreadsheet_id,
            range: `${list_name}!A1:D1`,
            valueInputOption: 'RAW',
            resource: {
                values: [headers]
            }
        });


        await sheets.spreadsheets.values.update({
            spreadsheetId: spreadsheet_id,
            range: `${list_name}!A2:D${values.length + 1}`,
            valueInputOption: 'RAW',
            resource: {
                values: values
            }
        });


        console.log(`Данные успешно записаны в лист "${list_name}".`);


    } catch (err) {
        console.error('Ошибка при обновлении таблицы:', err);
        throw err;
    }
}


(async () => {
    const total_pages = await get_total_pages();
    const profiles_data = await get_all_profiles_data(total_pages);
    await fill_sheet_with_data(oAuth2Client, config.table_id, config.table_sheet_name, profiles_data);
})()

Como obter table_id

  1. Abra ou crie uma planilha no Google Sheets.

  2. Copie o ID da tabela. Esta é a parte do URL entre /d/ e /edit.

Por exemplo, se seu link parecer https://docs.google.com/spreadsheets/d/1GoPnhkStxFFxDzGZjjXSyjT_H_msHlSDx51tBROewOA/edit, então o table_id é 1GoPnhkStxFFxDzGZjjXSyjT_H_msHlSDx51tBROewOA.

Como obter o arquivo credentials.json

  1. Faça login em uma conta pessoal (não organizacional) do Gmail.

  2. Acesse https://console.cloud.google.com/.

  3. Selecione qualquer país europeu na lista e clique em Concordar e continuar.

  4. Clique em Selecionar um projeto, depois em Novo projeto.

  5. Insira qualquer nome de projeto e clique em Criar.

  6. Acesse https://console.cloud.google.com/auth e clique em Começar.

  7. Insira qualquer Nome do App, seu e-mail, e clique em Próximo.

  8. Selecione Externo, insira seu e-mail novamente, clique em Próximo, marque a caixa de consentimento, depois clique em Continuar e Finalizar.

  9. Acesse https://console.cloud.google.com/auth/audience.

  10. Na seção Testar Usuários, clique em Adicionar Usuários.

In the Test Users section, click Add Users.
  1. Adicione seu e-mail e clique em Salvar.

  2. Na página https://console.cloud.google.com/auth/overview, clique em Criar Cliente OAuth.

Create OAuth Client
  1. Selecione Aplicativo de escritório na lista, insira qualquer nome e clique em Criar.

  2. Uma janela pop-up aparecerá. Baixe o arquivo JSON dela.

Download the JSON file from it.
  1. Acesse https://console.cloud.google.com/apis/library.

  2. Pesquise por Google Sheets API.

  3. Selecione-o e clique em Ativar.

  4. Abra o arquivo JSON baixado anteriormente.

  5. Altere o valor de redirect_uris de http://localhost para "redirect_uris": ["urn:ietf:wg:oauth:2.0:oob","http://localhost"]

Faça a alteração com cuidado para não remover acidentalmente os colchetes.

credentials.json
  1. Salve o arquivo.

  2. Renomeie-o para credentials.json.

  3. Adicione-o à pasta octo_api.

Como obter o arquivo token.json

  1. Crie um arquivo chamado get_token.js na pasta octo_api e abra-o no VS Code.

  2. Execute o comando Npm install googleapis no terminal do VS Code.

  3. Cole o script de geração de token em get_token.js e salve o arquivo.

  4. Execute o script usando node get_token.js.

  5. Você verá um link dizendo Abra essa URL no navegador: https://accounts.google.com/o/oauth2/…

  6. Abra o link.

  7. Faça login na sua conta Google.

  8. Clique nas telas de consentimento.

  9. Na página final, o Google exibirá um código de autorização.

Google will display an authorization code
  1. Cole o código no terminal e pressione Enter.

  2. O script então criará um arquivo token.json na pasta onde você o executou.

token.json

Script de geração de token

const fs = require('fs');
const readline = require('readline');
const { google } = require('googleapis');

const CREDENTIALS_PATH = 'credentials.json';
const TOKEN_PATH = 'token.json';


const credentials = JSON.parse(fs.readFileSync(CREDENTIALS_PATH));
const { client_secret, client_id, redirect_uris } = credentials.installed;

const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);


const SCOPES = ['https://www.googleapis.com/auth/spreadsheets'];


const authUrl = oAuth2Client.generateAuthUrl({
  access_type: 'offline',
  scope: SCOPES,
});

console.log('Open this URL in browser:', authUrl);

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

rl.question('Enter the code from that page here: ', (code) => {
  rl.close();
  oAuth2Client.getToken(code, (err, token) => {
    if (err) return console.error('Error retrieving access token', err);
    fs.writeFileSync(TOKEN_PATH, JSON.stringify(token));
    console.log('Token stored to', TOKEN_PATH);
  });
});
const fs = require('fs');
const readline = require('readline');
const { google } = require('googleapis');

const CREDENTIALS_PATH = 'credentials.json';
const TOKEN_PATH = 'token.json';


const credentials = JSON.parse(fs.readFileSync(CREDENTIALS_PATH));
const { client_secret, client_id, redirect_uris } = credentials.installed;

const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);


const SCOPES = ['https://www.googleapis.com/auth/spreadsheets'];


const authUrl = oAuth2Client.generateAuthUrl({
  access_type: 'offline',
  scope: SCOPES,
});

console.log('Open this URL in browser:', authUrl);

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

rl.question('Enter the code from that page here: ', (code) => {
  rl.close();
  oAuth2Client.getToken(code, (err, token) => {
    if (err) return console.error('Error retrieving access token', err);
    fs.writeFileSync(TOKEN_PATH, JSON.stringify(token));
    console.log('Token stored to', TOKEN_PATH);
  });
});

Conclusão

Usamos a linguagem de programação Node.js nos exemplos deste artigo. Você pode encontrar trechos úteis em outras linguagens na nossa documentação.

Octo Browser documentation

Neste artigo, cobrimos:

  1. como preparar seu ambiente;

  2. como obter seu token de API do Octo Browser;

  3. como recuperar um UUID de extensão;

  4. como obter nomes de perfis usando API e salvá-los em um arquivo de texto;

  5. como adicionar extensões, favoritos e páginas iniciais em massa a perfis existentes;

  6. como exportar dados de perfil para o Google Sheets.

Se você ainda tiver dúvidas ou não conseguiu encontrar o exemplo que precisa na documentação, sinta-se à vontade para entrar em contato com nosso Atendimento ao Cliente no Telegram ou usando nosso chat do site ou o widget do navegador.

Preparação

  1. Baixe e instale o VS Code.

  2. Baixe e instale o Node.js.

  3. Crie uma pasta em um local conveniente e nomeie-a, por exemplo, octo_api.

  4. Abra esta pasta no VS Code.

  5. Crie um arquivo com extensão .js. É melhor nomeá-lo de acordo com a ação que o código irá executar para evitar confusão: por exemplo, get_titles.js.

  6. Abra o terminal e execute o comando npm install axios para instalar uma dependência para o Node.js.

  7. Se o VS Code mostrar um erro, abra o Windows PowerShell como administrador, insira o comando Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned e confirme. Em seguida, repita a etapa anterior.

  8. Inicie o Octo Browser.

Onde encontrar seu token de API

Para interagir com o Octo Browser via API, você precisará de um token de API. Está disponível para usuários com uma assinatura Base e superior. O token de API é exibido no Octo Browser nas configurações da conta principal, na guia "Adicional" (outros membros da equipe não podem ver o token de API).

API token is displayed in Octo Browser

Limites de API (limites de taxa)

Lembre-se de que a API do Octo Browser tem limites de requisição. Os trechos fornecidos usam a função check_limits, que verifica os cabeçalhos de limites de taxa da API e pausa automaticamente se o número de requisições restantes for baixo. Isso ajuda a evitar erros 429 Too Many Requests.

Ao fazer requisições para a API Pública, 1 RPM e 1 RPH são consumidos por requisição. Quando usa a API Local, RPM/RPH não são consumidos, exceto para POST Start Profile (1 RPM e 1 RPH) e POST One-time profile (4 RPM e 4 RPH).

O número exato de perfis que você pode iniciar, criar e excluir sob uma assinatura específica depende totalmente de seus scripts e sua lógica. Você pode calcular de forma independente o número de requisições necessárias para seu fluxo de trabalho com base em quais requisições afetam os limites de RPH e RPM.

Algumas requisições de API podem exigir um processamento mais complexo, o que significa que sua execução pode custar mais do que uma única requisição. Isso é necessário para balancear a carga do servidor e garantir desempenho otimizado para todos os usuários.

RPM significa requisições por minuto.
RPH significa requisições por hora.
Os valores de limite de requisição dependem da sua assinatura.

Com a configuração completa, vamos passar para trechos de código úteis.

Recuperando nomes de perfil e salvando-os em um arquivo .txt

Este trecho é útil quando você precisa coletar rapidamente uma lista de todos os perfis criados em um único lugar. Ele ajuda a simplificar o rastreamento de perfis, verificar sua existência real e realizar outras ações com eles.

  1. Crie um arquivo get_titles.js na pasta octo_api e cole o código do trecho nele.

  2. Substitua OCTO_API_TOKEN no campo octo_token pelo seu token de API do Octo Browser.

OCTO_API_TOKEN
  1. Salve o arquivo.

  2. Insira o comando node get_titles.js no terminal e execute o script.

saving profile names to a .txt file

Os nomes dos perfis serão salvos linha por linha no arquivo profiles_titles.txt na mesma pasta que o script.

Trecho para salvar nomes de perfis em um arquivo .txt

const fs = require('fs').promises;
const axios = require('axios');
//This is the configuration. Specify your Octo API Token here
const config = {
    octo_token: "OCTO_API_TOKEN", // Specify your API token here
    octo_api_base_url: "https://app.octobrowser.net/api/v2/automation/",
}
const OCTO_REMOTE_API = axios.create({
    baseURL: config.octo_api_base_url, 
    timeout: 10000,
    headers: {
        'X-Octo-Api-Token': config.octo_token,
        'Content-Type': "application/json"
    }
});
async function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms * 1000));
}
async function check_limits(response) {
    function parse_int_safe(value) {
        const parsed = parseInt(value, 10);
        return isNaN(parsed) ? 0 : parsed;
    }
    const ratelimit_header = response.headers.ratelimit;
    if (!ratelimit_header) {
        console.warn('No ratelimit header found!');
        return;
    }
    const limit_entries = ratelimit_header.split(',').map(entry => entry.trim());
    for (const entry of limit_entries) {
        const name_match = entry.match(/^([^;]+)/);
        const r_match = entry.match(/;r=(\d+)/);
        const t_match = entry.match(/;t=(\d+)/);
        if (!r_match || !t_match) {
            console.warn(`Invalid ratelimit format: ${entry}`);
            continue;
        }
        const limit_name = name_match ? name_match[1] : 'unknown_limit';
        const remaining_quantity = parse_int_safe(r_match[1]);
        const window_seconds = parse_int_safe(t_match[1]);
        if (remaining_quantity < 5) {
            const wait_time = window_seconds + 1;
            console.log(`Waiting ${wait_time} seconds due to ${limit_name} limit`);
            await sleep(wait_time);
        }
    }
}
async function get_total_pages() {
    const response = await OCTO_REMOTE_API.get('/profiles?page=0&page_len=10');
    const total_profiles = response.data.total_count;
    const total_pages = Math.ceil(response.data.total_count / 100);
    console.log(`Total Profiles: ${total_profiles}\nTotal Pages: ${total_pages}`);
    await check_limits(response);
    return total_pages;
}
async function get_all_profiles_titles(total_pages) {
    let profiles = [];
    for (let i = 0; i < total_pages; i++) {
        let response = await OCTO_REMOTE_API.get(`/profiles?page=${i}&page_len=100&fields=title`);
        await check_limits(response);
        profiles.push(...response.data.data);
    }
    return profiles;
}
async function write_file(profiles) {
    const titles = profiles.map(item => item.title).filter(Boolean);
    try {
        await fs.writeFile('./profiles_titles.txt', titles.join('\n'), 'utf-8');
    } catch (error) {
        console.error(`ERROR: While trying writing the file some error occured:\n${error}`);
    }
}
(async () => {
    const total_pages = await get_total_pages();
    const profiles = await get_all_profiles_titles(total_pages);
    await write_file(profiles);
    console.log('Finished. Check profiles_titles.txt file...');
})()

Adicionar extensões, páginas iniciais e favoritos aos perfis

Este script de automação permite que você padronize rapidamente as configurações de perfil e evite adicionar manualmente os mesmos parâmetros.

Para adicionar extensões, você precisará dos UUIDs delas:

  1. Abra um perfil com a extensão instalada.

  2. Na barra de endereços do perfil, insira chrome://extensions/ e pressione Enter.

  3. Na extensão desejada, clique no botão “Detalhes”.

“Details” button
  1. No final da página, copie o UUID da extensão junto com sua versão.

copy the extension UUID

Você pode encontrar métodos para recuperar UUIDs via API na documentação.

Em seguida, você precisa editar o código:

  1. Crie um arquivo nomeado adding_info.js na pasta octo_api.

  2. Cole o trecho nele.

  3. Insira seu token de API no campo octo_token.

  4. Adicione os UUIDs das extensões necessárias ao campo extensions.

  5. Adicione o nome (título do favorito) e o URL (endereço do site) ao campo bookmarks.

  6. Adicione os URLs dos sites que você deseja abrir automaticamente ao iniciar o perfil no campo start_pages.

Se você só precisa adicionar extensões, favoritos ou páginas iniciais, pode remover os parâmetros desnecessários do trecho:

data: {

        extensions: ["nkbihfbeogaeaoehlefnkodbefgpgknn@12.17.2"], //remove if you don’t need to add extensions

        bookmarks: [

            { name: "google", url: "https://google.com" },

            { name: "facebook", url: "https://facebook.com" }

        ],//remove if you don’t need bookmarks

        start_pages: ["https://google.com", "https://facebook.com"] //remove if you don’t need start pages

    }
adding_info.js
  1. Salve o arquivo adding_info.js.

  2. Execute-o usando o comando node adding_info.js.

As extensões, favoritos e páginas iniciais necessários serão adicionados a todos os seus perfis. Se você precisar aplicar alterações apenas a perfis específicos, consulte o exemplo na documentação.

The required extensions, bookmarks, and start pages will be added to all your profiles.

Antes de executar o script, certifique-se de que os perfis que deseja atualizar não estejam em execução. Perfis em execução não serão atualizados.

Trecho para adicionar extensões, páginas iniciais e favoritos aos perfis

const axios = require('axios');
// This is the configuration. Specify your Octo API Token here; you can modify this for your personal needs
const config = {
    octo_token: "OCTO_API_TOKEN", // Specify your API token here
    octo_api_base_url: "https://app.octobrowser.net/api/v2/automation/",
    data: {
        "extensions": ["aapfglkgnhmiaabhiijkjpfmhllaodgp@4.2.3"],
        "bookmarks": [
            { "name": "google", "url": "https://google.com" },
            { "name": "facebook", "url": "https://facebook.com" }
        ],
        "start_pages": ["https://google.com", "https://facebook.com"]
    }
}
const OCTO_REMOTE_API = axios.create({
    baseURL: config.octo_api_base_url,
    timeout: 10000,
    headers: {
        'X-Octo-Api-Token': config.octo_token,
        'Content-Type': "application/json"
    }
});
//functions
async function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms * 1000));
}
async function check_limits(response) {
    function parse_int_safe(value) {
        const parsed = parseInt(value, 10);
        return isNaN(parsed) ? 0 : parsed;
    }
    const ratelimit_header = response.headers.ratelimit;
    if (!ratelimit_header) {
        console.warn('No ratelimit header found!');
        return;
    }
    const limit_entries = ratelimit_header.split(',').map(entry => entry.trim());
    for (const entry of limit_entries) {
        const name_match = entry.match(/^([^;]+)/);
        const r_match = entry.match(/;r=(\d+)/);
        const t_match = entry.match(/;t=(\d+)/);
        if (!r_match || !t_match) {
            console.warn(`Invalid ratelimit format: ${entry}`);
            continue;
        }
        const limit_name = name_match ? name_match[1] : 'unknown_limit';
        const remaining_quantity = parse_int_safe(r_match[1]);
        const window_seconds = parse_int_safe(t_match[1]);
        if (remaining_quantity < 5) {
            const wait_time = window_seconds + 1;
            console.log(`Waiting ${wait_time} seconds due to ${limit_name} limit`);
            await sleep(wait_time);
        }
    }
}
async function get_total_profiles() {
    const response = await OCTO_REMOTE_API.get('/profiles?page=0&page_len=10');
    const total_profiles = response.data.total_count;
    const total_pages = Math.ceil(response.data.total_count / 100);
    console.log(`Total Profiles: ${total_profiles}\nTotal Pages: ${total_pages}`);
    await check_limits(response);
    return total_pages;
}
async function get_all_profiles_uuids(total_pages) {
    let profiles = [];
    for (let i = 0; i < total_pages; i++) {
        let response = await OCTO_REMOTE_API.get(`/profiles?page=${i}&page_len=100`);
        await check_limits(response);
        profiles.push(...response.data.data);
    }
    return profiles;
}
async function patch_all_profiles(profiles) {
    let updated = [];
    let not_updated = [];
    for (let profile of profiles) {
        try {
            const response = await OCTO_REMOTE_API.patch(`/profiles/${profile.uuid}`, config.data);
            await check_limits(response);
            updated.push(profile);
            console.log(`Successfully updated ${profile.uuid}`);
        } catch (error) {
            not_updated.push(profile);
            console.error(`ERROR: Can't patch profile ${profile.uuid}`);
        }
    }
    return [updated, not_updated];
}
(async () => {
    const total = await get_total_profiles();
    const profiles = await get_all_profiles_uuids(total);
    const [updated, not_updated] = await patch_all_profiles(profiles);
    console.log(`Finished process:\nUpdated: ${updated.length}\nNot updated: ${not_updated.length}`);
})();

Exportando dados de perfil para o Google Sheets

Você pode exportar seus dados de perfil para uma planilha. Isso facilita a estruturação das informações, bem como classificá-las e analisá-las. Você pode exportar os seguintes parâmetros de perfil:

  • UUID,

  • tags,

  • nome,

  • descrição.

  1. Crie um arquivo nomeado google_sheets.js na pasta octo_api.

  2. Adicione o código do trecho ao arquivo.

  3. Preencha o campo octo_token. O token de API pode ser encontrado no Octo Browser.

  4. Preencha o campo table_id. Este é o ID do seu documento Google Sheets.

  5. Preencha o campo table_sheet_name. Este é o nome da folha dentro do documento Google Sheets onde você deseja exportar os dados.

google_sheets.js
  1. Salve as alterações.

  2. Adicione os arquivos credentials.json e token.json à pasta octo_api.

  3. Execute o script no terminal do VS Code usando o comando node google_sheets.js.

  4. Uma vez que o script termine, os dados serão carregados na planilha especificada.

    google_sheets.js

Trecho para exportar dados de perfil para o Google Sheets

const axios = require('axios');
const { google } = require('googleapis');
const token = require('./token.json');
const credentials = require('./credentials.json');


const oAuth2Client = new google.auth.OAuth2(
    credentials.installed.client_id,
    credentials.installed.client_secret,
    credentials.installed.redirect_uris[0]
);


oAuth2Client.setCredentials(token);


//configuration
//Paste your Octo API token, spreadsheet ID and sheet name here
const config = {
    octo_token: "OCTO_API_TOKEN",
    table_id: "1pqnVysHymZSI5Lzx6odasdasAqE022fqUEf6q5LnOqAhylLSM",
    table_sheet_name: "Profiles",
    octo_api_base_url: "https://app.octobrowser.net/api/v2/automation/",
}


const OCTO_REMOTE_API = axios.create({
    baseURL: config.octo_api_base_url,
    timeout: 10000,
    headers: {
        'X-Octo-Api-Token': config.octo_token,
        'Content-Type': "application/json"
    }
});


//functions
async function check_limits(response) {
    function parse_int_safe(value) {
        const parsed = parseInt(value, 10);
        return isNaN(parsed) ? 0 : parsed;
    }
    const ratelimit_header = response.headers.ratelimit;
    if (!ratelimit_header) {
        console.warn('No ratelimit header found!');
        return;
    }
    const limit_entries = ratelimit_header.split(',').map(entry => entry.trim());
    for (const entry of limit_entries) {
        const name_match = entry.match(/^([^;]+)/);
        const r_match = entry.match(/;r=(\d+)/);
        const t_match = entry.match(/;t=(\d+)/);
        if (!r_match || !t_match) {
            console.warn(`Invalid ratelimit format: ${entry}`);
            continue;
        }
        const limit_name = name_match ? name_match[1] : 'unknown_limit';
        const remaining_quantity = parse_int_safe(r_match[1]);
        const window_seconds = parse_int_safe(t_match[1]);
        if (remaining_quantity < 5) {
            const wait_time = window_seconds + 1;
            console.log(`Waiting ${wait_time} seconds due to ${limit_name} limit`);
            await sleep(wait_time);
        }
    }
}


async function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms * 1000));
}




async function get_total_pages() {
    const response = await OCTO_REMOTE_API.get('/profiles?page=0&page_len=10');
    const total_profiles = response.data.total_count;
    const total_pages = Math.ceil(response.data.total_count / 100);
    console.log(`Total Profiles: ${total_profiles}\nTotal Pages: ${total_pages}`);
    await check_limits(response);
    return total_pages;
}


async function get_all_profiles_data(total_pages) {
    let profiles = [];
    for (let i = 0; i < total_pages; i++) {
        let response = await OCTO_REMOTE_API.get(`/profiles?page=${i}&page_len=100&fields=title,description,tags`);
        await check_limits(response);
        profiles.push(...response.data.data);
    }
    return profiles;
}


async function fill_sheet_with_data(oAuth2Client, spreadsheet_id, list_name, data) {
    const sheets = google.sheets({ version: 'v4', auth: oAuth2Client });


    try {
        const sheetInfo = await sheets.spreadsheets.get({
            spreadsheetId: spreadsheet_id,
        });


        const sheetExists = sheetInfo.data.sheets.some(
            sheet => sheet.properties.title === list_name
        );


        if (!sheetExists) {
            await sheets.spreadsheets.batchUpdate({
                spreadsheetId: spreadsheet_id,
                resource: {
                    requests: [{
                        addSheet: {
                            properties: {
                                title: list_name
                            }
                        }
                    }]
                }
            });
            console.log(`Создан новый лист "${list_name}".`);
        }


        const headers = ['Uuid', 'Tags', 'Title', 'Description'];
        const values = data.map(item => [
            item.uuid,
            Array.isArray(item.tags) ? item.tags.join(', ') : item.tags,
            item.title,
            item.description || ''
        ]);


        await sheets.spreadsheets.values.update({
            spreadsheetId: spreadsheet_id,
            range: `${list_name}!A1:D1`,
            valueInputOption: 'RAW',
            resource: {
                values: [headers]
            }
        });


        await sheets.spreadsheets.values.update({
            spreadsheetId: spreadsheet_id,
            range: `${list_name}!A2:D${values.length + 1}`,
            valueInputOption: 'RAW',
            resource: {
                values: values
            }
        });


        console.log(`Данные успешно записаны в лист "${list_name}".`);


    } catch (err) {
        console.error('Ошибка при обновлении таблицы:', err);
        throw err;
    }
}


(async () => {
    const total_pages = await get_total_pages();
    const profiles_data = await get_all_profiles_data(total_pages);
    await fill_sheet_with_data(oAuth2Client, config.table_id, config.table_sheet_name, profiles_data);
})()

Como obter table_id

  1. Abra ou crie uma planilha no Google Sheets.

  2. Copie o ID da tabela. Esta é a parte do URL entre /d/ e /edit.

Por exemplo, se seu link parecer https://docs.google.com/spreadsheets/d/1GoPnhkStxFFxDzGZjjXSyjT_H_msHlSDx51tBROewOA/edit, então o table_id é 1GoPnhkStxFFxDzGZjjXSyjT_H_msHlSDx51tBROewOA.

Como obter o arquivo credentials.json

  1. Faça login em uma conta pessoal (não organizacional) do Gmail.

  2. Acesse https://console.cloud.google.com/.

  3. Selecione qualquer país europeu na lista e clique em Concordar e continuar.

  4. Clique em Selecionar um projeto, depois em Novo projeto.

  5. Insira qualquer nome de projeto e clique em Criar.

  6. Acesse https://console.cloud.google.com/auth e clique em Começar.

  7. Insira qualquer Nome do App, seu e-mail, e clique em Próximo.

  8. Selecione Externo, insira seu e-mail novamente, clique em Próximo, marque a caixa de consentimento, depois clique em Continuar e Finalizar.

  9. Acesse https://console.cloud.google.com/auth/audience.

  10. Na seção Testar Usuários, clique em Adicionar Usuários.

In the Test Users section, click Add Users.
  1. Adicione seu e-mail e clique em Salvar.

  2. Na página https://console.cloud.google.com/auth/overview, clique em Criar Cliente OAuth.

Create OAuth Client
  1. Selecione Aplicativo de escritório na lista, insira qualquer nome e clique em Criar.

  2. Uma janela pop-up aparecerá. Baixe o arquivo JSON dela.

Download the JSON file from it.
  1. Acesse https://console.cloud.google.com/apis/library.

  2. Pesquise por Google Sheets API.

  3. Selecione-o e clique em Ativar.

  4. Abra o arquivo JSON baixado anteriormente.

  5. Altere o valor de redirect_uris de http://localhost para "redirect_uris": ["urn:ietf:wg:oauth:2.0:oob","http://localhost"]

Faça a alteração com cuidado para não remover acidentalmente os colchetes.

credentials.json
  1. Salve o arquivo.

  2. Renomeie-o para credentials.json.

  3. Adicione-o à pasta octo_api.

Como obter o arquivo token.json

  1. Crie um arquivo chamado get_token.js na pasta octo_api e abra-o no VS Code.

  2. Execute o comando Npm install googleapis no terminal do VS Code.

  3. Cole o script de geração de token em get_token.js e salve o arquivo.

  4. Execute o script usando node get_token.js.

  5. Você verá um link dizendo Abra essa URL no navegador: https://accounts.google.com/o/oauth2/…

  6. Abra o link.

  7. Faça login na sua conta Google.

  8. Clique nas telas de consentimento.

  9. Na página final, o Google exibirá um código de autorização.

Google will display an authorization code
  1. Cole o código no terminal e pressione Enter.

  2. O script então criará um arquivo token.json na pasta onde você o executou.

token.json

Script de geração de token

const fs = require('fs');
const readline = require('readline');
const { google } = require('googleapis');

const CREDENTIALS_PATH = 'credentials.json';
const TOKEN_PATH = 'token.json';


const credentials = JSON.parse(fs.readFileSync(CREDENTIALS_PATH));
const { client_secret, client_id, redirect_uris } = credentials.installed;

const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);


const SCOPES = ['https://www.googleapis.com/auth/spreadsheets'];


const authUrl = oAuth2Client.generateAuthUrl({
  access_type: 'offline',
  scope: SCOPES,
});

console.log('Open this URL in browser:', authUrl);

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

rl.question('Enter the code from that page here: ', (code) => {
  rl.close();
  oAuth2Client.getToken(code, (err, token) => {
    if (err) return console.error('Error retrieving access token', err);
    fs.writeFileSync(TOKEN_PATH, JSON.stringify(token));
    console.log('Token stored to', TOKEN_PATH);
  });
});

Conclusão

Usamos a linguagem de programação Node.js nos exemplos deste artigo. Você pode encontrar trechos úteis em outras linguagens na nossa documentação.

Octo Browser documentation

Neste artigo, cobrimos:

  1. como preparar seu ambiente;

  2. como obter seu token de API do Octo Browser;

  3. como recuperar um UUID de extensão;

  4. como obter nomes de perfis usando API e salvá-los em um arquivo de texto;

  5. como adicionar extensões, favoritos e páginas iniciais em massa a perfis existentes;

  6. como exportar dados de perfil para o Google Sheets.

Se você ainda tiver dúvidas ou não conseguiu encontrar o exemplo que precisa na documentação, sinta-se à vontade para entrar em contato com nosso Atendimento ao Cliente no Telegram ou usando nosso chat do site ou o widget do navegador.

Mantenha-se atualizado com as últimas notícias do Octo Browser

Ao clicar no botão, você concorda com a nossa Política de Privacidade.

Mantenha-se atualizado com as últimas notícias do Octo Browser

Ao clicar no botão, você concorda com a nossa Política de Privacidade.

Mantenha-se atualizado com as últimas notícias do Octo Browser

Ao clicar no botão, você concorda com a nossa Política de Privacidade.

Junte-se ao Octo Browser agora mesmo

Ou entre em contato com a equipe de suporte no chat para tirar dúvidas a qualquer momento.

Junte-se ao Octo Browser agora mesmo

Ou entre em contato com a equipe de suporte no chat para tirar dúvidas a qualquer momento.

Junte-se ao Octo Browser agora mesmo

Ou entre em contato com a equipe de suporte no chat para tirar dúvidas a qualquer momento.

©

2026

Octo Browser

©

2026

Octo Browser

©

2026

Octo Browser