Downloads & integridade
- Binários prontos: ver a secção Releases do GitHub.
- Cada release inclui um ficheiro
checksums_<versão>.txtcom os SHA‑256 de todos os artefactos.Verificar no Linux/macOS
# Exemplo para a versão vX.Y.Z VER="vX.Y.Z" curl -sL "https://github.com/vba-excel/sharepoint-go/releases/download/${VER}/checksums_${VER}.txt" -o checksums.txt sha256sum -c checksums.txt # deve dizer OK para o(s) ficheiro(s) que tens localmenteVerificar no Windows (PowerShell)
$ver = 'vX.Y.Z' $zip = "sharepoint-client_$ver_windows_amd64.zip" $expected = (Invoke-WebRequest -UseBasicParsing "https://github.com/vba-excel/sharepoint-go/releases/download/$ver/checksums_$ver.txt").Content | Select-String $zip | ForEach-Object { ($_ -split ' ')[0] } $actual = (Get-FileHash $zip -Algorithm SHA256).Hash.ToLower() if ($actual -eq $expected) { "OK: $zip" } else { "FALHOU: $zip`nexpected=$expected`nactual =$actual"; exit 1 }
sharepoint-client é um utilitário de linha de comandos em Go para ler e escrever listas do SharePoint Online, com tolerância a throttling, paginação automática e formatos de saída pensados para pipelines (json, jsonl, csv).
Funciona bem com:
- Listas grandes (80k+ linhas)
- Consultas filtradas e ordenadas
- Casos de automação (integração com scripts)
- Criação / edição / remoção de itens
- Visão geral
- Configuração / pré-requisitos
- Flags globais
- Modos de operação
- Filtros, ordenação e limites
- Saída / formatação
- Throttling, fallback e summary
- Erros e exit codes
- Casos de uso comuns
- Resumo final
O binário expõe operações como:
- Ler registos (
list-items,latest-item,get-item) - Criar registos (
add-item) - Atualizar registos (
update-item) - Apagar registos (
delete-item)
Foi desenhado para:
- Evitar erros de throttling do SharePoint (“excede o limiar da vista de lista”)
- Exportar dados facilmente para outras ferramentas
- Ser previsível quando pedes “os últimos N registos”
- Fazer dumps completos de listas muito grandes, se precisares
Para correr sem build:
go run ./cmd/sharepoint-client [flags...]Para gerar binário:
go build ./cmd/sharepoint-client
./sharepoint-client [flags...]O cliente não pede user/pass diretamente. Em vez disso:
- Usa o Edge para obter cookies/tokens válidos do Microsoft 365.
- Faz cache desses cookies (memória e disco, cifrado).
- Usa esses cookies nas chamadas REST ao SharePoint.
O ficheiro de configuração (private.json por omissão) precisa de algo neste género:
{
"siteUrl": "https://TENANT.sharepoint.com/sites/NOME_DO_SITE",
"edgeOptions": {
"edgePath": "",
"userDataDir": "",
"profileDir": "",
"headless": true,
"timeoutSeconds": 180,
"debug": true,
"autoProfile": true,
"interactiveFallback": true,
"allowTempProfileWhenLocked": true,
"forceTempProfile": false,
"refreshSkewSeconds": 300
}
}-
O cliente não pede credenciais. Abre (ou reutiliza) uma sessão do Microsoft Edge, lê cookies de sessão M365/SharePoint (por ex.
FedAuth,rtFa) e guarda-as de forma segura:- cache em memória (válida durante a execução corrente),
- cache em disco cifrada (ficheiro no
%TEMP%/gosip, guardado com permissões 0600 para que só o utilizador atual consiga ler), - se ambas expirarem, volta a arrancar o Edge para renovar.
-
A autenticação é feita on-demand. Só abrimos Edge se não houver cache válida.
-
Se o teu perfil real do Edge estiver locked (por ex. o Edge já está aberto com a tua sessão), podemos cair automaticamente num perfil temporário em headless, para não tocar no teu perfil em uso e evitar erros tipo
"existing browser session".
Isto só acontece seallowTempProfileWhenLocked=true. -
Por omissão tentamos em modo headless (
headless=true).
Se for preciso MFA / prompt de SSO einteractiveFallback=true, fazemos fallback para janela visível (headful) para poderes autenticar manualmente. -
Se
forceTempProfile=true, nunca tocamos no teu perfil real; usamos sempre um perfil temporário limpo. -
Em modo
Debug=true, escrevemos parastderruma linha que explicita o modo de autenticação resolvido, por exemplo:[edgeondemand] resolved edge mode: autoProfile=true headlessFirst=true interactiveFallback=true allowTempWhenLocked=true forceTemp=false useTempProfileInitially=true realUserData="C:\Users\alice\AppData\Local\Microsoft\Edge\User Data" realProfileDir="Default" timeout=3m0sIsto mostra:
- Se vamos tentar headless primeiro.
- Se podemos cair para janela visível.
- Se vamos usar perfil real ou perfil temporário (e porquê).
- Qual o timeout efetivo.
-
siteUrlaponta para o site onde estão as listas (ex.:/sites/LavagensJMR).
Podes substituir ositeUrldo JSON em runtime com--site, sem editar o ficheiro.
Estas flags estão disponíveis em todos os modos:
-config caminho do ficheiro de configuração (default "private.json")
-site override do siteUrl definido no config (opcional)
-clean limpa a cache de cookies antes (força novo login)
-clean-output remove campos internos que começam por "__" do output final
-mode operação:
list-items | latest-item | get-item | add-item | update-item | delete-item
-http-timeout timeout em segundos por pedido HTTP individual (default 30)
-global-timeout timeout global da operação (segundos) para leituras longas/paginadas.
0 = sem limite global (scan completo permitido)
-list nome/título da lista SharePoint (ex.: "tblRegistos")
-id ID numérico do item (get-item / update-item / delete-item)
-select OData $select (ex.: "Id,Matricula,Operador,DataHora")
-filter OData $filter (ex.: "Matricula eq '57RT01'")
-where sinónimo amigável de --filter (mesma coisa)
-orderby OData $orderby (ex.: "ID desc" ou "DataHora asc")
-sort sinónimo amigável de --orderby
-top limite máximo de itens a devolver
-all se true, percorre TODAS as páginas ($skiptoken)
-latest-only se true, devolve só o item mais recente (maior ID)
-latest sinónimo de --latest-only
-fields em add-item/update-item:
"Campo1=Valor1,Campo2=Valor2,..."
-output formato de saída:
json | jsonl | csv
-summary imprime resumo técnico no stderr no fim
-quiet reduz verbosidade mesmo que o config tenha Debug=true
-
--http-timeout
Máximo que cada pedido HTTP individual pode demorar. -
--global-timeout
Tempo máximo total da operação como um todo, por exemplo:- um dump gigante com
--allque percorre centenas de páginas, - um scan de uma lista de 80k+ registos.
Se este tempo expirar:
- a leitura é interrompida,
- devolvemos tudo o que já tínhamos lido até esse momento,
- e marcamos isso no resumo (
partial=truee normalmentestoppedEarly=true).
- um dump gigante com
-
--global-timeout 0
Desativa esse limite global.
Útil para dumps completos de listas muito grandes (ex.: recolher 80k+ linhas).
Lê vários itens de uma lista.
Exemplo básico:
go run ./cmd/sharepoint-client \
--mode list-items \
--list tblRegistos \
--select "Id,Matricula,Operador,DataHora" \
--top 5 \
--clean-output \
--output json \
--summaryComportamentos importantes:
-
Se usares
--top Ne não deres--orderby, o cliente força internamenteID desc.
→ Recebes logo os N registos mais recentes (ID mais alto primeiro).
Exemplo: "dá-me os 5 últimos registos". -
Se deres
--orderby, usamos exatamente o que definiste
(por ex.--orderby "DataHora asc"para mais antigos primeiro). -
--filter/--whereaplicam um$filterOData. -
--allvarre TODAS as páginas via$skiptokenaté:- não haver mais páginas,
- ou atingir
--top, - ou bater timeout global.
Exemplos úteis:
Últimos 5 registos mais recentes (ordem automática ID desc):
go run ./cmd/sharepoint-client \
--mode list-items \
--list tblRegistos \
--select "Id,Matricula,Operador,DataHora" \
--top 5Mais antigos primeiro (ordenação explícita):
go run ./cmd/sharepoint-client \
--mode list-items \
--list tblRegistos \
--select "Id,Matricula,Operador,DataHora" \
--orderby "DataHora asc" \
--top 5Filtrar pela matrícula:
go run ./cmd/sharepoint-client \
--mode list-items \
--list tblRegistos \
--filter "Matricula eq '57RT01'" \
--select "Id,Matricula,Operador,DataHora" \
--top 5Dump completo de uma matrícula específica (pode ler centenas de páginas):
go run ./cmd/sharepoint-client \
--mode list-items \
--list tblRegistos \
--filter "Matricula eq '57RT01'" \
--select "Id,Matricula,Operador,DataHora" \
--all \
--clean-output \
--output json \
--summary > dump.jsonAtalho para “dá-me o registo mais recente que corresponde ao filtro”.
Internamente:
- força comportamento equivalente a
--allpara garantir varrimento suficiente, - força
LatestOnly=true, - se não houver
--orderby, assumeID descpara encontrar rapidamente o maior ID.
Exemplo:
go run ./cmd/sharepoint-client \
--mode latest-item \
--list tblRegistos \
--where "Matricula eq '57RT01'" \
--select "Id,Matricula,Operador,DataHora" \
--clean-output \
--output json \
--summarySaída típica:
{
"DataHora": "2025-10-28T13:42:38Z",
"ID": 86442,
"Id": 86442,
"Matricula": "57RT01",
"Operador": "1006371"
}Lê exatamente um item pelo ID interno da lista SharePoint.
go run ./cmd/sharepoint-client \
--mode get-item \
--list tblRegistos \
--id 86442 \
--select "Id,Matricula,Operador,DataHora" \
--clean-output \
--output jsonCria um novo registo. Os campos são fornecidos em --fields no formato chave=valor.
go run ./cmd/sharepoint-client \
--mode add-item \
--list tblRegTestes \
--fields "Matricula=ABCD01,Operador=999999,Zona=JMR Centro,Site=Azambuja,DataHora=2025-10-28T19:08:38Z" \
--select "Id,Matricula,Operador,DataHora,Zona,Site" \
--clean-output \
--output jsonComo funciona internamente:
- Faz o
POSTpara criar o item. - Lê de volta esse item pelo ID atribuído.
- Aplica o
--selectque deste. - Aplica
--clean-outputse pediste. - Mostra o estado final (real) do item, não apenas o payload enviado.
Se não deres --select, devolvemos todos os campos que o SharePoint retorna no GET, incluindo campos técnicos tipo ContentTypeId, ParentList, etc.
Nota: A API REST pode aceitar criar itens mesmo sem certos campos que a UI do SharePoint considera “obrigatórios”. Ou seja, a validação da API pode ser menos rígida do que a do formulário web.
Atualiza campos num item existente (PATCH/MERGE).
go run ./cmd/sharepoint-client \
--mode update-item \
--list tblRegTestes \
--id 2324 \
--fields "Operador=123456" \
--select "Id,Matricula,Operador,DataHora,Zona,Site" \
--clean-output \
--output jsonApós atualizar:
- Fazemos o PATCH/MERGE.
- Lemos novamente o item atualizado com GET.
- Aplicamos
--select. - Devolvemos o estado final atual.
Sem --select, devolvemos todos os campos retornados pelo SharePoint no GET.
Apaga um item concreto:
go run ./cmd/sharepoint-client \
--mode delete-item \
--list tblRegTestes \
--id 2321Saída:
{
"deleted": true,
"id": 2321
}--filter e --where são equivalentes e vão direto para OData $filter.
Exemplo típico:
--filter "Matricula eq '57RT01'"Isto também é importante para o fallback anti-throttle:
- Se o filtro for exatamente do tipo
Campo eq 'Valor'(uma única comparação simples), o cliente sabe aplicar esse filtro localmente em memória se o SharePoint recusar a query por throttling. - Se o filtro for complexo (ex.:
Campo1 eq 'X' and Campo2 gt 123), esse fallback local pode não ser possível.
Controla o OData $orderby.
Exemplos:
--orderby "Id desc"
--orderby "DataHora asc"Se não especificares --orderby e deres --top N, o cliente assume ID desc internamente.
Isto significa: “devolve logo os últimos registos”, mesmo que não peças explicitamente --orderby.
-
Sem
--all:- Lê até
Nresultados e pára cedo. - O summary vai indicar
topSatisfied=true(parou porque já tinha dados suficientes).
- Lê até
-
Com
--all:--toppassa a significar “limite máximo TOTAL de itens a devolver”.- Útil para dizer: “varre a lista inteira mas só preciso dos primeiros 200 que correspondem ao filtro; não vale a pena continuar”.
- Se esse limite for atingido, a recolha termina cedo e
topSatisfied=true.
Formato default. Saída indentada e legível.
Se houver apenas 1 item relevante (por exemplo get-item, latest-item ou list-items que devolveu só um), devolvemos um único objeto {...} em vez de um array [ {...} ].
Cada registo numa linha separada, estilo log, sem indentação.
Exemplo:
{"Id":86442,"Matricula":"57RT01","Operador":"1006371","DataHora":"2025-10-28T13:42:38Z"}
{"Id":86272,"Matricula":"57RT01","Operador":"1006371","DataHora":"2025-10-27T10:15:51Z"}
{"Id":86215,"Matricula":"57RT01","Operador":"1050429","DataHora":"2025-10-26T01:29:01Z"}
Isto é perfeito para grep, jq -c, etc, porque cada linha é JSON completo.
Gera CSV:
- Se usares
--select, a ordem das colunas segue exatamente o--select. - Se não usares
--select, usamos as chaves do primeiro item ordenadas alfabeticamente (para estabilidade).
Remove todas as chaves que começam por "__" (por exemplo "__metadata"), reduzindo ruído OData interno.
Não remove outros campos técnicos que não começam por "__".
- O conteúdo "útil" (registos, JSON final, CSV, etc.) sai sempre em stdout.
- Logs técnicos e diagnóstico (
[edgeondemand],[sp][req],[summary], erros HTTP detalhados) vão em stderr.
Isto significa que podes redirecionar as duas coisas separadamente, por exemplo em PowerShell:
go run ./cmd/sharepoint-client `
--mode list-items `
--list tblRegistos `
--top 5 `
--select "Id,Matricula,Operador,DataHora" `
--clean-output `
--summary `
1> output.json 2> debug.logoutput.jsonfica com os dados em JSON (stdout).debug.logfica com requests/responses, summary e info de autenticação (stderr).
SharePoint Online pode rejeitar queries com muitos resultados (“excede o limiar da vista de lista”), devolvendo um erro SPQueryThrottledException.
O cliente tenta contornar isso automaticamente.
- Faz a query exatamente como pediste (
$filter,$orderby,$top). - Se o SharePoint responder com throttle:
- Se o teu filtro for simples do tipo
Campo eq 'valor':- Faz uma segunda query sem filtro no servidor, em blocos grandes (tipicamente 200 items).
- Filtra localmente em memória (
Campo == valor). - Aplica
--tope/ouLatestOnlylocalmente. - Marca
fallback=trueno summary.
- Se o filtro for complexo (por ex.
Campo1 eq 'X' and Campo2 gt 123), esse fallback local pode não ser seguro → devolvemos erro.
- Se o teu filtro for simples do tipo
Resultado prático:
Mesmo listas de ~80k linhas podem devolver rapidamente só os registos daquela matrícula específica.
--all faz paginação com $skiptoken e percorre TODAS as páginas, potencialmente centenas.
Com --all, temos tolerância a:
- throttling a meio,
- cancels/timeout global,
- limitação por
--top.
Comportamento:
- Se houver throttling a meio do percurso, paramos, marcamos
partial=true, devolvemos o que já tínhamos apanhado até ali (sem erro fatal). - Se houver throttling logo na primeira página e o filtro é simples
Campo eq 'valor':- ativamos varrimento "fallback": pedimos páginas sem filtro ao servidor, filtramos localmente página a página (client-side), e continuamos a tentar varrer.
- Isto marca
fallback=true.
É possível ver partial=true mas, na prática, já ter capturado todos os registos que interessam.
Exemplo real: havia ~140 registos com Matricula eq '57RT01'.
A leitura parou “cedo” em termos de percorrer a lista globalmente, mas já tínhamos esses ~140 registos.
Tecnicamente partial=true porque não percorremos a lista inteira, mas o resultado útil estava completo.
Se definires --global-timeout com um valor >0:
- Se a operação demorar mais que esse limite (por exemplo: dump de 80k linhas com
--all), interrompemos. - Devolvemos o que já tínhamos até esse momento.
partial=true.stoppedEarly=true.
Se usares --global-timeout 0:
- Não há limite global de tempo.
- Bom para dumps totais.
- Se terminar sem interrupções:
partial=falsestoppedEarly=false
Se usares --summary, no fim imprimimos em stderr algo como:
[summary] items=125 pages=392 throttled=true partial=true fallback=true stoppedEarly=true topSatisfied=false
Significado de cada campo:
-
items
Quantos itens foram devolvidos nostdoutfinal. -
pages
Quantas páginas pedimos ao SharePoint (inclui chamadas paginadas via$skiptoken). -
throttled
Em algum momento o SharePoint respondeu comSPQueryThrottledException(limite de view / restrição de carga). -
fallback
true se tivemos de usar a estratégia de fallback:- pedir páginas sem filtro server-side,
- filtrar nós próprios em memória (ex.:
Campo eq 'Valor'), - e/ou usar pesquisa local para encontrar o “mais recente”.
-
partial
true se não percorremos a lista até ao fim natural.Isto pode acontecer por vários motivos:
- apanhámos throttling a meio e parámos em vez de falhar,
- atingimos
--global-timeoute interrompemos, - parámos cedo porque já reunimos resultados suficientes.
Atenção:
partial=trueNÃO quer automaticamente dizer “faltam resultados do teu filtro”.
Pode simplesmente querer dizer “decidimos não ler o resto da lista inteira porque já chegava”. -
stoppedEarly
true se terminámos a recolha antes do fim completo por um motivo externo (timeout global, throttling forte, etc.).Em termos práticos:
stoppedEarly=truequase sempre significa que houve interrupção forçada.stoppedEarly=falsesignifica que ou percorremos tudo, ou parámos apenas porque já tinhas--topsatisfeito.
-
topSatisfied
true se (na perspetiva do cliente) o pedido foi cumprido.Exemplos:
- Pediste
--top 5: apanhámos 5 →topSatisfied=true. - Pediste todos os IDs (
--all --global-timeout 0) e lemos a lista inteira →topSatisfied=true. - Pediste uma matrícula rara, e lemos o suficiente para obter todos os registos dessa matrícula antes de desistir → muitas vezes continua
topSatisfied=true.
Já
topSatisfied=falseaparece em cenários tipo:- Pediste
--allfiltrado e sabíamos (pelo SharePoint) que existiam mais páginas para percorrer, mas parámos cedo por throttling/timeout antes de as ler.
- Pediste
Quando algo corre mal (por exemplo --id não existe, ou o SharePoint devolve 404), o comando:
- escreve um objeto JSON de erro em stdout, com estrutura estável,
- sai com código
1.
Exemplo real de get-item para um ID inexistente:
{
"ok": false,
"mode": "get-item",
"error": "get-item falhou: unable to request api: 404 Not Found :: {\"error\":{\"code\":\"-2130575338, System.ArgumentException\",\"message\":{\"lang\":\"pt-PT\",\"value\":\"O item não existe. Pode ter sido eliminado por outro utilizador.\"}}}"
}Campos:
ok: será semprefalse.mode: o modo que falhou (get-item,update-item, etc.).error: mensagem detalhada, normalmente inclui o status code do SharePoint.
Isto facilita automação porque consegues inspecionar mode e error direto em JSON sem teres de fazer parse do texto do stderr.
Nota: o
stderrcontinua a ter logs técnicos ([sp][req],[summary],[edgeondemand]...), mas o objeto JSON de erro está emstdout. Ou seja, para scripts basta testares o exit code.
go run ./cmd/sharepoint-client \
--mode list-items \
--list tblRegistos \
--select "Id,Matricula,Operador,DataHora" \
--top 5 \
--clean-output \
--output json \
--summaryNota: se não deres --orderby, o cliente força ID desc internamente.
go run ./cmd/sharepoint-client \
--mode latest-item \
--list tblRegistos \
--where "Matricula eq '57RT01'" \
--select "Id,Matricula,Operador,DataHora" \
--clean-output \
--output json \
--summarygo run ./cmd/sharepoint-client \
--mode list-items \
--list tblRegistos \
--filter "Matricula eq '57RT01'" \
--select "Id,Matricula,Operador,DataHora" \
--all \
--clean-output \
--output json \
--summary > dump.jsongo run ./cmd/sharepoint-client \
--mode list-items \
--list tblRegistos \
--select "Id" \
--all \
--clean-output \
--output json \
--global-timeout 0 \
--summary > dump.jsonIsto tenta ler absolutamente tudo até ao fim da lista, sem limite de tempo global.
go run ./cmd/sharepoint-client \
--mode add-item \
--list tblRegTestes \
--fields "Matricula=ABCD01,Operador=999999,Zona=JMR Centro,Site=Azambuja,DataHora=2025-10-28T19:08:38Z" \
--select "Id,Matricula,Operador,DataHora,Zona,Site" \
--clean-output \
--output jsongo run ./cmd/sharepoint-client \
--mode update-item \
--list tblRegTestes \
--id 2324 \
--fields "Operador=123456" \
--select "Id,Matricula,Operador,DataHora,Zona,Site" \
--clean-output \
--output jsongo run ./cmd/sharepoint-client \
--mode delete-item \
--list tblRegTestes \
--id 2321Saída:
{
"deleted": true,
"id": 2321
}Esta versão do sharepoint-client oferece:
-
Leitura robusta
Inclui paginação$skiptokencom--alle tolerância a throttling. -
Fallback inteligente ao throttling
Se o SharePoint recusar uma query filtrada (SPQueryThrottledException), tentamos novamente sem filtro no servidor e filtramos nós mesmos em memória, sempre que possível (Campo eq 'valor'). -
Controlo de timeouts
--http-timeout(por pedido) e--global-timeout(para a operação inteira).
--global-timeout 0significa "vai até ao fim custe o que custar". -
Saídas fáceis de consumir
--output json,--output jsonl(1 linha por registo), ou--output csv.
--clean-outputremove ruído interno OData (__metadata, etc.). -
Pós-leitura automática em add/update
add-itemeupdate-itemfazem GET depois da operação e devolvem o estado real atualizado do item.
Se passares--select, já recebes só os campos que te interessam. -
Comportamento "últimos N registos" pronto a usar
Se pedes--top Nsem--orderby, o cliente assumeID descpara te devolver logo os registos mais recentes. -
Resumo técnico (
--summary) claro
Mostra: nº de páginas pedidas, throttling, se houve fallback, se a listagem terminou cedo por--top, se houve timeout global, etc.
Campos comopartial,stoppedEarlyetopSatisfiedajudam-te a perceber se tens dados completos ou parciais, e porquê. -
Erros consistentes em JSON
Em caso de erro devolvemos{ "ok": false, "mode": "...", "error": "..." }em stdout e saímos com exit code 1.
Fácil de automatizar em scripts.
Em resumo: dá-te uma forma estável, previsível e scriptável de interagir com listas do SharePoint Online, mesmo quando essas listas são grandes e o SharePoint começa a fazer throttle.