sawit-utils 0.1.0 → 0.1.1

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/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # Sawit Utils
2
+
3
+ A palm-based library for your various project needs. I actually created this package solely for personal use in my WhatsApp bot project.
4
+ However, if you'd like to use it too, feel free to do so :D
5
+ ## Getting Started
6
+
7
+ ### Install
8
+ Currently, sawit-utils is available in the [npmjs](https://www.npmjs.com/package/sawit-utils) and [jsr](https://jsr.io/@indra87g/sawit-utils) registry.
9
+
10
+ ```sh
11
+ npm install sawit-utils # npm
12
+
13
+ bun jsr add @indra87g/sawit-utils # bunjs (jsr)
14
+ ```
15
+
16
+ ## Use
17
+ For example, we want to download reels video from Instagram:
18
+
19
+ ```js
20
+ import { igdl } from "sawit-utils"
21
+
22
+ const instagram = await igdl(URL);
23
+ console.log(instagram.data.videoUrl);
24
+ ```
25
+
26
+ ## Planned Feature
27
+ - Payment API
28
+ - Mustikapay
29
+ - Zakkistore
30
+ - Pakasir
31
+
32
+ ## Contributing
33
+ ### Procedures
34
+ - Fork this repository
35
+ - Make your commit
36
+ - Open pull request and wait for code review
37
+ - Dont forget to give a star :)
38
+
39
+ ### Rules
40
+ - Use AI only in type definition generation
41
+
42
+ ## License
43
+
44
+ MIT
package/package.json CHANGED
@@ -1,11 +1,19 @@
1
1
  {
2
2
  "name": "sawit-utils",
3
3
  "description": "A palm-based utility for single-ball programmers who think palms are trees.",
4
- "version": "0.1.0",
4
+ "version": "0.1.1",
5
5
  "type": "module",
6
+ "main": "./src/index.js",
7
+ "types": "./src/index.d.ts",
8
+ "exports": {
9
+ ".": "./src/index.js"
10
+ },
11
+ "files": [
12
+ "src"
13
+ ],
6
14
  "scripts": {
7
15
  "start": "node index.js",
8
- "test": "vitest"
16
+ "test": "vitest src/tests"
9
17
  },
10
18
  "dependencies": {
11
19
  "moment": "^2.30.1",
package/src/index.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ export function convertMsToDuration(ms: number): string;
2
+
3
+ export function formatSize(
4
+ byteCount: number,
5
+ withPerSecond?: boolean
6
+ ): string;
7
+
8
+ export function generateUID(id: string): string | null;
9
+
10
+ export function getRandomElement<T>(array: T[]): T | null;
11
+
12
+ export function isUrl(url: string): boolean;
13
+
14
+ export function delay(ms: number): Promise<void> | null;
15
+
16
+ export const formatUptime: (startTime: number) => string;
17
+
18
+ export const escapeHTML: (text: string) => string;
19
+
20
+ export type {
21
+ IgdlVideo,
22
+ IgdlData,
23
+ IgdlSuccessResult,
24
+ IgdlErrorResult,
25
+ IgdlResult,
26
+ } from "./types/igdl.js";
27
+
28
+ export { igdl } from "./scraper/igdl.js";
@@ -108,3 +108,5 @@ export const escapeHTML = (text) => {
108
108
  .replace(/"/g, "&quot;")
109
109
  .replace(/'/g, "&#039;");
110
110
  };
111
+
112
+ export { igdl } from "./scraper/igdl.js";
@@ -0,0 +1,83 @@
1
+ export async function igdl(url) {
2
+ try {
3
+ const reelCode = (() => {
4
+ const patterns = [
5
+ /instagram\.com\/reel\/([A-Za-z0-9_-]+)/,
6
+ /instagram\.com\/p\/([A-Za-z0-9_-]+)/,
7
+ /instagram\.com\/tv\/([A-Za-z0-9_-]+)/,
8
+ ];
9
+ for (const pattern of patterns) {
10
+ const match = url.match(pattern);
11
+ if (match && match[1]) return match[1];
12
+ }
13
+ return null;
14
+ })();
15
+
16
+ if (!reelCode) {
17
+ throw new Error("URL Instagram tidak valid");
18
+ }
19
+
20
+ const formData = new URLSearchParams();
21
+ formData.append("id", url);
22
+ formData.append("locale", "id");
23
+ formData.append("cf-turnstile-response", "");
24
+ formData.append("tt", "a66b23d8bfa4878536d788ac3d33d1a6");
25
+ formData.append("ts", "1771729612");
26
+
27
+ const response = await fetch(`https://reelsvideo.io/reel/${reelCode}/`, {
28
+ method: "POST",
29
+ headers: {
30
+ Accept: "*/*",
31
+ "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
32
+ "HX-Request": "true",
33
+ Origin: "https://reelsvideo.io",
34
+ Referer: "https://reelsvideo.io/id",
35
+ "User-Agent":
36
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
37
+ },
38
+ body: formData,
39
+ });
40
+
41
+ const html = await response.text();
42
+
43
+ const usernameMatch = html.match(
44
+ /<span[^>]*class="text-400-16-18"[^>]*>([^<]+)<\/span>/,
45
+ );
46
+ const username = usernameMatch ? usernameMatch[1].trim() : null;
47
+
48
+ const thumbMatch = html.match(
49
+ /data-bg="([^"]+)"|style="background-image: url\(([^)]+)\)/,
50
+ );
51
+ const thumbnail = thumbMatch ? thumbMatch[1] || thumbMatch[2] : null;
52
+
53
+ const videoLinks = html.matchAll(
54
+ /<a[^>]*href="(https:\/\/ssscdn\.io\/reelsvideo\/[^"]+)"[^>]*class="[^"]*(?:download_link|type_videos|type_audio)[^"]*"[^>]*>/g,
55
+ );
56
+
57
+ const videoUrls = new Set();
58
+ for (const match of videoLinks) {
59
+ if (match[1]) videoUrls.add(match[1]);
60
+ }
61
+
62
+ const videos = Array.from(videoUrls).map((url) => ({
63
+ url: url,
64
+ quality: "HD",
65
+ }));
66
+
67
+ return {
68
+ success: true,
69
+ data: {
70
+ username: username,
71
+ thumbnail: thumbnail,
72
+ videos: videos,
73
+ videoUrl: videos.length > 0 ? videos[0].url : null,
74
+ alternativeUrl: videos.length > 1 ? videos[1].url : null,
75
+ },
76
+ };
77
+ } catch (error) {
78
+ return {
79
+ success: false,
80
+ error: error.message,
81
+ };
82
+ }
83
+ }
@@ -0,0 +1,20 @@
1
+ import { expect, test, toBe, beforeAll } from "vitest";
2
+ import { convertMsToDuration, formatSize } from "../index.js";
3
+ import { igdl } from "../scraper/igdl.js";
4
+
5
+ test("convert 10000ms to equal 10s", () => {
6
+ expect(convertMsToDuration(10000)).toBe("10 seconds");
7
+ });
8
+
9
+ test("format 100000000bytes to equal 95.37 MiB", () => {
10
+ expect(formatSize(100000000)).toBe("95.37 MiB");
11
+ });
12
+
13
+ test("download instagram video, must return true", () => {
14
+ let instagram;
15
+
16
+ beforeAll(async () => {
17
+ instagram = await igdl("https://www.instagram.com/reel/DVXpryaE841/?utm_source=ig_web_copy_link&igsh=NTc4MTIwNjQ2YQ==");
18
+ expect(instagram.success).toBe(true);
19
+ })
20
+ });
@@ -0,0 +1,24 @@
1
+ export interface IgdlVideo {
2
+ url: string;
3
+ quality: string;
4
+ }
5
+
6
+ export interface IgdlData {
7
+ username: string | null;
8
+ thumbnail: string | null;
9
+ videos: IgdlVideo[];
10
+ videoUrl: string | null;
11
+ alternativeUrl: string | null;
12
+ }
13
+
14
+ export interface IgdlSuccessResult {
15
+ success: true;
16
+ data: IgdlData;
17
+ }
18
+
19
+ export interface IgdlErrorResult {
20
+ success: false;
21
+ error: string;
22
+ }
23
+
24
+ export type IgdlResult = IgdlSuccessResult | IgdlErrorResult;
@@ -0,0 +1,7 @@
1
+ export type {
2
+ IgdlVideo,
3
+ IgdlData,
4
+ IgdlSuccessResult,
5
+ IgdlErrorResult,
6
+ IgdlResult,
7
+ } from "./igdl.js";
@@ -1,36 +0,0 @@
1
- # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2
- # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
3
-
4
- name: Node.js Package
5
-
6
- on:
7
- release:
8
- types: [created]
9
- workflow_dispatch:
10
-
11
- jobs:
12
- build:
13
- runs-on: ubuntu-latest
14
- steps:
15
- - uses: actions/checkout@v4
16
- - uses: actions/setup-node@v4
17
- with:
18
- node-version: 24
19
- - run: npm install
20
- - run: npm ci
21
- - run: npm test
22
-
23
- publish-npm:
24
- needs: build
25
- runs-on: ubuntu-latest
26
- steps:
27
- - uses: actions/checkout@v4
28
- - uses: actions/setup-node@v4
29
- with:
30
- node-version: 24
31
- registry-url: https://registry.npmjs.org/
32
- - run: npm install
33
- - run: npm ci
34
- - run: npm publish
35
- env:
36
- NODE_AUTH_TOKEN: ${{secrets.npm_token}}
@@ -1,24 +0,0 @@
1
- name: Publish to JSR
2
- on:
3
- workflow_dispatch:
4
-
5
- jobs:
6
- publish:
7
- runs-on: ubuntu-latest
8
- permissions:
9
- contents: read
10
- id-token: write
11
- steps:
12
- - name: Checkout
13
- uses: actions/checkout@v6.0.2
14
-
15
- - name: Setup Node
16
- uses: actions/setup-node@v4
17
- with:
18
- node-version: '24'
19
-
20
- - name: Install Package
21
- run: npm install
22
-
23
- - name: Publish package
24
- run: npx jsr publish
package/.si.json DELETED
@@ -1,15 +0,0 @@
1
- {
2
- "plugin": "org.smartide.plugin.nodejs",
3
- "run": "npm test",
4
- "gui": false,
5
- "intelligence": {
6
- ".json": {
7
- "enabled": true,
8
- "run": "vscode-json-language-server --stdio"
9
- },
10
- ".js": {
11
- "enabled": false,
12
- "run": "typescript-language-server --stdio"
13
- }
14
- }
15
- }
package/jsr.json DELETED
@@ -1,6 +0,0 @@
1
- {
2
- "name": "@indra87g/sawit-utils",
3
- "version": "0.1.0",
4
- "license": "MIT",
5
- "exports": "./index.js"
6
- }
@@ -1,10 +0,0 @@
1
- import { expect, test, toBe } from "vitest";
2
- import { convertMsToDuration, formatSize } from "../index.js";
3
-
4
- test("convert 10000ms to equal 10s", () => {
5
- expect(convertMsToDuration(10000)).toBe("10 seconds");
6
- });
7
-
8
- test("format 100000000bytes to equal 95.37 MiB", () => {
9
- expect(formatSize(100000000)).toBe("95.37 MiB");
10
- });