├── .gitattributes
├── icon.png
├── index.html
├── README.md
├── LICENSE
├── package.json
├── .github
└── workflows
│ └── publish.yml
├── .gitignore
└── index.ts
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sawhney17/logseq-twitter-extractor/HEAD/icon.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | logseq-custom-workflows
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Logseq Twitter Extractor Plugin
2 | >If this plugin helps you, I'd really appreciate your support. You can [buy me a coffee here. ](https://www.buymeacoffee.com/sawhney17)
3 | A quick utility to parse a twitter link into a post with a user specified template. Inspired by the Roam Smartblock for twitter extraction.
4 | 
5 |
6 | ## Instructions
7 | 1. Navigate to a block with the twitter URL
8 | 2. Use the slash command `/Parse Twitter URL`
9 | 3. Enjoy :)
10 |
11 | ## Configuration
12 | 1. In plugin settings, you can configure custom templates
13 | 2. Use {URL}, {Name}, {Username}, {Date}, and {Tweet} as placeholders in the template which will be auto replaced
14 | 3. Template 1 is the block that the logseq block with URL will be replaced into
15 | 4. Template 2 is an optional block that will be indented under the first block
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 sawhney17
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "logseq-twitter-extractor",
3 | "version": "1.0.15",
4 | "description": "Twitter Extractor",
5 | "main": "dist/index.html",
6 | "targets": {
7 | "main": false
8 | },
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1",
11 | "build": "parcel build --no-source-maps index.html --public-url ./"
12 | },
13 | "keywords": [],
14 | "author": "Aryan Sawhney",
15 | "license": "MIT",
16 | "dependencies": {
17 | "@logseq/libs": "0.0.1-alpha.35",
18 | "axios": "^0.26.1",
19 | "logseq-dateutils": "^0.0.20",
20 | "twitter": "^1.7.1",
21 | "v2-twitter": "^1.0.23"
22 | },
23 | "logseq": {
24 | "id": "logseq-twitter-extractor",
25 | "title": "logseq-twitter-extractor",
26 | "icon": "./icon.png"
27 | },
28 | "devDependencies": {
29 | "assert": "^2.0.0",
30 | "browserify-zlib": "^0.2.0",
31 | "buffer": "^6.0.3",
32 | "crypto-browserify": "^3.12.0",
33 | "events": "^3.3.0",
34 | "https-browserify": "^1.0.0",
35 | "parcel": "^2.0.0",
36 | "path-browserify": "^1.0.1",
37 | "querystring-es3": "^0.2.1",
38 | "stream-browserify": "^3.0.0",
39 | "stream-http": "^3.2.0",
40 | "url": "^0.11.0"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Build plugin
2 |
3 | on:
4 | push:
5 | # Sequence of patterns matched against refs/tags
6 | tags:
7 | - '*' # Push events to matching any tag format, i.e. 1.0, 20.15.10
8 |
9 | env:
10 | PLUGIN_NAME: logseq-twitter-extractor
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v2
18 | - name: Use Node.js
19 | uses: actions/setup-node@v1
20 | with:
21 | node-version: '16.x' # You might need to adjust this value to your own version
22 | - name: Build
23 | id: build
24 | env:
25 | BEARERTOKEN: ${{ secrets.BEARERTOKEN }}
26 | run: |
27 | npm i && npm run build
28 | mkdir ${{ env.PLUGIN_NAME }}
29 | cp README.md package.json icon.png ${{ env.PLUGIN_NAME }}
30 | mv dist ${{ env.PLUGIN_NAME }}
31 | zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }}
32 | ls
33 | echo "::set-output name=tag_name::$(git tag --sort version:refname | tail -n 1)"
34 | - name: Create Release
35 | uses: ncipollo/release-action@v1
36 | id: create_release
37 | env:
38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
39 | VERSION: ${{ github.ref }}
40 | with:
41 | allowUpdates: true
42 | draft: false
43 | prerelease: false
44 |
45 | - name: Upload zip file
46 | id: upload_zip
47 | uses: actions/upload-release-asset@v1
48 | env:
49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
50 | with:
51 | upload_url: ${{ steps.create_release.outputs.upload_url }}
52 | asset_path: ./${{ env.PLUGIN_NAME }}.zip
53 | asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip
54 | asset_content_type: application/zip
55 |
56 | - name: Upload package.json
57 | id: upload_metadata
58 | uses: actions/upload-release-asset@v1
59 | env:
60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
61 | with:
62 | upload_url: ${{ steps.create_release.outputs.upload_url }}
63 | asset_path: ./package.json
64 | asset_name: package.json
65 | asset_content_type: application/json
66 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | # Logs
3 | logs
4 | *.log
5 | npm-debug.log*
6 | yarn-debug.log*
7 | yarn-error.log*
8 | lerna-debug.log*
9 | .pnpm-debug.log*
10 |
11 | # Diagnostic reports (https://nodejs.org/api/report.html)
12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
13 |
14 | # Runtime data
15 | pids
16 | *.pid
17 | *.seed
18 | *.pid.lock
19 |
20 | # Directory for instrumented libs generated by jscoverage/JSCover
21 | lib-cov
22 |
23 | # Coverage directory used by tools like istanbul
24 | coverage
25 | *.lcov
26 |
27 | # nyc test coverage
28 | .nyc_output
29 |
30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
31 | .grunt
32 |
33 | # Bower dependency directory (https://bower.io/)
34 | bower_components
35 |
36 | # node-waf configuration
37 | .lock-wscript
38 |
39 | # Compiled binary addons (https://nodejs.org/api/addons.html)
40 | build/Release
41 |
42 | # Dependency directories
43 | node_modules/
44 | jspm_packages/
45 |
46 | # Snowpack dependency directory (https://snowpack.dev/)
47 | web_modules/
48 |
49 | # TypeScript cache
50 | *.tsbuildinfo
51 |
52 | # Optional npm cache directory
53 | .npm
54 |
55 | # Optional eslint cache
56 | .eslintcache
57 |
58 | # Microbundle cache
59 | .rpt2_cache/
60 | .rts2_cache_cjs/
61 | .rts2_cache_es/
62 | .rts2_cache_umd/
63 |
64 | # Optional REPL history
65 | .node_repl_history
66 |
67 | # Output of 'npm pack'
68 | *.tgz
69 |
70 | # Yarn Integrity file
71 | .yarn-integrity
72 |
73 | # dotenv environment variables file
74 | .env
75 | .env.test
76 | .env.production
77 |
78 | # parcel-bundler cache (https://parceljs.org/)
79 | .cache
80 | .parcel-cache
81 |
82 | # Next.js build output
83 | .next
84 | out
85 |
86 | # Nuxt.js build / generate output
87 | .nuxt
88 | dist
89 |
90 | # Gatsby files
91 | .cache/
92 | # Comment in the public line in if your project uses Gatsby and not Next.js
93 | # https://nextjs.org/blog/next-9-1#public-directory-support
94 | # public
95 |
96 | # vuepress build output
97 | .vuepress/dist
98 |
99 | # Serverless directories
100 | .serverless/
101 |
102 | # FuseBox cache
103 | .fusebox/
104 |
105 | # DynamoDB Local files
106 | .dynamodb/
107 |
108 | # TernJS port file
109 | .tern-port
110 |
111 | # Stores VSCode versions used for testing VSCode extensions
112 | .vscode-test
113 |
114 | # yarn v2
115 | .yarn/cache
116 | .yarn/unplugged
117 | .yarn/build-state.yml
118 | .yarn/install-state.gz
119 | .pnp.*
120 | yarn.lock
121 | package-lock.json
122 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | import "@logseq/libs";
2 | import { SettingSchemaDesc } from "@logseq/libs/dist/LSPlugin.user";
3 | import axios from "axios";
4 | import 'logseq-dateutils'
5 | import { getDateForPage, getDateForPageWithoutBrackets } from "logseq-dateutils";
6 |
7 | let BEARERTOKEN = process.env.BEARERTOKEN
8 | const defaultKey = "mod+shift+x"
9 | const baseURL = "https://api.twitter.com/2/tweets/";
10 | const tweetRegex = /https:\/\/twitter.com.\S*/g
11 |
12 | let settings: SettingSchemaDesc[] = [{
13 | key: "InsertionTemplateForBlock1",
14 | type: "string",
15 | title: "Insertion template for block 1",
16 | description: "Enter your desired template for the parent block, created by default for every return value of the query. Options are URL, Name, Username, Date, and Tweet",
17 | default: "{TWEET} {Date}, [[{Name}]]"
18 | },
19 | {
20 | key: "InsertionTemplateForBlock2",
21 | type: "string",
22 | title: "Insertion template for block 2",
23 | description: "Enter your desired template for the child block, created by default only if template is present, for every return value of the query. Options are URL, Name, Username, Date, and Tweet",
24 | default: ""
25 | },
26 | { //add option for keyboard shortcut
27 | key: "KeyboardShortcut",
28 | type: "string",
29 | title: "Keyboard Shortcut",
30 | description: `Enter your desired keyboard shortcut for the command, default (${defaultKey})`,
31 | default: defaultKey
32 | }
33 | ]
34 |
35 | async function formatDate(dateText) {
36 | var dateObject = new Date(dateText)
37 |
38 | const dateFormat = (await logseq.App.getUserConfigs()).preferredDateFormat
39 | console.log(dateObject)
40 | console.log(dateFormat)
41 | const date = getDateForPageWithoutBrackets(dateObject, dateFormat)
42 | console.log(date)
43 | return date
44 | }
45 | logseq.useSettingsSchema(settings);
46 |
47 | async function templateBlocks(data, template, url) {
48 | var finalString = template
49 | console.log(data)
50 | let replacements = {
51 | "URL": url,
52 | "Date": await formatDate(data.data[0].created_at),
53 | "Tweet": data.data[0].text,
54 | "Name": data["includes"]["users"][0]["name"],
55 | "Username": data["includes"]["users"][0]["username"],
56 | }
57 | for (let key in replacements) {
58 | let regexString = new RegExp(`{${key}}`, "gi")
59 | finalString = finalString.replaceAll(regexString, replacements[key])
60 | }
61 | return finalString
62 | }
63 | async function parseTweet(id, uuid, url) {
64 | axios.get(`https://api.twitter.com/2/tweets?ids=${id}&tweet.fields=created_at&expansions=author_id&user.fields=created_at`, {
65 | 'headers': {
66 | "Authorization": `Bearer ${BEARERTOKEN}`
67 | }
68 | }).then(async (result) => {
69 | logseq.Editor.updateBlock(uuid, await templateBlocks(result.data, logseq.settings.InsertionTemplateForBlock1, url))
70 | if (logseq.settings.InsertionTemplateForBlock2 != "") {
71 | logseq.Editor.insertBlock(uuid, await templateBlocks(result.data, logseq.settings.InsertionTemplateForBlock2, url), { sibling: false })
72 | }
73 | })
74 | }
75 | async function detectURL(e) {
76 | try {
77 | let url = (await logseq.Editor.getBlock(e.uuid)).content.match(tweetRegex)
78 | let id = url[0].split("/")[5].split("?")[0]
79 | parseTweet(id, e.uuid, url)
80 | }
81 | catch {
82 | logseq.App.showMsg(
83 | "Error: No URL detected",
84 | )
85 | }
86 | }
87 |
88 | const main = async () => {
89 | console.log("plugin loaded");
90 | logseq.Editor.registerSlashCommand("Parse Twitter URL", async (e) => {
91 | detectURL(e);
92 | });
93 |
94 | const registerKeyXtract = () => logseq.App.registerCommandPalette({
95 | key: "ParseTwitter",
96 | label: "Parse Twitter URL(s)",
97 | keybinding: {
98 | mode: "global",
99 | binding: logseq.settings.KeyboardShortcut.toLowerCase()
100 | },
101 | }, (e) => {
102 | if (e.uuid != null) {
103 | detectURL(e);
104 | }
105 | else {
106 | logseq.Editor.getSelectedBlocks().then((blocks) => {
107 | for (const x in blocks){
108 | detectURL(blocks[x])
109 | }
110 | }
111 | )
112 | }
113 | })
114 |
115 | logseq.onSettingsChanged((_updated) => {
116 | logseq.App.unregister_plugin_simple_command(`${logseq.baseInfo.id}/KeyboardShortcut`)
117 | registerKeyXtract()
118 | });
119 | };
120 |
121 | logseq.ready(main).catch(console.error);
122 |
--------------------------------------------------------------------------------