Herramientas de usuario

Herramientas del sitio


captador_de_datos_a_sqlite

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Ambos lados, revisión anteriorRevisión previa
Próxima revisión
Revisión previa
captador_de_datos_a_sqlite [2026/09/09 07:08] hispacaptador_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.
 +</WRAP>
 +
 +<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 = 'social.hispa.net';
 +  USUARIO_MASTODON = 'meteo';
 +  USER_AGENT = 'MastodonWeatherReader/1.0 (FreePascal)';
 +
 +  // Directorio para su uso en el sobremesa (testing)
 +  // DB_FILE = '/home/hispa/Nextcloud/Data_1/Documentos/Jorge/Programas/Pascal/captador_medidas_mastodon/clima.db';
 +  // Directorio para su uso en el servidor (definitivo)
 +  DB_FILE = '/media/Data_1/Documentos/Jorge/Programas/Pascal/captador_medidas_mastodon/clima.db';
 +
 +type
 +  TMastodonPost = record
 +    Content: string;
 +    CreatedAt: string; // ISO 8601 UTC
 +  end;
 +
 +  TDatosClima = record
 +    Temperatura: Double;
 +    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('User-Agent', USER_AGENT);
 +    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('https://%s/api/v1/accounts/lookup?acct=%s', [Instancia, Usuario]);
 +  try
 +    Response := HttpGet(URL);
 +    JsonData := GetJSON(Response);
 +    if Assigned(JsonData) then
 +    begin
 +      IdNode := JsonData.FindPath('id');
 +      if Assigned(IdNode) then
 +        Result := IdNode.AsString;
 +    end;
 +  except
 +    on E: Exception do
 +      WriteLn('[Error HTTP/JSON] Al buscar usuario: ', E.Message);
 +  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): Boolean;
 +var
 +  URL, Response: string;
 +  JsonData: TJSONData = nil;
 +  JsonArray: TJSONArray;
 +  PostObj: TJSONObject;
 +begin
 +  Result := False;
 +  Post.Content := '';
 +  Post.CreatedAt := '';
 +
 +  URL := Format('https://%s/api/v1/accounts/%s/statuses?limit=1', [Instancia, AccountId]);
 +  try
 +    Response := HttpGet(URL);
 +    JsonData := GetJSON(Response);
 +    if (JsonData is TJSONArray) and (TJSONArray(JsonData).Count > 0) then
 +    begin
 +      JsonArray := TJSONArray(JsonData);
 +      PostObj   := JsonArray.Objects[0];
 +
 +      Post.Content   := PostObj.Get('content', '');
 +      Post.CreatedAt := PostObj.Get('created_at', '');
 +      Result := True;
 +    end;
 +  except
 +    on E: Exception do
 +      WriteLn('[Error HTTP/JSON] Al obtener publicaciones: ', E.Message);
 +  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, ' ', False);
 +
 +    // Normalizar espacios múltiples a uno solo
 +    Regex.Expression := '\s+';
 +    Result := Trim(Regex.Replace(Result, ' ', False));
 +  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, True);
 +    DT_Local := UniversalTimeToLocal(DT_UTC);
 +    Result := FormatDateTime('yyyy-mm-dd hh:nn:ss', DT_Local);
 +  except
 +    Result := FechaUTC;
 +  end;
 +end;
 +
 +// Extrae valor numérico mediante Regex independientemente del separador (coma/punto)
 +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], ',', '.', [rfReplaceAll]);
 +      Result := StrToFloatDef(ValStr, 0.0, FS);
 +    end;
 +  finally
 +    Regex.Free;
 +  end;
 +end;
 +
 +// Parsea las tres magnitudes principales del post
 +function ParsearDatosClima(const Texto: string): TDatosClima;
 +begin
 +  Result.Temperatura := ExtraerValor(Texto, '(?:temp|temperatura)[^\d]*(-?\d+(?:[\.,]\d+)?)');
 +  Result.Presion     := ExtraerValor(Texto, '(?:presi|barom)[^\d]*(\d+(?:[\.,]\d+)?)');
 +  Result.Humedad     := ExtraerValor(Texto, '(?:hum|humedad)[^\d]*(\d+(?:[\.,]\d+)?)');
 +end;
 +
 +{ ============================================================================ }
 +{ MÓDULO DE BASE DE DATOS (SQLITE)                                             }
 +{ ============================================================================ }
 +
 +// Prepara la conexión SQL asegurando el estado abierto
 +procedure AbrirConexion(Conn: TSQLite3Connection);
 +begin
 +  if Conn.DatabaseName = '' then
 +    Conn.DatabaseName := DB_FILE;
 +  if not Conn.Connected then
 +    Conn.Open;
 +end;
 +
 +// Crea la estructura de tablas inicial
 +procedure InicializarBaseDatos(Conn: TSQLite3Connection; Trans: TSQLTransaction);
 +var
 +  Query: TSQLQuery = nil;
 +begin
 +  AbrirConexion(Conn);
 +  Trans.StartTransaction;
 +
 +  try
 +    Query := TSQLQuery.Create(nil);
 +    Query.DataBase := Conn;
 +    Query.Transaction := Trans;
 +    Query.SQL.Text :=
 +      'CREATE TABLE IF NOT EXISTS mediciones (' +
 +      '  id INTEGER PRIMARY KEY AUTOINCREMENT,' +
 +      '  fecha TEXT NOT NULL,' +
 +      '  temperatura REAL,' +
 +      '  presion REAL,' +
 +      '  humedad REAL' +
 +      ');';
 +    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: TSQLite3Connection; Trans: TSQLTransaction): string;
 +var
 +  Query: TSQLQuery = nil;
 +begin
 +  Result := '';
 +  AbrirConexion(Conn);
 +
 +  try
 +    Query := TSQLQuery.Create(nil);
 +    Query.DataBase := Conn;
 +    Query.Transaction := Trans;
 +    Query.SQL.Text := 'SELECT MAX(fecha) FROM mediciones;';
 +    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: TSQLite3Connection; Trans: TSQLTransaction;
 +  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 :=
 +      'INSERT INTO mediciones (fecha, temperatura, presion, humedad) ' +
 +      'VALUES (:fecha, :temp, :pres, :hum);';
 +
 +    Query.Params.ParamByName('fecha').AsString := Fecha;
 +    Query.Params.ParamByName('temp').AsFloat   := Datos.Temperatura;
 +    Query.Params.ParamByName('pres').AsFloat   := Datos.Presion;
 +    Query.Params.ParamByName('hum').AsFloat    := Datos.Humedad;
 +
 +    Query.ExecSQL;
 +    Trans.Commit;
 +    WriteLn('-> Nueva medida guardada con éxito.');
 +  except
 +    if Trans.Active then
 +      Trans.Rollback;
 +    Query.Free;
 +    raise;
 +  end;
 +  Query.Free;
 +end;
 +
 +{ ============================================================================ }
 +{ BLOQUE PRINCIPAL                                                             }
 +{ ============================================================================ }
 +
 +var
 +  Connection: TSQLite3Connection = nil;
 +  Transaction: TSQLTransaction = nil;
 +  AccountId, UltimaFechaBD, FechaLocal, TextoLimpio: string;
 +  Post: TMastodonPost;
 +  Clima: TDatosClima;
 +
 +begin
 +  {$IFDEF UNIX}
 +    SQLiteDefaultLibrary := 'libsqlite3.so.0';
 +  {$ENDIF}
 +  {$IFDEF WINDOWS}
 +    SQLiteDefaultLibrary := 'sqlite3.dll';
 +  {$ENDIF}
 +
 +  Connection := TSQLite3Connection.Create(nil);
 +  Transaction := TSQLTransaction.Create(nil);
 +  Connection.Transaction := Transaction;
 +
 +  try
 +    WriteLn('Base de datos: ', DB_FILE);
 +    InicializarBaseDatos(Connection, Transaction);
 +
 +    WriteLn(Format('Buscando cuenta "@%s@%s"...', [USUARIO_MASTODON, INSTANCIA_MASTODON]));
 +    AccountId := ObtenerAccountId(INSTANCIA_MASTODON, USUARIO_MASTODON);
 +
 +    if AccountId = '' then
 +    begin
 +      WriteLn('Error: No se pudo resolver la cuenta del usuario.');
 +      Exit;
 +    end;
 +
 +    WriteLn('Consultando último post...');
 +    if not ObtenerUltimoPost(INSTANCIA_MASTODON, AccountId, Post) then
 +    begin
 +      WriteLn('No se encontraron publicaciones.');
 +      Exit;
 +    end;
 +
 +    TextoLimpio := LimpiarTextoHTML(Post.Content);
 +    FechaLocal  := UTCaFechaLocal(Post.CreatedAt);
 +
 +    WriteLn('Fecha UTC: ', Post.CreatedAt);
 +    WriteLn('Fecha Local: ', FechaLocal);
 +    WriteLn('Contenido: ', TextoLimpio);
 +
 +    UltimaFechaBD := ObtenerUltimaFechaBD(Connection, Transaction);
 +
 +    if UltimaFechaBD <> '' then
 +      WriteLn('Última fecha registrada en BD: ', UltimaFechaBD)
 +    else
 +      WriteLn('Última fecha registrada en BD: Ninguna (BD vacía)');
 +
 +    // Verificar si la fecha actual es posterior a la almacenada
 +    if (UltimaFechaBD = '') or (FechaLocal > UltimaFechaBD) then
 +    begin
 +      Clima := ParsearDatosClima(TextoLimpio);
 +
 +      WriteLn(Format('Extraído -> Temp: %.2f °C | Presión: %.2f hPa | Humedad: %.2f%%',
 +        [Clima.Temperatura, Clima.Presion, Clima.Humedad]));
 +
 +      GuardarLectura(Connection, Transaction, FechaLocal, Clima);
 +    end;
 +
 +  finally
 +    Transaction.Free;
 +    Connection.Free;
 +  end;
 +end.
 +</code>
 +<WRAP collarge>
  
 === Estructura de Datos === === Estructura de Datos ===
captador_de_datos_a_sqlite.1788937738.txt.gz · Última modificación: por hispa

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki