@oboapp/source-nadezhda-org 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Extract the Nadezhda listing and article source into an independently versioned package.
6
+ - Preserve source identity, literal identifiers, detail dates, content cleanup and Markdown through the shared browser helper.
7
+ - Reuse one browser and keep two-second request pacing across listing and detail attempts.
package/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <https://unlicense.org>
package/README.md CHANGED
@@ -1,3 +1,40 @@
1
- # Temporary Holding Version
1
+ # @oboapp/source-nadezhda-org
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Collects the Announcements and notices listing from Sofia district Nadezhda. Chromium reads the listing and each article. The package preserves the existing `nadezhda-org` identity, literal identifiers, detail title/date behavior, content cleanup and Markdown output.
4
+
5
+ ## Install and use
6
+
7
+ ```sh
8
+ pnpm add @oboapp/source-nadezhda-org
9
+ ```
10
+
11
+ Pass the runtime factory and isolated metadata to Obo's scheduled source runner:
12
+
13
+ ```ts
14
+ import { createSource } from "@oboapp/source-nadezhda-org";
15
+ import { metadata } from "@oboapp/source-nadezhda-org/metadata";
16
+ ```
17
+
18
+ The metadata entrypoint can be loaded by the web catalog without loading browser dependencies. Direct SDK consumers call `await createSource({ locality: "bg.sofia" })`, followed by `discoverItems()` and `fetchItem(item)`. Always call `await source.close?.()` in `finally`.
19
+
20
+ Obo owns historical lookups, validation, persistence, scheduling, ingestion and notifications. Updating this package preserves historical identifiers.
21
+
22
+ ## Collection behavior
23
+
24
+ The legacy URL filter accepts the exact `nadezhda.sofia.bg` hostname and paths containing `/news/`. Exact duplicates are combined in their first listing position, matching the existing Map behavior. Query strings, fragments and percent-encoded spelling remain part of the identifier. Discovery returns identifiers only. A listing title is required to select an entry. Detail metadata comes only from the article page.
25
+
26
+ Details require a non-empty page title and content. The detail date uses the existing Bulgarian numeric date conversion, including two-digit years and slash separators. An empty or unparseable detail date falls back to the current time. Cleaned HTML becomes Markdown with document links preserved.
27
+
28
+ One Chromium browser is reused for the scan. Each listing/detail attempt closes its page and waits two seconds, including failed attempts. Image, media and font requests are blocked. Provider failures propagate to Obo, which records item failures and retries missing identifiers on a later scheduled scan.
29
+
30
+ The runtime requires Node.js 20.3 or later and `@oboapp/source-web-utils` with Playwright 1.61.1 and its matching Chromium revision and system libraries. Keep that browser revision aligned with the deployment image.
31
+
32
+ ## Feed and API check
33
+
34
+ The public announcement listing has no alternate feed links or AJAX collection endpoint. Checks of `/feed`, `/rss.xml` and `/atom.xml` did not expose a usable feed on 8 November 2026. Its scripts load jQuery and Bootstrap. This extraction preserves the existing browser listing.
35
+
36
+ ## Tests and release
37
+
38
+ Tests protect literal URLs, filtering, duplicate behavior, detail-only metadata, date conversion, cleanup, retry/session handling and isolated ESM/CommonJS metadata. Synthetic DOM fixtures cover edge cases. Public listing and detail fragments captured on 8 November 2026 include identifiers and content frozen from the original source-specific extractors, date parser and Markdown builder. They run without provider requests.
39
+
40
+ Run `pnpm check` from the sources repository root. Follow [the release procedure](../../RELEASING.md) for package checks, tarball inspection, publication and Obo integration.
@@ -0,0 +1,3 @@
1
+ import type { DataSource } from "@oboapp/source-sdk";
2
+ import { type BrowserDependencies } from "@oboapp/source-web-utils/browser";
3
+ export declare function createSourceWithDependencies(dependencies?: BrowserDependencies): DataSource;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSourceWithDependencies = createSourceWithDependencies;
4
+ const browser_1 = require("@oboapp/source-web-utils/browser");
5
+ const INDEX_URL = "https://nadezhda.sofia.bg/%D0%BE%D0%B1%D1%8F%D0%B2%D0%B8-%D0%B8-%D1%81%D1%8A%D0%BE%D0%B1%D1%89%D0%B5%D0%BD%D0%B8%D1%8F";
6
+ function acceptsUrl(url) {
7
+ try {
8
+ const parsed = new URL(url);
9
+ return parsed.hostname === "nadezhda.sofia.bg" && parsed.pathname.includes("/news/");
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ function createSourceWithDependencies(dependencies) {
16
+ return (0, browser_1.createBrowserSource)({
17
+ indexUrl: INDEX_URL,
18
+ selectors: {
19
+ INDEX: {
20
+ POST_CONTAINER: "li.shadow-sm",
21
+ POST_LINK: 'a[href*="/news/"]',
22
+ POST_DATE: "small, .small, .date, [class*=date]",
23
+ POST_TITLE: "a",
24
+ },
25
+ POST: {
26
+ CONTENT: ".justify-content-center > div > .bg-white.mt-2 > .p-2, .justify-content-center > div .p-2",
27
+ TITLE: ".justify-content-center > div > h1.pl-2, .justify-content-center h1.pl-2",
28
+ DATE: ".justify-content-center > div > .bg-white.mt-2 > small.pl-2, .justify-content-center small.pl-2",
29
+ },
30
+ },
31
+ urlFilter: acceptsUrl,
32
+ dedupe: "last",
33
+ unwantedElements: ["script", "style", "nav", "footer", ".breadcrumb"],
34
+ }, dependencies);
35
+ }
@@ -0,0 +1,2 @@
1
+ import type { CreateSource } from "@oboapp/source-sdk";
2
+ export declare const createSource: CreateSource;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSource = void 0;
4
+ const browser_js_1 = require("./browser.js");
5
+ const createSource = () => {
6
+ return (0, browser_js_1.createSourceWithDependencies)();
7
+ };
8
+ exports.createSource = createSource;
@@ -0,0 +1,2 @@
1
+ import type { SourceMetadata } from "@oboapp/source-sdk/metadata";
2
+ export declare const metadata: SourceMetadata;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.metadata = void 0;
4
+ exports.metadata = {
5
+ id: "nadezhda-org",
6
+ url: "https://nadezhda.sofia.bg/%D0%BE%D0%B1%D1%8F%D0%B2%D0%B8-%D0%B8-%D1%81%D1%8A%D0%BE%D0%B1%D1%89%D0%B5%D0%BD%D0%B8%D1%8F",
7
+ name: 'Столична община, Район "Надежда"',
8
+ localities: ["bg.sofia"],
9
+ };
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
@@ -0,0 +1,3 @@
1
+ import type { DataSource } from "@oboapp/source-sdk";
2
+ import { type BrowserDependencies } from "@oboapp/source-web-utils/browser";
3
+ export declare function createSourceWithDependencies(dependencies?: BrowserDependencies): DataSource;
@@ -0,0 +1,32 @@
1
+ import { createBrowserSource } from "@oboapp/source-web-utils/browser";
2
+ const INDEX_URL = "https://nadezhda.sofia.bg/%D0%BE%D0%B1%D1%8F%D0%B2%D0%B8-%D0%B8-%D1%81%D1%8A%D0%BE%D0%B1%D1%89%D0%B5%D0%BD%D0%B8%D1%8F";
3
+ function acceptsUrl(url) {
4
+ try {
5
+ const parsed = new URL(url);
6
+ return parsed.hostname === "nadezhda.sofia.bg" && parsed.pathname.includes("/news/");
7
+ }
8
+ catch {
9
+ return false;
10
+ }
11
+ }
12
+ export function createSourceWithDependencies(dependencies) {
13
+ return createBrowserSource({
14
+ indexUrl: INDEX_URL,
15
+ selectors: {
16
+ INDEX: {
17
+ POST_CONTAINER: "li.shadow-sm",
18
+ POST_LINK: 'a[href*="/news/"]',
19
+ POST_DATE: "small, .small, .date, [class*=date]",
20
+ POST_TITLE: "a",
21
+ },
22
+ POST: {
23
+ CONTENT: ".justify-content-center > div > .bg-white.mt-2 > .p-2, .justify-content-center > div .p-2",
24
+ TITLE: ".justify-content-center > div > h1.pl-2, .justify-content-center h1.pl-2",
25
+ DATE: ".justify-content-center > div > .bg-white.mt-2 > small.pl-2, .justify-content-center small.pl-2",
26
+ },
27
+ },
28
+ urlFilter: acceptsUrl,
29
+ dedupe: "last",
30
+ unwantedElements: ["script", "style", "nav", "footer", ".breadcrumb"],
31
+ }, dependencies);
32
+ }
@@ -0,0 +1,2 @@
1
+ import type { CreateSource } from "@oboapp/source-sdk";
2
+ export declare const createSource: CreateSource;
@@ -0,0 +1,4 @@
1
+ import { createSourceWithDependencies } from "./browser.js";
2
+ export const createSource = () => {
3
+ return createSourceWithDependencies();
4
+ };
@@ -0,0 +1,2 @@
1
+ import type { SourceMetadata } from "@oboapp/source-sdk/metadata";
2
+ export declare const metadata: SourceMetadata;
@@ -0,0 +1,6 @@
1
+ export const metadata = {
2
+ id: "nadezhda-org",
3
+ url: "https://nadezhda.sofia.bg/%D0%BE%D0%B1%D1%8F%D0%B2%D0%B8-%D0%B8-%D1%81%D1%8A%D0%BE%D0%B1%D1%89%D0%B5%D0%BD%D0%B8%D1%8F",
4
+ name: 'Столична община, Район "Надежда"',
5
+ localities: ["bg.sofia"],
6
+ };
package/package.json CHANGED
@@ -1,6 +1,68 @@
1
1
  {
2
2
  "name": "@oboapp/source-nadezhda-org",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Webpage source for Sofia district Nadezhda",
5
+ "main": "./dist/cjs/index.js",
6
+ "license": "Unlicense",
7
+ "type": "module",
8
+ "types": "./dist/cjs/index.d.ts",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/oboapp/sources.git",
12
+ "directory": "packages/source-nadezhda-org"
13
+ },
14
+ "engines": {
15
+ "node": ">=20.3"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "LICENSE",
23
+ "README.md",
24
+ "CHANGELOG.md"
25
+ ],
26
+ "typesVersions": {
27
+ "*": {
28
+ "metadata": [
29
+ "dist/cjs/metadata.d.ts"
30
+ ]
31
+ }
32
+ },
33
+ "exports": {
34
+ ".": {
35
+ "import": {
36
+ "types": "./dist/esm/index.d.ts",
37
+ "default": "./dist/esm/index.js"
38
+ },
39
+ "require": {
40
+ "types": "./dist/cjs/index.d.ts",
41
+ "default": "./dist/cjs/index.js"
42
+ }
43
+ },
44
+ "./metadata": {
45
+ "import": {
46
+ "types": "./dist/esm/metadata.d.ts",
47
+ "default": "./dist/esm/metadata.js"
48
+ },
49
+ "require": {
50
+ "types": "./dist/cjs/metadata.d.ts",
51
+ "default": "./dist/cjs/metadata.js"
52
+ }
53
+ }
54
+ },
55
+ "sideEffects": false,
56
+ "dependencies": {
57
+ "@oboapp/source-sdk": "0.1.1",
58
+ "@oboapp/source-web-utils": "0.2.0"
59
+ },
60
+ "devDependencies": {
61
+ "happy-dom": "20.0.11",
62
+ "playwright": "1.61.1"
63
+ },
64
+ "scripts": {
65
+ "test": "tsc -p test/tsconfig.json && tsx --test test/*.test.ts",
66
+ "build": "tsc -p tsconfig.json && tsc -p tsconfig.cjs.json && node scripts/finalize-build.mjs"
67
+ }
6
68
  }