captador_de_datos_a_sqlite
Diferencias
Muestra las diferencias entre dos versiones de la página.
| Ambos lados, revisión anteriorRevisión previaPróxima revisión | Revisión previa | ||
| captador_de_datos_a_sqlite [2026/09/09 07:08] – hispa | captador_de_datos_a_sqlite [2026/09/09 07:19] (actual) – hispa | ||
|---|---|---|---|
| Línea 22: | Línea 22: | ||
| * Librería Externa: libsqlite3.so.0 (en entornos UNIX/Linux) o sqlite3.dll (en entornos Windows). | * Librería Externa: libsqlite3.so.0 (en entornos UNIX/Linux) o sqlite3.dll (en entornos Windows). | ||
| * En entornos Linux, ejecución mediante un programador de tareas como Cron. También es posible crear un disparador como servicio en systemd que ejecute el programa a intervalos definidos. | * En entornos Linux, ejecución mediante un programador de tareas como Cron. También es posible crear un disparador como servicio en systemd que ejecute el programa a intervalos definidos. | ||
| + | </ | ||
| + | |||
| + | <code pascal> | ||
| + | program captadormedidasmastodon; | ||
| + | |||
| + | {$mode objfpc}{$H+} | ||
| + | |||
| + | uses | ||
| + | // Soporte para UTF-8 | ||
| + | {$IFDEF UNIX} | ||
| + | cwstring, | ||
| + | {$ENDIF} | ||
| + | // Soporte para Windows | ||
| + | {$IFDEF WINDOWS} | ||
| + | Windows, | ||
| + | {$ENDIF} | ||
| + | // SysUtils Siempre detrás de cwstring para evitar errores | ||
| + | SysUtils, | ||
| + | // Soporte de OOP | ||
| + | Classes, | ||
| + | // Soporte HTTP, SSL y JSON | ||
| + | fphttpclient, | ||
| + | opensslsockets, | ||
| + | fpjson, | ||
| + | jsonparser, | ||
| + | // Procesado de expresiones regulares | ||
| + | RegExpr, | ||
| + | // Soporte SQL y SQLite | ||
| + | sqlite3conn, | ||
| + | sqlite3dyn, | ||
| + | sqldb, | ||
| + | // Soporte de fecha/hora | ||
| + | DateUtils; | ||
| + | |||
| + | const | ||
| + | // Constantes para leer el contenido del usuario bot (@meteo) en Mastodon | ||
| + | INSTANCIA_MASTODON = ' | ||
| + | USUARIO_MASTODON = ' | ||
| + | USER_AGENT = ' | ||
| + | |||
| + | // Directorio para su uso en el sobremesa (testing) | ||
| + | // DB_FILE = '/ | ||
| + | // Directorio para su uso en el servidor (definitivo) | ||
| + | DB_FILE = '/ | ||
| + | |||
| + | type | ||
| + | TMastodonPost = record | ||
| + | Content: string; | ||
| + | CreatedAt: string; // ISO 8601 UTC | ||
| + | end; | ||
| + | |||
| + | TDatosClima = record | ||
| + | Temperatura: | ||
| + | Presion: Double; | ||
| + | Humedad: Double; | ||
| + | end; | ||
| + | |||
| + | { ============================================================================ } | ||
| + | { MÓDULO DE RED Y API MASTODON | ||
| + | { ============================================================================ } | ||
| + | |||
| + | // Realiza una petición GET HTTPS configurando el User-Agent | ||
| + | function HttpGet(const URL: string): string; | ||
| + | var | ||
| + | Client: TFPHTTPClient = nil; | ||
| + | begin | ||
| + | Result := ''; | ||
| + | try | ||
| + | Client := TFPHTTPClient.Create(nil); | ||
| + | Client.AddHeader(' | ||
| + | Result := Client.Get(URL); | ||
| + | finally | ||
| + | Client.Free; | ||
| + | end; | ||
| + | end; | ||
| + | |||
| + | // Obtiene el ID numérico interno de un usuario de Mastodon | ||
| + | function ObtenerAccountId(const Instancia, Usuario: string): string; | ||
| + | var | ||
| + | URL, Response: string; | ||
| + | JsonData: TJSONData = nil; | ||
| + | IdNode: TJSONData = nil; | ||
| + | begin | ||
| + | Result := ''; | ||
| + | URL := Format(' | ||
| + | try | ||
| + | Response := HttpGet(URL); | ||
| + | JsonData := GetJSON(Response); | ||
| + | if Assigned(JsonData) then | ||
| + | begin | ||
| + | IdNode := JsonData.FindPath(' | ||
| + | if Assigned(IdNode) then | ||
| + | Result := IdNode.AsString; | ||
| + | end; | ||
| + | except | ||
| + | on E: Exception do | ||
| + | WriteLn(' | ||
| + | end; | ||
| + | JsonData.Free; | ||
| + | end; | ||
| + | |||
| + | // Obtiene el contenido y fecha del último estado publicado por la cuenta | ||
| + | function ObtenerUltimoPost(const Instancia, AccountId: string; out Post: TMastodonPost): | ||
| + | var | ||
| + | URL, Response: string; | ||
| + | JsonData: TJSONData = nil; | ||
| + | JsonArray: TJSONArray; | ||
| + | PostObj: TJSONObject; | ||
| + | begin | ||
| + | Result := False; | ||
| + | Post.Content := ''; | ||
| + | Post.CreatedAt := ''; | ||
| + | |||
| + | URL := Format(' | ||
| + | try | ||
| + | Response := HttpGet(URL); | ||
| + | JsonData := GetJSON(Response); | ||
| + | if (JsonData is TJSONArray) and (TJSONArray(JsonData).Count > 0) then | ||
| + | begin | ||
| + | JsonArray := TJSONArray(JsonData); | ||
| + | PostObj | ||
| + | |||
| + | Post.Content | ||
| + | Post.CreatedAt := PostObj.Get(' | ||
| + | Result := True; | ||
| + | end; | ||
| + | except | ||
| + | on E: Exception do | ||
| + | WriteLn(' | ||
| + | end; | ||
| + | JsonData.Free; | ||
| + | end; | ||
| + | |||
| + | { ============================================================================ } | ||
| + | { MÓDULO DE TRATAMIENTO DE TEXTO Y VALORES METEOROLÓGICOS | ||
| + | { ============================================================================ } | ||
| + | |||
| + | // Remueve etiquetas HTML y espacios múltiples sobrantes | ||
| + | function LimpiarTextoHTML(const TextoHTML: string): string; | ||
| + | var | ||
| + | Regex: TRegExpr = nil; | ||
| + | begin | ||
| + | try | ||
| + | Regex := TRegExpr.Create; | ||
| + | Regex.Expression := '< | ||
| + | Result := Regex.Replace(TextoHTML, | ||
| + | |||
| + | // Normalizar espacios múltiples a uno solo | ||
| + | Regex.Expression := ' | ||
| + | Result := Trim(Regex.Replace(Result, | ||
| + | finally | ||
| + | Regex.Free; | ||
| + | end; | ||
| + | end; | ||
| + | |||
| + | // Convierte fecha ISO 8601 UTC a hora local (formato YYYY-MM-DD HH:NN:SS) | ||
| + | function UTCaFechaLocal(const FechaUTC: string): string; | ||
| + | var | ||
| + | DT_UTC, DT_Local: TDateTime; | ||
| + | begin | ||
| + | try | ||
| + | DT_UTC := ISO8601ToDate(FechaUTC, | ||
| + | DT_Local := UniversalTimeToLocal(DT_UTC); | ||
| + | Result := FormatDateTime(' | ||
| + | except | ||
| + | Result := FechaUTC; | ||
| + | end; | ||
| + | end; | ||
| + | |||
| + | // Extrae valor numérico mediante Regex independientemente del separador (coma/ | ||
| + | function ExtraerValor(const Texto, Patron: string): Double; | ||
| + | var | ||
| + | Regex: TRegExpr = nil; | ||
| + | ValStr: string; | ||
| + | FS: TFormatSettings; | ||
| + | begin | ||
| + | Result := 0.0; | ||
| + | |||
| + | // Configuración de formato independiente del locale | ||
| + | FS := DefaultFormatSettings; | ||
| + | FS.DecimalSeparator := ' | ||
| + | |||
| + | try | ||
| + | Regex := TRegExpr.Create; | ||
| + | Regex.ModifierI := True; // Insensible a mayúsculas | ||
| + | Regex.Expression := Patron; | ||
| + | |||
| + | if Regex.Exec(Texto) then | ||
| + | begin | ||
| + | ValStr := StringReplace(Regex.Match[1], | ||
| + | Result := StrToFloatDef(ValStr, | ||
| + | end; | ||
| + | finally | ||
| + | Regex.Free; | ||
| + | end; | ||
| + | end; | ||
| + | |||
| + | // Parsea las tres magnitudes principales del post | ||
| + | function ParsearDatosClima(const Texto: string): TDatosClima; | ||
| + | begin | ||
| + | Result.Temperatura := ExtraerValor(Texto, | ||
| + | Result.Presion | ||
| + | Result.Humedad | ||
| + | end; | ||
| + | |||
| + | { ============================================================================ } | ||
| + | { MÓDULO DE BASE DE DATOS (SQLITE) | ||
| + | { ============================================================================ } | ||
| + | |||
| + | // Prepara la conexión SQL asegurando el estado abierto | ||
| + | procedure AbrirConexion(Conn: | ||
| + | begin | ||
| + | if Conn.DatabaseName = '' | ||
| + | Conn.DatabaseName := DB_FILE; | ||
| + | if not Conn.Connected then | ||
| + | Conn.Open; | ||
| + | end; | ||
| + | |||
| + | // Crea la estructura de tablas inicial | ||
| + | procedure InicializarBaseDatos(Conn: | ||
| + | var | ||
| + | Query: TSQLQuery = nil; | ||
| + | begin | ||
| + | AbrirConexion(Conn); | ||
| + | Trans.StartTransaction; | ||
| + | |||
| + | try | ||
| + | Query := TSQLQuery.Create(nil); | ||
| + | Query.DataBase := Conn; | ||
| + | Query.Transaction := Trans; | ||
| + | Query.SQL.Text := | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | Query.ExecSQL; | ||
| + | Trans.Commit; | ||
| + | except | ||
| + | if Trans.Active then | ||
| + | Trans.Rollback; | ||
| + | Query.Free; | ||
| + | raise; | ||
| + | end; | ||
| + | Query.Free; | ||
| + | end; | ||
| + | |||
| + | // Devuelve la última fecha registrada en SQLite o cadena vacía si no hay registros | ||
| + | function ObtenerUltimaFechaBD(Conn: | ||
| + | var | ||
| + | Query: TSQLQuery = nil; | ||
| + | begin | ||
| + | Result := ''; | ||
| + | AbrirConexion(Conn); | ||
| + | |||
| + | try | ||
| + | Query := TSQLQuery.Create(nil); | ||
| + | Query.DataBase := Conn; | ||
| + | Query.Transaction := Trans; | ||
| + | Query.SQL.Text := ' | ||
| + | Query.Open; | ||
| + | |||
| + | if not Query.EOF and not Query.Fields[0].IsNull then | ||
| + | Result := Query.Fields[0].AsString; | ||
| + | finally | ||
| + | Query.Free; | ||
| + | end; | ||
| + | end; | ||
| + | |||
| + | // Guarda los valores parseados en la base de datos | ||
| + | procedure GuardarLectura(Conn: | ||
| + | const Fecha: string; const Datos: TDatosClima); | ||
| + | var | ||
| + | Query: TSQLQuery = nil; | ||
| + | begin | ||
| + | AbrirConexion(Conn); | ||
| + | Trans.StartTransaction; | ||
| + | |||
| + | try | ||
| + | Query := TSQLQuery.Create(nil); | ||
| + | Query.DataBase := Conn; | ||
| + | Query.Transaction := Trans; | ||
| + | Query.SQL.Text := | ||
| + | ' | ||
| + | ' | ||
| + | |||
| + | Query.Params.ParamByName(' | ||
| + | Query.Params.ParamByName(' | ||
| + | Query.Params.ParamByName(' | ||
| + | Query.Params.ParamByName(' | ||
| + | |||
| + | Query.ExecSQL; | ||
| + | Trans.Commit; | ||
| + | WriteLn(' | ||
| + | except | ||
| + | if Trans.Active then | ||
| + | Trans.Rollback; | ||
| + | Query.Free; | ||
| + | raise; | ||
| + | end; | ||
| + | Query.Free; | ||
| + | end; | ||
| + | |||
| + | { ============================================================================ } | ||
| + | { BLOQUE PRINCIPAL | ||
| + | { ============================================================================ } | ||
| + | |||
| + | var | ||
| + | Connection: TSQLite3Connection = nil; | ||
| + | Transaction: | ||
| + | AccountId, UltimaFechaBD, | ||
| + | Post: TMastodonPost; | ||
| + | Clima: TDatosClima; | ||
| + | |||
| + | begin | ||
| + | {$IFDEF UNIX} | ||
| + | SQLiteDefaultLibrary := ' | ||
| + | {$ENDIF} | ||
| + | {$IFDEF WINDOWS} | ||
| + | SQLiteDefaultLibrary := ' | ||
| + | {$ENDIF} | ||
| + | |||
| + | Connection := TSQLite3Connection.Create(nil); | ||
| + | Transaction := TSQLTransaction.Create(nil); | ||
| + | Connection.Transaction := Transaction; | ||
| + | |||
| + | try | ||
| + | WriteLn(' | ||
| + | InicializarBaseDatos(Connection, | ||
| + | |||
| + | WriteLn(Format(' | ||
| + | AccountId := ObtenerAccountId(INSTANCIA_MASTODON, | ||
| + | |||
| + | if AccountId = '' | ||
| + | begin | ||
| + | WriteLn(' | ||
| + | Exit; | ||
| + | end; | ||
| + | |||
| + | WriteLn(' | ||
| + | if not ObtenerUltimoPost(INSTANCIA_MASTODON, | ||
| + | begin | ||
| + | WriteLn(' | ||
| + | Exit; | ||
| + | end; | ||
| + | |||
| + | TextoLimpio := LimpiarTextoHTML(Post.Content); | ||
| + | FechaLocal | ||
| + | |||
| + | WriteLn(' | ||
| + | WriteLn(' | ||
| + | WriteLn(' | ||
| + | |||
| + | UltimaFechaBD := ObtenerUltimaFechaBD(Connection, | ||
| + | |||
| + | if UltimaFechaBD <> '' | ||
| + | WriteLn(' | ||
| + | else | ||
| + | WriteLn(' | ||
| + | |||
| + | // Verificar si la fecha actual es posterior a la almacenada | ||
| + | if (UltimaFechaBD = '' | ||
| + | begin | ||
| + | Clima := ParsearDatosClima(TextoLimpio); | ||
| + | |||
| + | WriteLn(Format(' | ||
| + | [Clima.Temperatura, | ||
| + | |||
| + | GuardarLectura(Connection, | ||
| + | end; | ||
| + | |||
| + | finally | ||
| + | Transaction.Free; | ||
| + | Connection.Free; | ||
| + | end; | ||
| + | end. | ||
| + | </ | ||
| + | <WRAP collarge> | ||
| === Estructura de Datos === | === Estructura de Datos === | ||
captador_de_datos_a_sqlite.1788937738.txt.gz · Última modificación: por hispa
