├── 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(/<div class="card card--md layout-2 (movie|season|show)(.|\n)*social-sharing/g) 60 | 61 | let movie_data = [] 62 | for (var i in resp_data) { 63 | let title = resp_data[i].match(/card__title.+title="([^"]+)/)[1] 64 | let poster = resp_data[i].match(/http.+\.(jpg|png)/)[0] 65 | let type = resp_data[i].match(/card card--md layout-2\s([^"]+)/)[1] 66 | let description = resp_data[i].match(/truncate">(.+)/)[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 | ![Contrigets](https://raw.githubusercontent.com/Neurogram-R/JSBox/main/Widgets/Material/Contrigets.jpg) 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 | ![Dougets](https://raw.githubusercontent.com/Neurogram-R/JSBox/main/Widgets/Material/Dougets.png) 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 | ![Instagets](https://raw.githubusercontent.com/Neurogram-R/JSBox/main/Widgets/Material/Instagets.jpg) 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 | ![Netgets](https://raw.githubusercontent.com/Neurogram-R/JSBox/main/Widgets/Material/Netgets.png) 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 | ![Telegets](https://raw.githubusercontent.com/Neurogram-R/JSBox/main/Widgets/Material/Telegets.jpg) 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 dir="auto">(.+)<\/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 | --------------------------------------------------------------------------------