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 +44 -0
- package/package.json +10 -2
- package/src/index.d.ts +28 -0
- package/{index.js → src/index.js} +2 -0
- package/src/scraper/igdl.js +83 -0
- package/src/tests/index.test.js +20 -0
- package/src/types/igdl.d.ts +24 -0
- package/src/types/index.d.ts +7 -0
- package/.github/workflows/npm-publish.yml +0 -36
- package/.github/workflows/publish-jsr.yml +0 -24
- package/.si.json +0 -15
- package/jsr.json +0 -6
- package/tests/index.test.js +0 -10
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.
|
|
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";
|
|
@@ -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;
|
|
@@ -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
package/tests/index.test.js
DELETED
|
@@ -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
|
-
});
|