├── Keyboard
└── ChatGPT.js
├── README.md
└── Widgets
├── Contrigets.js
├── Dougets.js
├── Instagets.js
├── Material
├── Contrigets.jpg
├── Dougets.png
├── Instagets.jpg
├── Netgets.png
└── Telegets.jpg
├── Netgets.js
├── README.md
└── Telegets.js
/Keyboard/ChatGPT.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | ChatGPT Keyboard by Neurogram
4 |
5 | - Support editing tools
6 | - Support appending or overwriting prompts with generated result
7 | - Support custom roles.
8 | - Support prompts templates.
9 | - Support multi round of dialogue
10 | - Support displaying length of prompts
11 | - Support displaying tokens usage reminder
12 | - Support DeepL and Google translate
13 | - Support shortcut key to run
14 |
15 | Manual: https://neurogram.notion.site/ChatGPT-Keyboard-af8f7c74bc5c47989259393c953b8017
16 |
17 | */
18 |
19 | const api_key = "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" // Your API key
20 | const model = "gpt-4o"
21 | const openai_proxy_url = "" // Optional
22 |
23 | const deepl_api_key = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx:fx"
24 | const deepl_api_url = "" // Optional
25 |
26 | const user_gesture = { // Generated results: 0: auto-wrap 1: overwrite selected/all prompts
27 | tap: 1,
28 | long_press: 0
29 | }
30 | const usage_toast = true // Display usage toast
31 |
32 | const shortcut = true // false: off, true: on
33 | const shortcut_action = "tap" // tap, long_press
34 | const auto_switch_kbd = true // false: off, true: on
35 |
36 | const keyboard_sound = true
37 | const keyboard_vibrate = -1 // -1: no vibration, 0~2: vibration level
38 | const edit_tool_columns = 5
39 | const chatgpt_role_columns = 3
40 | const keyboard_spacing = 5
41 | const keyboard_height = 40
42 | const keyboard_total_height = 0
43 | $keyboard.barHidden = false
44 |
45 | const heartbeat = 2 // -1: no heartbeat, 0~2: heartbeat level
46 | const heartbeat_interval = 1.2 // seconds
47 |
48 | const role_data = {
49 | "🤖 Assistant": {
50 | "type": "GPT",
51 | "messages": [
52 | { "role": "system", "content": "You are a helpful assistant." },
53 | { "role": "user", "content": `{USER_CONTENT}` }
54 | ],
55 | "shortcut": "="
56 | },
57 | "🗂️ Summarizer": {
58 | "type": "GPT",
59 | "messages": [
60 | { "role": "system", "content": "" },
61 | { "role": "user", "content": `Please summarize the following content:{USER_CONTENT}` }
62 | ],
63 | "shortcut": ""
64 | },
65 | "📑 Expander": {
66 | "type": "GPT",
67 | "messages": [
68 | { "role": "system", "content": "" },
69 | { "role": "user", "content": `{USER_CONTENT}\n\nExpand the above content` }
70 | ],
71 | "shortcut": ""
72 | },
73 | "🇺🇸 GPT": {
74 | "type": "GPT",
75 | "messages": [
76 | { "role": "user", "content": `Please translate content into English: {USER_CONTENT}` }
77 | ],
78 | "shortcut": ""
79 | },
80 | "🇺🇸 DeepL": {
81 | "type": "DeepL",
82 | "target_lang": "EN",
83 | "shortcut": ""
84 | },
85 | "🇺🇸 Google": {
86 | "type": "Google",
87 | "target_lang": "en",
88 | "shortcut": ""
89 | },
90 | }
91 |
92 | const edit_tool = {
93 | "Start": "arrow.left.to.line",
94 | "Left": "arrow.left",
95 | "Right": "arrow.right",
96 | "End": "arrow.right.to.line",
97 | "Return": "return",
98 | "Copy": "doc.on.doc",
99 | "Paste": "doc.on.clipboard",
100 | "Cut": "scissors",
101 | "Empty": "trash",
102 | "Dismiss": "keyboard.chevron.compact.down"
103 | }
104 |
105 | const edit_tool_amount = Object.keys(edit_tool).length
106 | let dialogue = $cache.get("dialogue")
107 | let multi_turn = false
108 | if (dialogue) multi_turn = dialogue.mode
109 |
110 | $app.theme = "auto"
111 | $ui.render({
112 | props: {
113 | title: "ChatGPT",
114 | navBarHidden: $app.env == $env.keyboard,
115 | pageSheet: $app.env == $env.keyboard,
116 | bgcolor: $color("#D0D3D9", "#2D2D2D"),
117 | },
118 | views: [{
119 | type: "matrix",
120 | props: {
121 | spacing: keyboard_spacing,
122 | bgcolor: $color("clear"),
123 | data: dataPush(Object.keys(edit_tool).concat(Object.keys(role_data))),
124 | template: {
125 | props: {},
126 | views: [{
127 | type: "button",
128 | props: {
129 | id: "button",
130 | radius: 10,
131 | titleColor: $color("black", "white"),
132 | tintColor: $color("black", "white"),
133 | bgcolor: $color("#FFFFFF", "#6B6B6B"),
134 | align: $align.center
135 | },
136 | layout: $layout.fill,
137 | events: {
138 | tapped: function (sender, indexPath, data) {
139 | handler(sender, "tap")
140 | },
141 | longPressed: function (info, indexPath, data) {
142 | handler(info.sender, "long_press")
143 | }
144 | }
145 | }]
146 | },
147 | footer: {
148 | type: "button",
149 | props: {
150 | id: "footer",
151 | height: 20,
152 | title: " ChatGPT Keyboard by Neurogram",
153 | titleColor: $color("#AAAAAA"),
154 | bgcolor: $color("clear"),
155 | symbol: multi_turn ? "bubble.left.and.bubble.right" : "bubble.left",
156 | tintColor: $color("#AAAAAA"),
157 | align: $align.center,
158 | font: $font(10)
159 | },
160 | events: {
161 | tapped: async (sender) => {
162 | const popover = $ui.popover({
163 | sourceView: sender,
164 | sourceRect: sender.bounds,
165 | directions: $popoverDirection.any,
166 | size: $size(320, 200),
167 | views: [
168 | {
169 | type: "scroll",
170 | layout: function (make, view) {
171 | make.edges.insets($insets(10, 10, 10, 10))
172 | },
173 | views: [{
174 | type: "label",
175 | props: {
176 | text: await get_content(1),
177 | font: $font(15),
178 | lines: 0
179 | },
180 | layout: function (make, view) {
181 | make.width.equalTo(300)
182 | },
183 | events: {
184 | tapped: () => {
185 | popover.dismiss()
186 | }
187 | }
188 | }]
189 | }
190 | ]
191 | })
192 | },
193 | longPressed: function (info) {
194 | multi_turn = multi_turn ? false : true
195 | set_bubble()
196 | $ui.toast("Dialogue Mode " + (multi_turn ? "On" : "Off"))
197 | $cache.set("dialogue", { mode: multi_turn })
198 | }
199 | }
200 | }
201 | },
202 | layout: $layout.fill,
203 | events: {
204 | itemSize: function (sender, indexPath) {
205 | let keyboard_columns = indexPath.item < edit_tool_amount ? edit_tool_columns : chatgpt_role_columns
206 | return $size(($device.info.screen.width - (keyboard_columns + 1) * keyboard_spacing) / keyboard_columns, keyboard_height);
207 | }
208 | }
209 | }],
210 | events: {
211 | appeared: function () {
212 | if (keyboard_total_height) $keyboard.height = keyboard_total_height
213 | },
214 | disappeared: function () {
215 | $keyboard.barHidden = false
216 | }
217 | }
218 | })
219 |
220 | function dataPush(data) {
221 | let key_title = []
222 | for (let i = 0; i < data.length; i++) {
223 | key_title.push({
224 | button: {
225 | title: i < edit_tool_amount ? "" : data[i],
226 | symbol: i < edit_tool_amount ? edit_tool[data[i]] : "",
227 | info: { action: i < edit_tool_amount ? data[i] : "" }
228 | }
229 | })
230 | }
231 | return key_title
232 | }
233 |
234 | function handler(sender, gesture, is_shorcut) {
235 | if (keyboard_sound) $keyboard.playInputClick()
236 | if (keyboard_vibrate != -1) $device.taptic(keyboard_vibrate)
237 | if ($app.env != $env.keyboard) return $ui.warning("Please Run In Keyboard")
238 | if (sender.info.action) return edit(sender.info.action, gesture)
239 | gpt(sender.title, gesture, is_shorcut)
240 | }
241 |
242 | async function edit(action, gesture) {
243 |
244 | let before = $keyboard.textBeforeInput ? $keyboard.textBeforeInput.length : 0
245 | let after = $keyboard.textAfterInput ? $keyboard.textAfterInput.length : 0
246 |
247 | if (action == "Start") return $keyboard.moveCursor(-before)
248 | if (action == "Left") return $keyboard.moveCursor(-1)
249 | if (action == "Right") return $keyboard.moveCursor(1)
250 | if (action == "End") return $keyboard.moveCursor(after)
251 | if (action == "Return") return $keyboard.insert("\n")
252 | if (action == "Paste") return $keyboard.insert($clipboard.text || "")
253 | if (action == "Dismiss") return gesture == "tap" ? $app.close() : $keyboard.dismiss()
254 | if (action == "Empty" && gesture == "tap") return $keyboard.delete()
255 |
256 | let content = await get_content(0)
257 | if (action != "Empty") $clipboard.text = content
258 |
259 | if (action == "Copy") return $ui.success("Done")
260 |
261 | if (action == "Cut" || action == "Empty") {
262 | if (!$keyboard.selectedText) {
263 | $keyboard.moveCursor(after)
264 | delete_content(content.length)
265 | }
266 | if ($keyboard.selectedText) $keyboard.delete()
267 | }
268 |
269 | }
270 |
271 | let generating = false
272 | let timer = ""
273 | let generating_icon = 0
274 |
275 | async function gpt(role, gesture, is_shorcut) {
276 |
277 | if (generating) return $ui.warning("In Generating")
278 | let user_content = await get_content(0)
279 | if (!user_content && !multi_turn) return $ui.warning("No Prompts Found")
280 |
281 | let shortcut_length = 0
282 | if (is_shorcut) {
283 | let shortcut_data = get_shortcut_data()
284 | let patt = new RegExp(`(${shortcut_data.keys.join("|")})$`)
285 | shortcut_length = user_content.match(patt)[1].length
286 | user_content = user_content.replace(patt, "")
287 | }
288 |
289 | generating = true
290 |
291 | let messages = []
292 |
293 | if (multi_turn) {
294 | if (role_data[role].type != "GPT") {
295 | generating = false
296 | return $ui.warning("Multi-round Are NOT Supported")
297 | }
298 |
299 | if ($keyboard.selectedText) $keyboard.moveCursor(1)
300 |
301 | if (!user_content.match(/⚙️ SYSTEM:[^🔚]+/)) {
302 | $ui.warning("No Dialogue Found")
303 | $keyboard.insert(`\n⚙️ SYSTEM:\n${role_data[role][0] || "-"}🔚\n\n👨💻 USER:\n`)
304 | generating = false
305 | return
306 | }
307 |
308 | let contents = user_content.match(/(👨💻 USER|🤖 ASSISTANT):\n([^🔚]+)/g)
309 |
310 | if (contents) {
311 | for (let i in contents) {
312 | if (contents[i].match(/👨💻 USER:\n([^🔚]+)/)) messages.push({ "role": "user", "content": contents[i].match(/👨💻 USER:\n([^🔚]+)/)[1] })
313 | if (contents[i].match(/🤖 ASSISTANT:\n([^🔚]+)/)) messages.push({ "role": "assistant", "content": contents[i].match(/🤖 ASSISTANT:\n([^🔚]+)/)[1] })
314 | }
315 | }
316 |
317 | if (!contents || messages[messages.length - 1].role != "user") {
318 | $ui.warning("No User Content Found")
319 | generating = false
320 | return
321 | }
322 |
323 | let system_content = user_content.match(/⚙️ SYSTEM:\n([^🔚]+)/)[1]
324 | if (system_content != "-") messages = [{ "role": "system", "content": system_content }].concat(messages)
325 | }
326 |
327 | if (!multi_turn) {
328 | if (!user_gesture[gesture]) {
329 | $keyboard.moveCursor(1)
330 | $keyboard.insert("\n")
331 | }
332 |
333 | if (user_gesture[gesture] && !$keyboard.selectedText) delete_content(user_content.length + shortcut_length)
334 |
335 | if (role_data[role].type == "GPT") {
336 |
337 | let preset_prompt = role_data[role].messages
338 |
339 | for (let i in preset_prompt) {
340 | messages.push({ "role": preset_prompt[i].role, "content": preset_prompt[i].content.replace(/{USER_CONTENT}/g, user_content) })
341 | }
342 | }
343 |
344 | }
345 |
346 | if (heartbeat != -1) {
347 | timer = $timer.schedule({
348 | interval: heartbeat_interval,
349 | handler: async () => {
350 | $device.taptic(heartbeat)
351 | $("footer").symbol = "ellipsis.bubble.fill"
352 | await $wait(0.2)
353 | $device.taptic(heartbeat)
354 | $("footer").symbol = "ellipsis.bubble"
355 | }
356 | })
357 | }
358 |
359 | if (heartbeat == -1) {
360 | timer = $timer.schedule({
361 | interval: heartbeat_interval / 2,
362 | handler: async () => {
363 | if (generating_icon) {
364 | generating_icon = 0
365 | $("footer").symbol = "ellipsis.bubble"
366 | } else {
367 | generating_icon = 1
368 | $("footer").symbol = "ellipsis.bubble.fill"
369 | }
370 | }
371 | })
372 | }
373 |
374 | if (role_data[role].type == "GPT") {
375 | let openai = await $http.post({
376 | url: openai_proxy_url || "https://api.openai.com/v1/chat/completions",
377 | header: {
378 | "Content-Type": "application/json",
379 | "Authorization": `Bearer ${api_key}`
380 | },
381 | body: {
382 | "model": model,
383 | "messages": messages
384 | }
385 | })
386 | timer.invalidate()
387 | set_bubble()
388 | generating = false
389 | generating_icon = 0
390 | if (openai.data.error) return $ui.error(openai.data.error.message)
391 |
392 | if (!multi_turn) $keyboard.insert(openai.data.choices[0].message.content)
393 | if (multi_turn) $keyboard.insert(`🔚\n\n🤖 ASSISTANT:\n${openai.data.choices[0].message.content}🔚\n\n👨💻 USER:\n`)
394 |
395 | if (!usage_toast) return
396 | let usage = openai.data.usage
397 | $ui.toast(`Usage: P${usage.prompt_tokens} + C${usage.completion_tokens} = T${usage.total_tokens}`)
398 | }
399 |
400 | if (role_data[role].type == "DeepL") {
401 | let deepl = await $http.post({
402 | url: deepl_api_url || "https://api-free.deepl.com/v2/translate",
403 | header: {
404 | "Content-Type": "application/json",
405 | "Authorization": `DeepL-Auth-Key ${deepl_api_key}`
406 | },
407 | body: {
408 | "text": [user_content],
409 | "target_lang": role_data[role].target_lang
410 | }
411 | })
412 | timer.invalidate()
413 | set_bubble()
414 | generating = false
415 | generating_icon = 0
416 | if (typeof deepl.data == "string") return $ui.error("DeepL Error: " + deepl.data)
417 | if (typeof deepl.data == "object" && !deepl.data.translations) return $ui.error("DeepL Error: " + deepl.data.message)
418 | let translations = []
419 |
420 | for (let t in deepl.data.translations) {
421 | translations.push(deepl.data.translations[t].text)
422 | }
423 |
424 | $keyboard.insert(translations.join("\n"))
425 | }
426 |
427 | if (role_data[role].type == "Google") {
428 | let google = await $http.get({
429 | url: `https://translate.google.com/translate_a/single?client=it&dt=qca&dt=t&dt=rmt&dt=bd&dt=rms&dt=sos&dt=md&dt=gt&dt=ld&dt=ss&dt=ex&otf=2&dj=1&hl=en&ie=UTF-8&oe=UTF-8&sl=auto&tl=${role_data[role].target_lang}&q=${encodeURIComponent(user_content)}`,
430 | header: {
431 | "User-Agent": "GoogleTranslate/6.29.59279 (iPhone; iOS 15.4; en; iPhone14,2)"
432 | }
433 | })
434 | timer.invalidate()
435 | set_bubble()
436 | generating = false
437 | generating_icon = 0
438 | if (!google.data.sentences) return $ui.error("Google Error: " + JSON.stringify(google.data))
439 | let translations = []
440 |
441 | for (let s in google.data.sentences) {
442 | if (google.data.sentences[s].trans) translations.push(google.data.sentences[s].trans)
443 | }
444 |
445 | $keyboard.insert(translations.join("\n"))
446 | }
447 |
448 | if (is_shorcut && auto_switch_kbd) $keyboard.next()
449 | }
450 |
451 | async function get_content(length) {
452 | let content = $keyboard.selectedText || await $keyboard.getAllText()
453 | if (length) content = `Length: ${content.replace(/(⚙️ SYSTEM|👨💻 USER|🤖 ASSISTANT):\n|🔚/g, "").replace(/\n+/g, "\n").length}\n\n${content}`
454 | return content
455 | }
456 |
457 | function delete_content(times) {
458 | for (let i = 0; i < times; i++) {
459 | $keyboard.delete()
460 | }
461 | }
462 |
463 | function set_bubble() {
464 | $("footer").symbol = multi_turn ? "bubble.left.and.bubble.right" : "bubble.left"
465 | }
466 |
467 | $delay(0.3, async () => {
468 | if (shortcut) {
469 | let user_content = await get_content(0)
470 | let shortcut_data = get_shortcut_data()
471 | if (shortcut_data.keys.length > 0) {
472 | let patt = new RegExp(`(${shortcut_data.keys.join("|")})$`)
473 | let shortcut_match = user_content.match(patt)
474 | if (shortcut_match && shortcut_data.role[shortcut_match[1]]) handler({ "title": shortcut_data.role[shortcut_match[1]], "info": {} }, shortcut_action, true)
475 | }
476 | }
477 | })
478 |
479 | function get_shortcut_data() {
480 | let shortcut_key = []
481 | let role = {}
482 | for (let i in role_data) {
483 | if (role_data[i].shortcut) {
484 | shortcut_key.push(role_data[i].shortcut)
485 | role[role_data[i].shortcut] = i
486 | }
487 | }
488 | return {
489 | keys: shortcut_key,
490 | role: role
491 | }
492 | }
493 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JSBox
--------------------------------------------------------------------------------
/Widgets/Contrigets.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Contrigets by Neurogram
4 |
5 | - Fill items in Input Value of widget (separated by commas)
6 | - username
7 | - username,green or username,blue (small widget & medium widget)
8 | - username,today
9 | - username,cube (rectangular widget)
10 | - Tap to open user GitHub page
11 |
12 | */
13 |
14 | const inputValue = $widget.inputValue;
15 | const theme_colors = {
16 | green: ["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"],
17 | blue: ["#ebedf0", "#79B8FE", "#2188FF", "#005CC5", "#044289"]
18 | }
19 |
20 | if (inputValue) {
21 | let username = inputValue.split(",")[0]
22 | let theme = inputValue.split(",")[1] && theme_colors[inputValue.split(",")[1]] ? theme_colors[inputValue.split(",")[1]] : theme_colors.green
23 | let url = "https://github.com/" + username
24 | let resp = await $http.get(url)
25 |
26 | let avatar = resp.data.match(/og:image" content="([^"]+)/)[1]
27 | let title = resp.data.match(/
.+\((.+)\).*<\/title>/)[1]
28 |
29 | $widget.setTimeline({
30 | render: ctx => {
31 | //$widget.family = 1
32 | const family = ctx.family;
33 | const width = $widget.displaySize.width
34 | const height = $widget.displaySize.height
35 |
36 | let contributions_data = resp.data.match(/data-date="\d{4}-\d\d-\d\d".+data-level="\d+".+\n.+/g).join('\n')
37 | let contributions = contributions_data.replace(/(data-date="[^"]+").+(data-level="[^"]+").+\n.+>(\d+|No) contributions* on.+/g, `$1 $2 data-count="$3"`).split('\n')
38 | contributions.sort()
39 |
40 | let day_left = 6 - new Date(contributions[contributions.length - 1].match(/data-date="(\d{4}-\d\d-\d\d)/)[1]).getDay();
41 | contributions = contributions.slice(-((family == 1 ? 20 : family == 6 ? 15 : 9) * 7 - day_left))
42 |
43 | let counter = contributions.join("\n").replace(/data-count="No/g, 'data-count="0').match(/data-count="\d+/g).join("\n")
44 | counter = counter.replace(/data-count="/g, "").split("\n")
45 |
46 | let colors_data = contributions.join("\n").match(/data-level="\d+/g).join("\n")
47 | colors_data = colors_data.replace(/data-level="/g, "").split("\n")
48 |
49 | let colors_row_spacing = family == 0 ? 2 : family == 6 ? 1 : 2 // row spacing (small : rectangular : medium)
50 | let colors_column_spacing = family == 0 ? 4 : family == 6 ? 1 : 5.05 // column spacing (small : rectangular : medium)
51 |
52 | let colors_view = []
53 | let colors_square_width = (width - 30 - 8 * colors_row_spacing) / 9 // family == 0
54 | if (family == 1) colors_square_width = (width - 30 - 19 * colors_row_spacing) / 20
55 | if (family == 6) colors_square_width = (height - 6) / 7
56 |
57 | for (var i = 0; i < colors_data.length; i++) {
58 | colors_view.push({
59 | type: "color",
60 | props: {
61 | light: theme[colors_data[i]],
62 | dark: colors_data[i] == "0" ? "#3E3E41" : theme[colors_data[i]],
63 | frame: {
64 | width: colors_square_width,
65 | height: colors_square_width
66 | },
67 | cornerRadius: 2
68 | }
69 | })
70 | }
71 |
72 | let inline_widget = {
73 | type: "text",
74 | props: {
75 | text: family < 5 ? `(${counter_sum(counter)} CONTRIBUTIONS)` : inputValue.split(",")[1] == "today" ? colors_data[colors_data.length - 1] : `${counter_sum(counter)}`,
76 | font: family > 4 ? $font("bold", 20) : $font(10),
77 | color: family > 4 ? $color("white") : $color("#9A9AA1"),
78 | minimumScaleFactor: 0.5,
79 | lineLimit: 1
80 | }
81 | }
82 |
83 | let rectangular_cube_widget = {
84 | type: "hgrid",
85 | props: {
86 | rows: Array(7).fill({
87 | flexible: {
88 | minimum: family == 6 ? 1 : 10,
89 | maximum: Infinity
90 | },
91 | spacing: colors_column_spacing
92 | }),
93 | spacing: colors_row_spacing
94 | },
95 | views: colors_view
96 | }
97 |
98 | let rectangular_avatar_widget = {
99 | type: "hstack",
100 | props: {
101 | alignment: $widget.verticalAlignment.center
102 | },
103 | views: [
104 | {
105 | type: "image",
106 | props: {
107 | uri: avatar,
108 | frame: {
109 | width: height - 8,
110 | height: height - 8
111 | },
112 | cornerRadius: {
113 | value: (height - 8) / 2,
114 | style: 0
115 | },
116 | resizable: true
117 | }
118 | },
119 | {
120 | type: "vstack",
121 | props: {
122 | alignment: $widget.horizontalAlignment.leading
123 | },
124 | views: [
125 | inline_widget,
126 | {
127 | type: "text",
128 | props: {
129 | text: "Contributions",
130 | font: $font(10),
131 | color: $color("#9A9AA1"),
132 | minimumScaleFactor: 0.5,
133 | lineLimit: 1
134 | }
135 | }
136 | ]
137 | }
138 | ]
139 | }
140 |
141 | if (family == 6) return inputValue.split(",")[1] == "cube" ? rectangular_cube_widget : rectangular_avatar_widget
142 | if (family == 5 || family == 7) return inline_widget
143 |
144 | if (family < 2) return {
145 | type: "vstack",
146 | props: {
147 | alignment: $widget.horizontalAlignment.leading,
148 | spacing: 10,
149 | frame: {
150 | width: width - 30,
151 | height: height
152 | },
153 | widgetURL: url
154 | },
155 | views: [
156 | {
157 | type: "hstack",
158 | props: {
159 | alignment: $widget.verticalAlignment.center,
160 | spacing: 3
161 | },
162 | views: [
163 | {
164 | type: "image",
165 | props: {
166 | uri: avatar,
167 | frame: {
168 | width: 20,
169 | height: 20
170 | },
171 | cornerRadius: {
172 | value: 10,
173 | style: 0
174 | },
175 | resizable: true
176 | }
177 | },
178 | {
179 | type: "text",
180 | props: {
181 | text: title.toUpperCase(),
182 | font: $font("bold", 13),
183 | color: $color("#9A9AA1"),
184 | minimumScaleFactor: 0.5,
185 | lineLimit: 1
186 | }
187 | },
188 | family == 0 ? null : inline_widget
189 | ]
190 | },
191 | rectangular_cube_widget
192 | ]
193 | }
194 | }
195 | })
196 | }
197 |
198 | function counter_sum(arr) {
199 | let total = 0
200 | for (var i = 0; i < arr.length; i++) {
201 | total += parseFloat(arr[i])
202 | }
203 | return total
204 | }
205 |
--------------------------------------------------------------------------------
/Widgets/Dougets.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Dougets by Neurogram
4 |
5 | - Medium widget only
6 | - Tap image to open movie web page of Douban
7 |
8 | */
9 |
10 | let resp = await $http.get({
11 | url: `https://frodo.douban.com/api/v2/calendar/today?apikey=0ab215a8b1977939201640fa14c66bab&date=${timefmt(new Date(), "yyyy-MM-dd")}&alt=json&_sig=tuOyn%2B2uZDBFGAFBLklc2GkuQk4%3D&_ts=1610703479`,
12 | header: {
13 | "User-Agent": "api-client/0.1.3 com.douban.frodo/6.50.0"
14 | }
15 | })
16 |
17 | let movie_data = resp.data
18 |
19 | $widget.setTimeline({
20 | render: ctx => {
21 | //$widget.family = 1
22 | const family = ctx.family;
23 | const width = $widget.displaySize.width
24 | const height = $widget.displaySize.height
25 |
26 | let poster_view = {
27 | type: "image",
28 | props: {
29 | uri: movie_data.comment.poster,
30 | resizable: true,
31 | scaledToFill: true,
32 | link: movie_data.subject.url
33 | }
34 | }
35 |
36 | let medium_widget = {
37 | type: "zstack",
38 | props: {
39 | alignment: $widget.alignment.center
40 | },
41 | views: [
42 | poster_view,
43 | {
44 | type: "color",
45 | props: {
46 | color: "black",
47 | opacity: 0.2
48 | }
49 | },
50 | {
51 | type: "vstack",
52 | props: {
53 | alignment: $widget.horizontalAlignment.leading,
54 | spacing: 5,
55 | frame: {
56 | width: width - 20,
57 | height: height
58 | }
59 | },
60 | views: [
61 | spacerMaker(height * 70 / 155, width - 20),
62 | {
63 | type: "hstack",
64 | props: {
65 | alignment: $widget.verticalAlignment.center,
66 | spacing: 0
67 | },
68 | views: [
69 | {
70 | type: "text",
71 | props: {
72 | text: `《${movie_data.subject.title}》`,
73 | font: $font("bold", 15),
74 | color: $color("white"),
75 | minimumScaleFactor: 0.5,
76 | lineLimit: 1
77 | }
78 | },
79 | {
80 | type: "zstack",
81 | props: {
82 | alignment: $widget.alignment.center,
83 | frame: {
84 | width: 70,
85 | height: 15
86 | }
87 | },
88 | views: [
89 | {
90 | type: "color",
91 | props: {
92 | color: $color("#FEAC2D"),
93 | cornerRadius: 7.5
94 | }
95 | },
96 | {
97 | type: "text",
98 | props: {
99 | text: `豆瓣评分 ${movie_data.subject.rating == null ? "无" : movie_data.subject.rating.value}`,
100 | font: $font("bold", 10),
101 | color: $color("black"),
102 | minimumScaleFactor: 0.5,
103 | lineLimit: 1
104 | }
105 | }
106 | ]
107 | }
108 |
109 | ]
110 | },
111 | {
112 | type: "text",
113 | props: {
114 | text: `❝ ${movie_data.comment.content}`,
115 | font: $font("bold", 12),
116 | color: $color("white"),
117 | minimumScaleFactor: 0.5,
118 | lineLimit: 2
119 | }
120 | }
121 | ]
122 | }
123 | ]
124 | }
125 | return family == 1 ? medium_widget : ""
126 | }
127 | })
128 |
129 | function spacerMaker(height, width) {
130 | return {
131 | type: "spacer",
132 | props: {
133 | frame: {
134 | width: width,
135 | height: height
136 | }
137 | }
138 | }
139 | }
140 |
141 | function timefmt(time, fmt) {
142 | var o = {
143 | "M+": time.getMonth() + 1, //月份
144 | "d+": time.getDate(), //日
145 | "h+": time.getHours(), //小时
146 | "m+": time.getMinutes(), //分
147 | "s+": time.getSeconds(), //秒
148 | "q+": Math.floor((time.getMonth() + 3) / 3), //季度
149 | "S": time.getMilliseconds() //毫秒
150 | };
151 | if (/(y+)/.test(fmt)) {
152 | fmt = fmt.replace(RegExp.$1, (time.getFullYear() + "").substr(4 - RegExp.$1.length));
153 | }
154 | for (var k in o) {
155 | if (new RegExp("(" + k + ")").test(fmt)) {
156 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
157 | }
158 | }
159 | return fmt;
160 | }
161 |
--------------------------------------------------------------------------------
/Widgets/Instagets.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Instagets by Neurogram
4 |
5 | - Fill Instagram web login header in line 16 of the script
6 | - Fill Instagram usernames in Input Value of widget (separated by commas)
7 | - Tap image to open post
8 | - Tap profile to open user profile (medium widget only)
9 | - Set language in line 20 of the script
10 | - Set random post in line 21 of the script
11 | - Set random children post in line 22 of the script
12 |
13 | */
14 |
15 | const inputValue = $widget.inputValue;
16 | const header = {
17 | "Cookie": ``,
18 | "X-IG-App-ID": ""
19 | }
20 | const language = "en" // en or cn
21 | let random_post_max = 1 // max range, 1 for lastest post
22 | const random_children_post = true // true or false
23 |
24 | const edge_type_label = {
25 | "en": ["Posts", "Followers", "Following"],
26 | "cn": ["帖子", "粉丝", "关注"]
27 | }
28 |
29 | if (inputValue) {
30 | let usernames = inputValue.split(",")
31 | let instagram_url = "https://i.instagram.com/api/v1/users/web_profile_info/?username=" + usernames[Random(0, usernames.length - 1)]
32 | let resp = await $http.get({
33 | url: instagram_url,
34 | header: header
35 | })
36 | let share_data = resp.data.data.user
37 |
38 | let counters = [numFormatter(share_data.edge_owner_to_timeline_media.count, 1), numFormatter(share_data.edge_followed_by.count, 1), numFormatter(share_data.edge_follow.count, 1)]
39 | let type = edge_type_label[language]
40 |
41 | let counter_view = []
42 | let type_view = []
43 |
44 | for (var i = 0; i < counters.length; i++) {
45 | counter_view.push({
46 | type: "text",
47 | props: {
48 | text: counters[i],
49 | font: $font("bold", 13),
50 | light: "#282828",
51 | dark: "white",
52 | minimumScaleFactor: 0.5,
53 | lineLimit: 1
54 | }
55 | })
56 | type_view.push({
57 | type: "text",
58 | props: {
59 | text: type[i],
60 | font: $font(10),
61 | color: $color("#aaaaaa"),
62 | minimumScaleFactor: 0.5,
63 | lineLimit: 1
64 | }
65 | })
66 | }
67 |
68 | $widget.setTimeline({
69 | render: ctx => {
70 | //$widget.family = 1
71 | const family = ctx.family;
72 | const width = $widget.displaySize.width
73 | const height = $widget.displaySize.height
74 | let share_total = share_data.edge_owner_to_timeline_media.edges
75 |
76 | if (random_post_max > share_total.length) random_post_max = share_total.length
77 |
78 | let current_share = share_total[Random(0, random_post_max - 1)].node
79 | let display_url = current_share.display_url
80 |
81 | if (random_children_post && current_share.edge_sidecar_to_children) {
82 | let children = current_share.edge_sidecar_to_children.edges
83 | display_url = children[Random(0, children.length - 1)].node.display_url
84 | }
85 |
86 | let small_widget = {
87 | type: "zstack",
88 | props: {
89 | alignment: $widget.alignment.center,
90 | frame: {
91 | width: height,
92 | height: height
93 | },
94 | clipped: true
95 | },
96 | views: [
97 | {
98 | type: "image",
99 | props: {
100 | uri: display_url,
101 | resizable: true,
102 | scaledToFill: true,
103 | widgetURL: "https://www.instagram.com/p/" + current_share.shortcode
104 | }
105 | },
106 | {
107 | type: "vstack",
108 | props: {
109 | alignment: $widget.horizontalAlignment.leading,
110 | spacing: 0
111 | },
112 | views: [
113 | spacerMaker(family == 2 ? height * 310 / 345 : height * 130 / 155, height),
114 | {
115 | type: "text",
116 | props: {
117 | text: `${family == 2 ? " " : " "}♥️ ${numFormatter(current_share.edge_liked_by.count, 1)} 💬 ${numFormatter(current_share.edge_media_to_comment.count, 1)}`,
118 | font: family == 2 ? $font(13) : $font(9),
119 | color: $color("white"),
120 | minimumScaleFactor: 0.5,
121 | lineLimit: 1
122 | }
123 | }
124 | ]
125 | }
126 | ]
127 | }
128 |
129 | let inline_widget = {
130 | type: "text",
131 | props: {
132 | text: family < 5 ? share_data.full_name : counters[1],
133 | font: $font("bold", 20),
134 | light: "#282828",
135 | dark: "white",
136 | minimumScaleFactor: 0.5,
137 | lineLimit: 1
138 | }
139 | }
140 |
141 | let rectangular_widget = {
142 | type: "hstack",
143 | props: {
144 | alignment: $widget.verticalAlignment.center
145 | },
146 | views: [
147 | {
148 | type: "image",
149 | props: {
150 | uri: share_data.profile_pic_url_hd,
151 | frame: {
152 | width: family < 5 ? 35 : height - 8,
153 | height: family < 5 ? 35 : height - 8
154 | },
155 | cornerRadius: {
156 | value: family < 5 ? 17.5 : (height - 8) / 2,
157 | style: 0
158 | },
159 | resizable: true
160 | }
161 | },
162 | {
163 | type: "vstack",
164 | props: {
165 | alignment: $widget.horizontalAlignment.leading
166 | },
167 | views: [
168 | inline_widget,
169 | {
170 | type: "text",
171 | props: {
172 | text: family < 5 ? "@" + share_data.username : type[1],
173 | font: $font(10),
174 | color: $color("#2481cc"),
175 | minimumScaleFactor: 0.5,
176 | lineLimit: 1
177 | }
178 | }
179 | ]
180 | }
181 | ]
182 | }
183 |
184 | let medium_widget = {
185 | type: "hstack",
186 | props: {
187 | alignment: $widget.verticalAlignment.center,
188 | spacing: 7.5
189 | },
190 | views: [
191 | small_widget,
192 | {
193 | type: "vstack",
194 | props: {
195 | alignment: $widget.horizontalAlignment.leading,
196 | spacing: 13,
197 | frame: {
198 | width: width - height - 15,
199 | height: height
200 | },
201 | link: "https://www.instagram.com/" + instagram_url.match(/username=(.+)/)[1]
202 | },
203 | views: [
204 | rectangular_widget,
205 | {
206 | type: "text",
207 | props: {
208 | text: share_data.biography,
209 | font: $font(10),
210 | light: "#282828",
211 | dark: "white",
212 | minimumScaleFactor: 0.5,
213 | lineLimit: 3
214 | }
215 | },
216 | {
217 | type: "vgrid",
218 | props: {
219 | columns: Array(counter_view.concat(type_view).length / 2).fill({
220 | flexible: {
221 | minimum: 10,
222 | maximum: Infinity
223 | }
224 | })
225 | },
226 | views: counter_view.concat(type_view)
227 | }
228 | ]
229 | },
230 | spacerMaker(height, 0)
231 | ]
232 | }
233 |
234 | if (family == 0 || family == 2) return small_widget
235 | if (family == 1) return medium_widget
236 | if (family == 5 || family == 7) return inline_widget
237 | if (family == 6) return rectangular_widget
238 | }
239 | })
240 | }
241 |
242 | function spacerMaker(height, width) {
243 | return {
244 | type: "spacer",
245 | props: {
246 | frame: {
247 | width: width,
248 | height: height
249 | }
250 | }
251 | }
252 | }
253 |
254 | function numFormatter(num, digits) {
255 | const si = [
256 | { value: 1, symbol: "" },
257 | { value: 1E3, symbol: "K" },
258 | { value: 1E6, symbol: "M" },
259 | { value: 1E9, symbol: "G" },
260 | { value: 1E12, symbol: "T" },
261 | { value: 1E15, symbol: "P" },
262 | { value: 1E18, symbol: "E" }
263 | ];
264 | const rx = /\.0+$|(\.[0-9]*[1-9])0+$/;
265 | let i;
266 | for (i = si.length - 1; i > 0; i--) {
267 | if (num >= si[i].value) {
268 | break;
269 | }
270 | }
271 | return (num / si[i].value).toFixed(digits).replace(rx, "$1") + si[i].symbol;
272 | }
273 |
274 | function Random(min, max) {
275 | return Math.round(Math.random() * (max - min)) + min;
276 | }
277 |
--------------------------------------------------------------------------------
/Widgets/Material/Contrigets.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Neurogram-R/JSBox/1a70d3c5f3a5e322dc4be1c2bc23af35fd62b7b5/Widgets/Material/Contrigets.jpg
--------------------------------------------------------------------------------
/Widgets/Material/Dougets.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Neurogram-R/JSBox/1a70d3c5f3a5e322dc4be1c2bc23af35fd62b7b5/Widgets/Material/Dougets.png
--------------------------------------------------------------------------------
/Widgets/Material/Instagets.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Neurogram-R/JSBox/1a70d3c5f3a5e322dc4be1c2bc23af35fd62b7b5/Widgets/Material/Instagets.jpg
--------------------------------------------------------------------------------
/Widgets/Material/Netgets.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Neurogram-R/JSBox/1a70d3c5f3a5e322dc4be1c2bc23af35fd62b7b5/Widgets/Material/Netgets.png
--------------------------------------------------------------------------------
/Widgets/Material/Telegets.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Neurogram-R/JSBox/1a70d3c5f3a5e322dc4be1c2bc23af35fd62b7b5/Widgets/Material/Telegets.jpg
--------------------------------------------------------------------------------
/Widgets/Netgets.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Netgets by Neurogram
4 |
5 | - Medium widget only
6 | - Fill Netflix country name in Input Value of widget (default USA)
7 | - Support countries list in line 16 of the script
8 | - Tap image to open movie web page of Netflix
9 |
10 | */
11 |
12 | const inputValue = $widget.inputValue;
13 |
14 | const default_country = "USA"
15 |
16 | const country_list = {
17 | "Australia": "australia",
18 | "Austria": "austria",
19 | "Bangladesh": "bangladesh",
20 | "Belgium": "belgium",
21 | "Brazil": "brazil",
22 | "Canada": "canada",
23 | "CostaRica": "costa-rica",
24 | "Denmark": "denmark",
25 | "Egypt": "egypt",
26 | "Finland": "finland",
27 | "France": "france",
28 | "Germany": "germany",
29 | "HongKong": "hong-kong",
30 | "India": "india",
31 | "Ireland": "ireland",
32 | "Israel": "israel",
33 | "Italy": "italy",
34 | "Japan": "japan",
35 | "Luxembourg": "luxembourg",
36 | "Mexico": "mexico",
37 | "Netherlands": "netherlands",
38 | "NewZealand": "new-zealand",
39 | "Norway": "norway",
40 | "Pakistan": "pakistan",
41 | "Panama": "panama",
42 | "Philippines": "philippines",
43 | "Portugal": "portugal",
44 | "Russia": "russia",
45 | "Singapore": "singapore",
46 | "SouthAfrica": "south-africa",
47 | "SouthKorea": "south-korea",
48 | "Spain": "spain",
49 | "Sweden": "sweden",
50 | "Switzerland": "switzerland",
51 | "Taiwan": "taiwan",
52 | "Thailand": "thailand",
53 | "USA": "usa",
54 | "UnitedKingdom": "united-kingdom"
55 | }
56 |
57 | let resp = await $http.get(`https://whatsnewonnetflix.com/${inputValue ? country_list[inputValue] : country_list[default_country]}`)
58 |
59 | let resp_data = resp.data.match(/(.+)/)[1]
67 | let link = resp_data[i].match(/https:\/\/netflix.com[^"]+/)[0]
68 |
69 | let entities = description.match(/\d{2,3};/g)
70 | if (entities) {
71 | for (var k in entities) {
72 | let rExp = new RegExp(entities[k], "g")
73 | description = description.replace(rExp, entityToString(entities[k]))
74 | }
75 | }
76 |
77 | movie_data.push([html_decode(title), poster, type.toUpperCase(), html_decode(description), link])
78 | }
79 | movie_data = movie_data[Random(0, movie_data.length - 1)]
80 |
81 | $widget.setTimeline({
82 | render: ctx => {
83 | //$widget.family = 1
84 | const family = ctx.family;
85 | const width = $widget.displaySize.width
86 | const height = $widget.displaySize.height
87 |
88 | let poster_view = {
89 | type: "image",
90 | props: {
91 | uri: movie_data[1],
92 | resizable: true,
93 | scaledToFill: true,
94 | link: movie_data[4]
95 | }
96 | }
97 |
98 | let medium_widget = {
99 | type: "zstack",
100 | props: {
101 | alignment: $widget.alignment.center
102 | },
103 | views: [
104 | poster_view,
105 | {
106 | type: "color",
107 | props: {
108 | color: "black",
109 | opacity: 0.3
110 | }
111 | },
112 | {
113 | type: "vstack",
114 | props: {
115 | alignment: $widget.horizontalAlignment.leading,
116 | spacing: 5,
117 | frame: {
118 | width: width - 20,
119 | height: height
120 | }
121 | },
122 | views: [
123 | spacerMaker(height * 70 / 155, width - 20),
124 | {
125 | type: "hstack",
126 | props: {
127 | alignment: $widget.verticalAlignment.center,
128 | spacing: 0
129 | },
130 | views: [
131 | {
132 | type: "text",
133 | props: {
134 | text: `${movie_data[0]} `,
135 | font: $font("bold", 15),
136 | color: $color("white"),
137 | minimumScaleFactor: 0.8,
138 | lineLimit: 1
139 | }
140 | },
141 | {
142 | type: "zstack",
143 | props: {
144 | alignment: $widget.alignment.center,
145 | frame: {
146 | width: 50,
147 | height: 15
148 | }
149 | },
150 | views: [
151 | {
152 | type: "color",
153 | props: {
154 | color: $color("#e50914"),
155 | cornerRadius: 7.5
156 | }
157 | },
158 | {
159 | type: "text",
160 | props: {
161 | text: `${movie_data[2]}`,
162 | font: $font("bold", 10),
163 | color: $color("white"),
164 | minimumScaleFactor: 0.8,
165 | lineLimit: 1
166 | }
167 | }
168 | ]
169 | }
170 |
171 | ]
172 | },
173 | {
174 | type: "text",
175 | props: {
176 | text: `${movie_data[3]}`,
177 | font: $font("bold", 12),
178 | color: $color("white"),
179 | minimumScaleFactor: 0.5,
180 | lineLimit: 3
181 | }
182 | }
183 | ]
184 | }
185 | ]
186 | }
187 | return family == 1 ? medium_widget : ""
188 | }
189 | })
190 |
191 | function spacerMaker(height, width) {
192 | return {
193 | type: "spacer",
194 | props: {
195 | frame: {
196 | width: width,
197 | height: height
198 | }
199 | }
200 | }
201 | }
202 |
203 | function Random(min, max) {
204 | return Math.round(Math.random() * (max - min)) + min;
205 | }
206 |
207 | function entityToString(entity) {
208 | let entities = entity.split(';')
209 | entities.pop()
210 | let tmp = entities.map(item => String.fromCharCode(
211 | item[2] === 'x' ? parseInt(item.slice(3), 16) : parseInt(item.slice(2)))).join('')
212 | return tmp
213 | }
214 |
215 | function html_decode(str) {
216 | if (str.length == 0) return ""
217 | return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/ /g, " ").replace(/´/g, "\'").replace(/"/g, "\"");
218 | }
219 |
--------------------------------------------------------------------------------
/Widgets/README.md:
--------------------------------------------------------------------------------
1 | # JSBox Widgets
2 |
3 | Widget|Small|Medium|Large|Circular|Rectangular|Inline|Install
4 | :-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:
5 | [Contrigets](https://github.com/Neurogram-R/JSBox/blob/main/Widgets/README.md#contrigets---github-contributions-widgets)|✅|✅||✅|✅|✅|[🔗](https://xteko.com/redir?url=https://raw.githubusercontent.com/Neurogram-R/JSBox/main/Widgets/Contrigets.js&name=Contrigets&author=Neurogram&icon=icon_177.png&version=1.0.0)
6 | [Dougets](https://github.com/Neurogram-R/JSBox/blob/main/Widgets/README.md#dougets---douban-movie-widgets)||✅|||||[🔗](https://xteko.com/redir?url=https://raw.githubusercontent.com/Neurogram-R/JSBox/main/Widgets/Dougets.js&name=Dougets&author=Neurogram&icon=icon_192.png&version=1.0.0)
7 | [Instagets](https://github.com/Neurogram-R/JSBox/blob/main/Widgets/README.md#instagets---instagram-widgets)|✅|✅|✅|✅|✅|✅|[🔗](https://xteko.com/redir?url=https://raw.githubusercontent.com/Neurogram-R/JSBox/main/Widgets/Instagets.js&name=Instagets&author=Neurogram&icon=icon_079.png&version=1.1.0)
8 | [Netgets](https://github.com/Neurogram-R/JSBox/blob/main/Widgets/README.md#netgets---netflix-widgets)||✅|||||[🔗](https://xteko.com/redir?url=https://raw.githubusercontent.com/Neurogram-R/JSBox/main/Widgets/Netgets.js&name=Netgets&author=Neurogram&icon=icon_114.png&version=1.0.0)
9 | [Telegets](https://github.com/Neurogram-R/JSBox/blob/main/Widgets/README.md#telegets---telegram-channel-widgets)|✅|✅||✅|✅|✅|[🔗](https://xteko.com/redir?url=https://raw.githubusercontent.com/Neurogram-R/JSBox/main/Widgets/Telegets.js&name=Telegets&author=Neurogram&icon=icon_172.png&version=1.0.0)
10 |
11 |
12 | ### [Contrigets - GitHub contributions widgets](https://xteko.com/redir?url=https://raw.githubusercontent.com/Neurogram-R/JSBox/main/Widgets/Contrigets.js&name=Contrigets&author=Neurogram&icon=icon_177.png&version=1.0.0)
13 | - Fill GitHub username and theme color in Input Value of widget (separated by commas)
14 | - Support theme color: green, blue
15 | - Tap to open user GitHub page
16 |
17 | 
18 |
19 |
20 | ### [Dougets - Douban movie widgets](https://xteko.com/redir?url=https://raw.githubusercontent.com/Neurogram-R/JSBox/main/Widgets/Dougets.js&name=Dougets&author=Neurogram&icon=icon_192.png&version=1.0.0)
21 | - Medium widget only
22 | - Tap image to open movie web page of Douban
23 |
24 | 
25 |
26 |
27 | ### [Instagets - Instagram widgets](https://xteko.com/redir?url=https://raw.githubusercontent.com/Neurogram-R/JSBox/main/Widgets/Instagets.js&name=Instagets&author=Neurogram&icon=icon_079.png&version=1.1.0)
28 | - Fill Instagram web login cookie in line 16 of the script
29 | - Fill Instagram usernames in Input Value of widget (separated by commas)
30 | - Tap image to open post
31 | - Tap profile to open user profile (medium widget only)
32 | - Set language in line 17 of the script
33 | - Set random post in line 18 of the script
34 | - Set random children post in line 19 of the script
35 |
36 | 
37 |
38 |
39 | ### [Netgets - Netflix widgets](https://xteko.com/redir?url=https://raw.githubusercontent.com/Neurogram-R/JSBox/main/Widgets/Netgets.js&name=Netgets&author=Neurogram&icon=icon_114.png&version=1.0.0)
40 | - Medium widget only
41 | - Fill Netflix country name in Input Value of widget (default USA)
42 | - Support countries list in line 16 of the script
43 | - Tap image to open movie web page of Netflix
44 |
45 | 
46 |
47 |
48 | ### [Telegets - Telegram channel widgets](https://xteko.com/redir?url=https://raw.githubusercontent.com/Neurogram-R/JSBox/main/Widgets/Telegets.js&name=Telegets&author=Neurogram&icon=icon_172.png&version=1.0.0)
49 | - Fill Telegram channel link path in Input Value of widget (separated by commas)
50 | - Tap to open Channel
51 |
52 | 
53 |
--------------------------------------------------------------------------------
/Widgets/Telegets.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Telegets by Neurogram
4 |
5 | - Fill Telegram channel link path in Input Value of widget (separated by commas)
6 | - Tap to open Channel
7 |
8 | */
9 |
10 | const inputValue = $widget.inputValue;
11 |
12 | if (inputValue) {
13 | let usernames = inputValue.split(",")
14 | let username = usernames[Random(0, usernames.length - 1)]
15 | let resp = await $http.get("https://t.me/s/" + username)
16 | let data = resp.data.match(/tgme_channel_info_header">(.|\n)+tgme_channel_download_telegram"/)[0]
17 | let logo = data.match(/https.+jpg/)[0]
18 | let title = data.match(/header_title">(.+)<\/span>/)[1]
19 | let entities = title.match(/\d{2,3};/g)
20 | if (entities) {
21 | for (var k in entities) {
22 | let rExp = new RegExp(entities[k], "g")
23 | title = title.replace(rExp, entityToString(entities[k]))
24 | }
25 | }
26 | let counters = data.match(/counter_value">.+?<\/span>/g)
27 | let type = data.match(/counter_type">.+?<\/span>/g)
28 | let counter_view = []
29 | let type_view = []
30 |
31 | for (var i in counters) {
32 | counter_view.push({
33 | type: "text",
34 | props: {
35 | text: counters[i].match(/>(.+))[1],
36 | font: $font("bold", 18),
37 | light: "#282828",
38 | dark: "white",
39 | minimumScaleFactor: 0.5,
40 | lineLimit: 1
41 | }
42 | })
43 | type_view.push({
44 | type: "text",
45 | props: {
46 | text: type[i].match(/>(.+))[1],
47 | font: $font(10),
48 | color: $color("#aaaaaa"),
49 | minimumScaleFactor: 0.5,
50 | lineLimit: 1
51 | }
52 | })
53 | }
54 |
55 | $widget.setTimeline({
56 | render: ctx => {
57 | //$widget.family = 0
58 | const family = ctx.family;
59 | const width = $widget.displaySize.width
60 | const height = $widget.displaySize.height
61 |
62 | const logo_view = {
63 | type: "image",
64 | props: {
65 | uri: logo,
66 | frame: {
67 | width: family == 6 ? height - 8 : 60,
68 | height: family == 6 ? height - 8 : 60
69 | },
70 | resizable: true,
71 | cornerRadius: 15
72 | }
73 | }
74 |
75 | const title_view = {
76 | type: "text",
77 | props: {
78 | text: title,
79 | font: family == 0 ? $font("bold", 20) : $font("bold", 25),
80 | light: "#282828",
81 | dark: "white",
82 | minimumScaleFactor: 0.5,
83 | lineLimit: 1
84 | }
85 | }
86 |
87 | const path_view = {
88 | type: "text",
89 | props: {
90 | text: "@" + username,
91 | font: $font(10),
92 | color: $color("#2481cc"),
93 | minimumScaleFactor: 0.5,
94 | lineLimit: 1
95 | }
96 | }
97 |
98 | const inline_widget = [counter_view[0]]
99 |
100 | const rectangular_widget = [
101 | {
102 | type: "hstack",
103 | props: {
104 | alignment: $widget.verticalAlignment.center,
105 | frame: {
106 | width: family == 6 ? width - 20 : width - 30,
107 | height: 60
108 | },
109 | spacing: 0
110 | },
111 | views: [
112 | logo_view,
113 | {
114 | type: "vstack",
115 | props: {
116 | alignment: $widget.horizontalAlignment.center,
117 | frame: {
118 | maxWidth: Infinity,
119 | maxHeight: Infinity
120 | },
121 | spacing: 0
122 | },
123 | views: [
124 | counter_view.concat(type_view)[0],
125 | counter_view.concat(type_view)[counter_view.concat(type_view).length / 2]
126 | ]
127 | }
128 | ]
129 | }
130 | ]
131 |
132 | const small_widget = [
133 | rectangular_widget[0],
134 | spacerMaker(18, width - 30),
135 | title_view,
136 | spacerMaker(3, width - 30),
137 | path_view
138 | ]
139 |
140 | const medium_widget = [
141 | {
142 | type: "hstack",
143 | props: {
144 | alignment: $widget.verticalAlignment.center,
145 | spacing: 0,
146 | frame: {
147 | width: width - 30,
148 | height: 60
149 | }
150 | },
151 | views: [
152 | logo_view,
153 | spacerMaker(60, 8),
154 | {
155 | type: "vstack",
156 | props: {
157 | alignment: $widget.horizontalAlignment.leading,
158 | spacing: 0,
159 | frame: {
160 | width: width - 98,
161 | height: 60
162 | }
163 | },
164 | views: [
165 | spacerMaker(0, width - 98),
166 | title_view,
167 | path_view
168 | ]
169 | }
170 |
171 | ]
172 | },
173 | spacerMaker(18, width - 30),
174 | {
175 | type: "vgrid",
176 | props: {
177 | columns: Array(counter_view.concat(type_view).length / 2).fill({
178 | flexible: {
179 | minimum: 10,
180 | maximum: Infinity
181 | },
182 | spacing: 0
183 | }),
184 | spacing: 0
185 | },
186 | views: counter_view.concat(type_view)
187 | }
188 | ]
189 |
190 | let current_view = small_widget
191 | if (family == 1) current_view = medium_widget
192 | if (family == 5 || family == 7) current_view = inline_widget
193 | if (family == 6) current_view = rectangular_widget
194 |
195 | return {
196 | type: "vstack",
197 | props: {
198 | alignment: $widget.horizontalAlignment.leading,
199 | frame: {
200 | width: family == 5 || family == 7 ? width : family == 6 ? width - 20 : width - 30,
201 | height: height
202 | },
203 | spacing: 0,
204 | widgetURL: "tg://resolve?domain=" + username
205 | },
206 | views: current_view
207 | }
208 | }
209 | })
210 | }
211 |
212 | function spacerMaker(height, width) {
213 | return {
214 | type: "spacer",
215 | props: {
216 | frame: {
217 | width: width,
218 | height: height
219 | }
220 | }
221 | }
222 | }
223 |
224 | function entityToString(entity) {
225 | let entities = entity.split(';')
226 | entities.pop()
227 | let tmp = entities.map(item => String.fromCharCode(
228 | item[2] === 'x' ? parseInt(item.slice(3), 16) : parseInt(item.slice(2)))).join('')
229 | return tmp
230 | }
231 |
232 | function Random(min, max) {
233 | return Math.round(Math.random() * (max - min)) + min;
234 | }
235 |
--------------------------------------------------------------------------------