Skip to content

Commit 37184c7

Browse files
authored
Merge branch 'main' into dependabot/npm_and_yarn/mongodb-4.17.0
2 parents 7a1e1d0 + da9630f commit 37184c7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+4627
-707
lines changed

Diff for: .github/workflows/publish-docker.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Publish Docker Image 'jeeetpaul/whatsbot'
1+
name: Publish Docker Image 'wcyat/whatsbot'
22

33
on:
44
push:
@@ -20,5 +20,5 @@ jobs:
2020
with:
2121
username: ${{ secrets.DOCKER_USERNAME }}
2222
password: ${{ secrets.DOCKER_PASSWORD }}
23-
repository: jeeetpaul/whatsbot
23+
repository: wcyat/whatsbot
2424
tag_with_ref: true

Diff for: .gitignore

-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ session-output/*
2323
session-output
2424
session.secure
2525
/.lh/
26-
**/*.js
2726
/dist/
2827
/docker/data
2928
/docker/wtstodis-data

Diff for: .gitlab-ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ update-dependencies:
5252
- git reset --hard origin/main
5353
script:
5454
- yarn
55-
- yarn add whatsapp-web.js@latest
55+
- yarn add https://github.com/pedroslopez/whatsapp-web.js
5656
- git commit -a -m "Update whatsapp-web.js" || exit 0
5757
- git push origin main
5858
rules:

Diff for: Dockerfile

+5-7
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,19 @@ COPY . ./
1212

1313
RUN yarn build
1414

15-
RUN yarn install --production
16-
1715
FROM ghcr.io/puppeteer/puppeteer:latest
1816

19-
USER root
20-
2117
WORKDIR /app
2218

23-
RUN apt-get update && apt-get install -y ffmpeg && rm -rf /var/lib/{apt,dpkg,cache,log}/
19+
USER root
2420

25-
COPY --from=build /app/dist ./
26-
COPY ./yarn.lock ./yarn.lock
21+
RUN apt-get update && apt-get install -y ffmpeg stockfish && rm -rf /var/lib/{apt,dpkg,cache,log}/
2722

23+
COPY ./package.json ./yarn.lock ./
2824
RUN yarn install --production
2925

26+
COPY --from=build /app/dist ./
27+
3028
VOLUME ["/app/data"]
3129

3230
CMD ["node", "startProcess.js"]

Diff for: README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# Whatsapp to Discord
1+
# Whatsbot
22

3-
Forward messages between whatsapp and discord. Based on [WhatsBot](https://github.com/tuhinpal/WhatsBot/).
3+
This is a feature-rich heavily-modified Whatsbot fork.
44

55
<!-- <h1 align="center">
66
<a href="https://github.com/tuhinpal/WhatsBot"><img src="https://telegra.ph/file/96ccad5945c18944c5f15.png" alt="whatsbot" width="290"></a>

Diff for: cf-worker/.editorconfig

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# http://editorconfig.org
2+
root = true
3+
4+
[*]
5+
indent_style = tab
6+
tab_width = 2
7+
end_of_line = lf
8+
charset = utf-8
9+
trim_trailing_whitespace = true
10+
insert_final_newline = true
11+
12+
[*.yml]
13+
indent_style = space

Diff for: cf-worker/.prettierrc

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"printWidth": 140,
3+
"singleQuote": true,
4+
"semi": true,
5+
"useTabs": true
6+
}

Diff for: cf-worker/package.json

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "whatsbot-worker",
3+
"version": "0.0.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"deploy": "wrangler deploy",
8+
"dev": "wrangler dev",
9+
"start": "wrangler dev"
10+
},
11+
"devDependencies": {
12+
"wrangler": "^3.0.0"
13+
},
14+
"dependencies": {
15+
"@cloudflare/ai": "^1.1.0"
16+
}
17+
}

Diff for: cf-worker/src/auth.ts

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { BadRequestException, UnauthorizedException } from './exceptions.js';
2+
import { Env } from './types.js';
3+
4+
export function basicAuthentication(request: Request) {
5+
const Authorization = request.headers.get('Authorization');
6+
7+
const [scheme, encoded] = Authorization?.split(' ') || ['', ''];
8+
9+
// The Authorization header must start with Basic, followed by a space.
10+
if (!encoded || scheme !== 'Basic') {
11+
throw new BadRequestException('Malformed authorization header.');
12+
}
13+
14+
// Decodes the base64 value and performs unicode normalization.
15+
// @see https://datatracker.ietf.org/doc/html/rfc7613#section-3.3.2 (and #section-4.2.2)
16+
// @see https://dev.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/normalize
17+
const buffer = Uint8Array.from(atob(encoded), (character) => character.charCodeAt(0));
18+
const decoded = new TextDecoder().decode(buffer).normalize();
19+
20+
// The username & password are split by the first colon.
21+
//=> example: "username:password"
22+
const index = decoded.indexOf(':');
23+
24+
// The user & password are split by the first colon and MUST NOT contain control characters.
25+
// @see https://tools.ietf.org/html/rfc5234#appendix-B.1 (=> "CTL = %x00-1F / %x7F")
26+
// eslint-disable-next-line no-control-regex
27+
if (index === -1 || /[\0-\x1F\x7F]/.test(decoded)) {
28+
throw new BadRequestException('Invalid authorization value.');
29+
}
30+
31+
return {
32+
user: decoded.substring(0, index),
33+
pass: decoded.substring(index + 1),
34+
};
35+
}
36+
37+
export function verifyCredentials(user: string, pass: string, env: Env) {
38+
if (env.AUTH_USER !== user) {
39+
throw new UnauthorizedException('Invalid credentials.');
40+
}
41+
42+
if (env.AUTH_PASSWORD !== pass) {
43+
throw new UnauthorizedException('Invalid credentials.');
44+
}
45+
}

Diff for: cf-worker/src/exceptions.ts

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
export class UnauthorizedException {
2+
status: number;
3+
statusText: string;
4+
reason: string;
5+
6+
constructor(reason: string) {
7+
this.status = 401;
8+
this.statusText = 'Unauthorized';
9+
this.reason = reason;
10+
}
11+
}
12+
13+
export class BadRequestException {
14+
status: number;
15+
statusText: string;
16+
reason: string;
17+
18+
constructor(reason: string) {
19+
this.status = 400;
20+
this.statusText = 'Bad Request';
21+
this.reason = reason;
22+
}
23+
}
24+
25+
export class FailedException {
26+
status: number;
27+
statusText: string;
28+
reason: string;
29+
30+
constructor(reason: string) {
31+
this.status = 500;
32+
this.statusText = 'Internal Server Error';
33+
this.reason = reason;
34+
}
35+
}

Diff for: cf-worker/src/index.ts

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
2+
// @ts-ignore
3+
import { Ai } from '@cloudflare/ai';
4+
import { Env } from './types.js';
5+
import { BadRequestException, FailedException } from './exceptions.js';
6+
import { basicAuthentication, verifyCredentials } from './auth.js';
7+
8+
export default {
9+
async fetch(request: Request, env: Env) {
10+
const { protocol } = new URL(request.url);
11+
12+
// In the case of a Basic authentication, the exchange MUST happen over an HTTPS (TLS) connection to be secure.
13+
if ('https:' !== protocol || 'https' !== request.headers.get('x-forwarded-proto')) {
14+
throw new BadRequestException('Please use a HTTPS connection.');
15+
}
16+
17+
if (request.headers.has('Authorization')) {
18+
// Throws exception when authorization fails.
19+
const { user, pass } = basicAuthentication(request);
20+
try {
21+
verifyCredentials(user, pass, env);
22+
} catch {
23+
// Not authenticated.
24+
return new Response('You need to login.', {
25+
status: 401,
26+
headers: {
27+
// Prompts the user for credentials.
28+
'WWW-Authenticate': 'Basic realm="my scope", charset="UTF-8"',
29+
},
30+
});
31+
}
32+
33+
// Only returns this response when no exception is thrown.
34+
} else {
35+
// Not authenticated.
36+
return new Response('You need to login.', {
37+
status: 401,
38+
headers: {
39+
// Prompts the user for credentials.
40+
'WWW-Authenticate': 'Basic realm="my scope", charset="UTF-8"',
41+
},
42+
});
43+
}
44+
45+
const ai = new Ai(env.AI);
46+
47+
const url = new URL(request.url);
48+
const params = url.searchParams;
49+
const path = url.pathname;
50+
51+
if ((path === '/' && request.method === 'GET') || path === '/llama') {
52+
const prompt = params.get('prompt');
53+
let messages: { role: 'user' | 'system' | 'assistant'; content: string }[] = [];
54+
try {
55+
messages = JSON.parse(decodeURIComponent(params.get('messages')) || '[]');
56+
} catch {
57+
return new BadRequestException('Messages must be a valid JSON array.');
58+
}
59+
60+
const chat = {
61+
messages: [
62+
{
63+
role: 'system',
64+
content: 'Below is an instruction that describes a task. Write a response that appropriately completes the request.',
65+
},
66+
...(messages?.length
67+
? messages
68+
: [
69+
{
70+
role: 'user',
71+
content: prompt,
72+
},
73+
]),
74+
],
75+
};
76+
let response: string;
77+
for (let i = 5; i > 0; i--) {
78+
try {
79+
response = await ai.run('@cf/meta/llama-3-8b-instruct', chat);
80+
break;
81+
} catch {
82+
if (i === 1) {
83+
return new FailedException('Failed to generate after five tries.');
84+
}
85+
}
86+
}
87+
88+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
89+
// @ts-ignore
90+
return Response.json(response);
91+
}
92+
93+
if ((path === '/' && request.method === 'POST') || path === '/transcribe') {
94+
const contentType = request.headers.get('Content-Type');
95+
if (!contentType || !contentType.includes('multipart/form-data')) {
96+
return new Response('Content-Type must be "multipart/form-data"', { status: 400 });
97+
}
98+
99+
const formData = await request.formData();
100+
const audioFile = formData.get('audio');
101+
102+
// Check if audio file exists in the POST request
103+
if (!audioFile || !(audioFile instanceof File)) {
104+
return new Response('Audio file not found in the request', { status: 400 });
105+
}
106+
107+
// Convert audio file to ArrayBuffer
108+
const blob = await audioFile.arrayBuffer();
109+
const inputs = {
110+
audio: [...new Uint8Array(blob)],
111+
};
112+
const response = await ai.run('@cf/openai/whisper', inputs);
113+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
114+
// @ts-ignore
115+
return Response.json(response);
116+
}
117+
118+
if (path === '/sd') {
119+
const prompt = params.get('prompt');
120+
const inputs = {
121+
prompt,
122+
};
123+
124+
const response = await ai.run('@cf/lykon/dreamshaper-8-lcm', inputs);
125+
126+
return new Response(response, {
127+
headers: {
128+
'content-type': 'image/png',
129+
},
130+
});
131+
}
132+
},
133+
};

Diff for: cf-worker/src/types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface Env {
2+
AUTH_USER: string;
3+
AUTH_PASSWORD: string;
4+
AI: string;
5+
}

Diff for: cf-worker/wrangler.toml.template

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
name = "your-worker-name"
2+
main = "src/index.ts"
3+
compatibility_date = "2023-10-07"
4+
workers_dev = true
5+
6+
[ai]
7+
binding = "AI"

0 commit comments

Comments
 (0)