├── img
└── screenshot.png
├── public
├── icon128.png
├── icon16.png
├── icon48.png
├── popup.html
└── vcbf.css
├── src
├── popup
│ ├── logo.png
│ ├── png.d.ts
│ ├── popup.tsx
│ ├── App.tsx
│ └── App.css
├── background
│ └── background.ts
└── content
│ └── content.ts
├── designs
└── icon.sketch
├── .babelrc
├── .gitignore
├── tsconfig.json
├── scripts
├── archive.js
├── publish-firefox.js
└── publish-chrome.js
├── manifests
├── webkit
│ └── manifest.json
└── firefox
│ └── manifest.json
├── LICENSE
├── package.json
├── webpack
├── webpack.config.firefox.js
└── webpack.config.webkit.js
└── README.md
/img/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Borodutch/bot-finder/HEAD/img/screenshot.png
--------------------------------------------------------------------------------
/public/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Borodutch/bot-finder/HEAD/public/icon128.png
--------------------------------------------------------------------------------
/public/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Borodutch/bot-finder/HEAD/public/icon16.png
--------------------------------------------------------------------------------
/public/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Borodutch/bot-finder/HEAD/public/icon48.png
--------------------------------------------------------------------------------
/src/popup/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Borodutch/bot-finder/HEAD/src/popup/logo.png
--------------------------------------------------------------------------------
/designs/icon.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Borodutch/bot-finder/HEAD/designs/icon.sketch
--------------------------------------------------------------------------------
/src/popup/png.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.png' {
2 | const content: any;
3 | export default content;
4 | }
5 |
--------------------------------------------------------------------------------
/public/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | VC bots finder
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/ .babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "modules": false
7 | }
8 | ],
9 | "@babel/preset-react"
10 | ],
11 | "plugins": [
12 | "react-hot-loader/babel"
13 | ]
14 | }
--------------------------------------------------------------------------------
/src/popup/popup.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import * as ReactDOM from "react-dom";
3 |
4 | import App from "./App";
5 |
6 | chrome.storage.local.get(['bots'], function (data) {
7 | ReactDOM.render(
8 | ,
9 | document.getElementById("popup")
10 | );
11 | });
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | /node_modules
3 | /.pnp
4 | .pnp.js
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 | /dist
12 |
13 | # misc
14 | .DS_Store
15 | .env.local
16 | .env.development.local
17 | .env.test.local
18 | .env.production.local
19 |
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 |
24 | .env
--------------------------------------------------------------------------------
/src/background/background.ts:
--------------------------------------------------------------------------------
1 | chrome.runtime.onInstalled.addListener(function (details) {
2 | chrome.storage.local.set({ bots: true });
3 | });
4 |
5 | chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
6 | chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
7 | if (tabs[0].id) {
8 | chrome.tabs.sendMessage(tabs[0].id, { action: 'RELOAD' }, function (_) {
9 | void chrome.runtime.lastError;
10 | });
11 | }
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./dist/",
4 | "sourceMap": true,
5 | "strict": true,
6 | "noImplicitReturns": true,
7 | "noImplicitAny": true,
8 | "module": "es6",
9 | "moduleResolution": "node",
10 | "allowSyntheticDefaultImports": true,
11 | "noImplicitThis": true,
12 | "strictNullChecks": true,
13 | "target": "es5",
14 | "allowJs": true,
15 | "jsx": "react",
16 | },
17 | "include": [
18 | "./src/**/*",
19 | "src/png.d.ts"
20 | ]
21 | }
--------------------------------------------------------------------------------
/scripts/archive.js:
--------------------------------------------------------------------------------
1 | const { ZipAFolder } = require('zip-a-folder');
2 | const fs = require('fs');
3 |
4 | const archiveDir = `${__dirname}/../build`;
5 | const archivePath = `${archiveDir}/${new Date().toISOString()}.zip`;
6 |
7 | async function release() {
8 | if (!fs.existsSync(archiveDir)) {
9 | fs.mkdirSync(archiveDir);
10 | }
11 | console.log('Archiving started...');
12 | try {
13 | await ZipAFolder.zip(`${__dirname}/../dist`, archivePath);
14 | console.log('Archiving finished.');
15 | } catch (error) {
16 | console.log('Error happened: ' + error);
17 | }
18 | }
19 |
20 | release();
21 |
--------------------------------------------------------------------------------
/src/popup/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import logo from "./logo.png";
3 | import "./App.css";
4 |
5 | interface IAppProps {
6 | bots?: boolean
7 | }
8 |
9 | const App = (props: IAppProps) => {
10 | const [bots, setBots] = React.useState(props.bots);
11 |
12 | React.useEffect(() => {
13 | chrome.storage.local.set({bots: bots});
14 | }, [bots])
15 |
16 | return (
17 |
26 | );
27 | };
28 |
29 | export default App;
30 |
--------------------------------------------------------------------------------
/scripts/publish-firefox.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const { zip } = require('zip-a-folder')
3 |
4 | async function release() {
5 | const timestamp = Date.now()
6 | console.log('Archiving the Firefox add-on...')
7 | await zip(
8 | `${__dirname}/../dist`,
9 | `/Users/nikitakolmogorov/Desktop/firefox.zip`
10 | )
11 | console.log(`Moving node_modules to /tmp/${timestamp}...`)
12 | fs.mkdirSync(`/tmp/${timestamp}`)
13 | fs.renameSync(
14 | `${__dirname}/../node_modules`,
15 | `/tmp/${timestamp}/node_modules`
16 | )
17 | console.log(`Archiving the Firefox sources...`)
18 | await zip(
19 | `${__dirname}/..`,
20 | `/Users/nikitakolmogorov/Desktop/firefox-sources.zip`
21 | )
22 | console.log(`Moving node_modules back...`)
23 | fs.renameSync(
24 | `/tmp/${timestamp}/node_modules`,
25 | `${__dirname}/../node_modules`
26 | )
27 | console.log(
28 | 'Add-on is archived, submit it at https://addons.mozilla.org/en-US/developers/addon/bot-finder/versions/submit/'
29 | )
30 | }
31 |
32 | release()
33 |
--------------------------------------------------------------------------------
/src/popup/App.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
4 | 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif,
5 | 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
6 | background-color: #111;
7 | }
8 |
9 | .App {
10 | font-size: 14px;
11 | }
12 |
13 | .App-header > p {
14 | padding: 10px 0 0 0;
15 | margin: 0;
16 | width: 100%;
17 | text-align: left;
18 | }
19 |
20 | .App-logo {
21 | pointer-events: none;
22 | width: 150px;
23 | }
24 |
25 | .App-header {
26 | background-color: #111;
27 | display: flex;
28 | flex-direction: column;
29 | align-items: center;
30 | justify-content: center;
31 | color: #fff;
32 | min-width: 100px;
33 | padding: 10px;
34 | }
35 |
36 | .App-settings {
37 | padding-top: 10px;
38 | width: 100%;
39 | display: flex;
40 | flex-direction: row;
41 | align-items: center;
42 | justify-content: space-between;
43 | }
44 |
45 | .App-link {
46 | color: #61dafb;
47 | }
48 |
--------------------------------------------------------------------------------
/manifests/webkit/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Это бот?",
3 | "description": "Простое расширение, которое позволяет быстро увидеть, бот ли пользователь в комментариях.",
4 | "manifest_version": 2,
5 | "version": "1.1.4",
6 | "icons": {
7 | "16": "icon16.png",
8 | "48": "icon48.png",
9 | "128": "icon128.png"
10 | },
11 | "browser_action": {
12 | "default_icon": {
13 | "16": "icon16.png",
14 | "48": "icon48.png"
15 | },
16 | "default_popup": "popup.html"
17 | },
18 | "permissions": [
19 | "storage",
20 | "tabs",
21 | "activeTab"
22 | ],
23 | "background": {
24 | "scripts": [
25 | "background.js"
26 | ],
27 | "persistent": true
28 | },
29 | "short_name": "VC bots",
30 | "content_scripts": [
31 | {
32 | "matches": [
33 | "*://vc.ru/*",
34 | "*://tjournal.ru/*",
35 | "*://dtf.ru/*"
36 | ],
37 | "js": [
38 | "content.js"
39 | ],
40 | "css": [
41 | "vcbf.css"
42 | ],
43 | "run_at": "document_idle"
44 | }
45 | ]
46 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Stanislav Nepomniashchikh
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.
--------------------------------------------------------------------------------
/manifests/firefox/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Это бот?",
3 | "description": "Простое расширение, которое позволяет быстро увидеть, бот ли пользователь в комментариях.",
4 | "manifest_version": 2,
5 | "version": "1.1.4",
6 | "icons": {
7 | "16": "icon16.png",
8 | "48": "icon48.png",
9 | "128": "icon128.png"
10 | },
11 | "browser_action": {
12 | "default_icon": {
13 | "16": "icon16.png",
14 | "48": "icon48.png"
15 | },
16 | "default_popup": "popup.html"
17 | },
18 | "applications": {
19 | "gecko": {
20 | "id": "me@stasn.ru",
21 | "strict_min_version": "45.0"
22 | }
23 | },
24 | "permissions": [
25 | "storage",
26 | "tabs",
27 | "activeTab"
28 | ],
29 | "background": {
30 | "scripts": [
31 | "background.js"
32 | ],
33 | "persistent": true
34 | },
35 | "short_name": "VC bots",
36 | "content_scripts": [
37 | {
38 | "matches": [
39 | "*://vc.ru/*",
40 | "*://tjournal.ru/*",
41 | "*://dtf.ru/*"
42 | ],
43 | "js": [
44 | "content.js"
45 | ],
46 | "css": [
47 | "vcbf.css"
48 | ],
49 | "run_at": "document_idle"
50 | }
51 | ]
52 | }
--------------------------------------------------------------------------------
/scripts/publish-chrome.js:
--------------------------------------------------------------------------------
1 | const dotenv = require('dotenv')
2 | dotenv.config({ path: `${__dirname}/../.env` })
3 | const fs = require('fs')
4 | const { zip } = require('zip-a-folder')
5 |
6 | const archivePath = `${__dirname}/../chrome.zip`
7 |
8 | async function release() {
9 | console.log('Archiving...')
10 | await zip(`${__dirname}/../dist`, archivePath)
11 | const webStore = require('chrome-webstore-upload')({
12 | extensionId: 'fbjbccjcmmnegakmjkklplmijeilnbhd',
13 | clientId: process.env.GOOGLE_ID,
14 | clientSecret: process.env.GOOGLE_SECRET,
15 | refreshToken: process.env.GOOGLE_REFRESH,
16 | })
17 | console.log('Getting an access token...')
18 | const token = await webStore.fetchToken()
19 | console.log('Uploading the archive...')
20 | const archive = fs.createReadStream(archivePath)
21 | const response = await webStore.uploadExisting(archive, token)
22 | if (response.itemError && response.itemError.length) {
23 | console.log('Error', response.itemError)
24 | process.exit(1)
25 | }
26 | console.log('Deleting the archive...')
27 | fs.unlinkSync(archivePath)
28 | console.log('Publishing the extension...')
29 | const status = (await webStore.publish('default', token)).status
30 | console.log('Finished with status', status)
31 | }
32 |
33 | release()
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bot-finder",
3 | "version": "1.0.4",
4 | "description": "Web extension that shows account creation date and last comments for users of vc.ru, dtf.ru and tjournal.ru in the comments.",
5 | "scripts": {
6 | "increment-version": "versiony package.json --patch && versiony manifests/firefox/manifest.json --patch && versiony manifests/webkit/manifest.json --patch && git add . && git commit -m 'new version' && git push",
7 | "build-webkit": "webpack --mode=production --config webpack/webpack.config.webkit.js",
8 | "build-firefox": "webpack --mode=production --config webpack/webpack.config.firefox.js",
9 | "archive": "node scripts/archive.js",
10 | "publish-chrome": "node scripts/publish-chrome.js",
11 | "publish-firefox": "node scripts/publish-firefox.js",
12 | "build-and-publish": "yarn increment-version && yarn build-webkit && yarn publish-chrome && yarn build-firefox && yarn publish-firefox"
13 | },
14 | "keywords": [
15 | "chrome",
16 | "opera",
17 | "firefox",
18 | "safari",
19 | "extension",
20 | "vc.ru",
21 | "vc bots finder"
22 | ],
23 | "license": "MIT",
24 | "devDependencies": {
25 | "@babel/core": "^7.12.3",
26 | "@babel/preset-env": "^7.12.1",
27 | "@babel/preset-react": "^7.12.1",
28 | "@hot-loader/react-dom": "^17.0.0-rc.2",
29 | "@types/chrome": "0.0.125",
30 | "@types/react": "^16.9.53",
31 | "@types/react-dom": "^16.9.8",
32 | "babel-loader": "^8.1.0",
33 | "chrome-webstore-upload": "^0.4.4",
34 | "copy-webpack-plugin": "^6.2.1",
35 | "css-loader": "^5.0.0",
36 | "dotenv": "^8.2.0",
37 | "file-loader": "^6.1.1",
38 | "style-loader": "^2.0.0",
39 | "ts-loader": "^8.0.5",
40 | "typescript": "^4.0.3",
41 | "url-loader": "^4.1.1",
42 | "versiony": "^2.0.1",
43 | "webpack": "^5.1.3",
44 | "webpack-cli": "^4.0.0",
45 | "webpack-dev-server": "^3.11.0",
46 | "zip-a-folder": "^0.0.12"
47 | },
48 | "dependencies": {
49 | "react": "^16.14.0",
50 | "react-dom": "^16.14.0",
51 | "react-hot-loader": "^4.13.0"
52 | }
53 | }
--------------------------------------------------------------------------------
/webpack/webpack.config.firefox.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const path = require('path')
3 | const CopyPlugin = require('copy-webpack-plugin')
4 |
5 | const config = {
6 | entry: {
7 | popup: path.join(__dirname, '../src/popup/popup.tsx'),
8 | content: path.join(__dirname, '../src/content/content.ts'),
9 | background: path.join(__dirname, '../src/background/background.ts'),
10 | },
11 | output: {
12 | path: path.join(__dirname, '../dist'),
13 | filename: '[name].js',
14 | clean: true,
15 | },
16 | module: {
17 | rules: [
18 | {
19 | test: /\.(js|jsx)$/,
20 | use: 'babel-loader',
21 | exclude: /node_modules/,
22 | },
23 | {
24 | test: /\.css$/,
25 | use: ['style-loader', 'css-loader'],
26 | exclude: /\.module\.css$/,
27 | },
28 | {
29 | test: /\.ts(x)?$/,
30 | loader: 'ts-loader',
31 | exclude: /node_modules/,
32 | },
33 | {
34 | test: /\.css$/,
35 | use: [
36 | 'style-loader',
37 | {
38 | loader: 'css-loader',
39 | options: {
40 | importLoaders: 1,
41 | modules: true,
42 | },
43 | },
44 | ],
45 | include: /\.module\.css$/,
46 | },
47 | {
48 | test: /\.svg$/,
49 | use: 'file-loader',
50 | },
51 | {
52 | test: /\.png$/,
53 | use: [
54 | {
55 | loader: 'url-loader',
56 | options: {
57 | mimetype: 'image/png',
58 | },
59 | },
60 | ],
61 | },
62 | ],
63 | },
64 | resolve: {
65 | extensions: ['.js', '.jsx', '.tsx', '.ts'],
66 | alias: {
67 | 'react-dom': '@hot-loader/react-dom',
68 | },
69 | },
70 | devServer: {
71 | contentBase: './dist',
72 | },
73 | plugins: [
74 | new CopyPlugin({
75 | patterns: [
76 | { from: 'public', to: '.' },
77 | { from: 'manifests/firefox/manifest.json', to: '.' },
78 | ],
79 | }),
80 | ],
81 | }
82 |
83 | module.exports = config
84 |
--------------------------------------------------------------------------------
/webpack/webpack.config.webkit.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const path = require('path')
3 | const CopyPlugin = require('copy-webpack-plugin')
4 |
5 | const config = {
6 | entry: {
7 | popup: path.join(__dirname, '../src/popup/popup.tsx'),
8 | content: path.join(__dirname, '../src/content/content.ts'),
9 | background: path.join(__dirname, '../src/background/background.ts'),
10 | },
11 | output: {
12 | path: path.join(__dirname, '../dist'),
13 | filename: '[name].js',
14 | clean: true,
15 | },
16 | module: {
17 | rules: [
18 | {
19 | test: /\.(js|jsx)$/,
20 | use: 'babel-loader',
21 | exclude: /node_modules/,
22 | },
23 | {
24 | test: /\.css$/,
25 | use: ['style-loader', 'css-loader'],
26 | exclude: /\.module\.css$/,
27 | },
28 | {
29 | test: /\.ts(x)?$/,
30 | loader: 'ts-loader',
31 | exclude: /node_modules/,
32 | },
33 | {
34 | test: /\.css$/,
35 | use: [
36 | 'style-loader',
37 | {
38 | loader: 'css-loader',
39 | options: {
40 | importLoaders: 1,
41 | modules: true,
42 | },
43 | },
44 | ],
45 | include: /\.module\.css$/,
46 | },
47 | {
48 | test: /\.svg$/,
49 | use: 'file-loader',
50 | },
51 | {
52 | test: /\.png$/,
53 | use: [
54 | {
55 | loader: 'url-loader',
56 | options: {
57 | mimetype: 'image/png',
58 | },
59 | },
60 | ],
61 | },
62 | ],
63 | },
64 | resolve: {
65 | extensions: ['.js', '.jsx', '.tsx', '.ts'],
66 | alias: {
67 | 'react-dom': '@hot-loader/react-dom',
68 | },
69 | },
70 | devServer: {
71 | contentBase: './dist',
72 | },
73 | plugins: [
74 | new CopyPlugin({
75 | patterns: [
76 | { from: 'public', to: '.' },
77 | { from: 'manifests/webkit/manifest.json', to: '.' },
78 | ],
79 | }),
80 | ],
81 | }
82 |
83 | module.exports = config
84 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Bot finder
2 |
3 |
4 |
5 |
6 |
7 |
8 | Web extension that shows account creation date and other info for users of vc.ru, TJ and DTF in the comments.
9 |
10 |
11 |
12 | Website • Chrome Web Store • Firefox Browser Add-ons
13 |
14 |
15 |
16 | ## Getting started
17 |
18 | Navigate to the project directory and install the dependencies.
19 |
20 | ```
21 | $ yarn
22 | ```
23 |
24 | ### To build the extension for Chrome and Opera, run
25 |
26 | ```
27 | $ yarn build-webkit && yarn archive
28 | ```
29 |
30 | ### To build the extension for Safari
31 |
32 | You need to have XCode installed on your Mac.
33 |
34 | Follow instructions for [Chrome](https://github.com/backmeupplz/bot-finder/#to-build-the-extension-for-chrome-and-opera-run), then follow these instructions from Apple:
35 |
36 | https://developer.apple.com/documentation/safariservices/safari_web_extensions/converting_a_web_extension_for_safari. The command you need to use is probably going to look like this:
37 |
38 | ```
39 | xcrun safari-web-extension-converter /Volumes/Experiments/bot-finder/dist/
40 | ```
41 |
42 | If you don't have developer certificate, you'll also need to follow these instructions:
43 | https://developer.apple.com/documentation/safariservices/safari_app_extensions/building_a_safari_app_extension#2957926 (see "Enable Your App Extension in Safari").
44 |
45 | ### To build the extension for Firefox, run
46 |
47 | ```
48 | $ yarn build-firefox && yarn archive
49 | ```
50 |
51 | After the project has been built, a directories named `dist` and `build` (with build archive) have been created.
52 |
53 | ## Publications
54 | - [vc.ru](https://vc.ru/tribuna/226020-eto-bot-rasshirenie-dlya-brauzera-pokazyvayushchee-bolshe-informacii-o-kommentatorah-na-vc-ru)
55 | - [TJ](https://tjournal.ru/flood/362516-my-napisali-rasshirenie-dlya-brauzera-pokazyvayushchee-bolshe-informacii-o-kommentatorah-na-tjournal)
56 | - [DTF](https://dtf.ru/flood/699403-my-napisali-rasshirenie-dlya-brauzera-pokazyvayushchee-bolshe-informacii-o-kommentatorah-na-dtf)
57 |
--------------------------------------------------------------------------------
/public/vcbf.css:
--------------------------------------------------------------------------------
1 | .__vcbf {
2 | position: relative;
3 | -webkit-user-select: none;
4 | -moz-user-select: none;
5 | -ms-user-select: none;
6 | user-select: none;
7 | }
8 |
9 | .__vcbf__popup {
10 | position: absolute;
11 | right: 0;
12 | top: 27px;
13 | color: #000;
14 | background-color: #fff;
15 | -webkit-box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.07),
16 | 0 4px 8px 0 rgba(0, 0, 0, 0.07);
17 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.07), 0 4px 8px 0 rgba(0, 0, 0, 0.07);
18 | text-align: left;
19 | font-size: 13px;
20 | border-radius: 5px;
21 | z-index: 6;
22 | min-width: 100px;
23 | }
24 |
25 | .__vcbf__popup:before {
26 | content: '';
27 | position: absolute;
28 | width: 10px;
29 | height: 10px;
30 | background: #fff;
31 | -webkit-transform: rotate(45deg);
32 | transform: rotate(45deg);
33 | top: -5px;
34 | right: 23px;
35 | -webkit-box-shadow: -1px -1px 0 0 rgba(0, 0, 0, 0.07);
36 | box-shadow: -1px -1px 0 0 rgba(0, 0, 0, 0.07);
37 | }
38 |
39 | .__vcbf__popup-preloader {
40 | padding: 10px;
41 | color: #595959;
42 | }
43 |
44 | .__vcbf__popup-header {
45 | display: flex;
46 | justify-content: space-between;
47 | align-items: center;
48 | padding: 10px;
49 | border-bottom: 1px solid #f8f8f8;
50 | line-height: 1.3;
51 | }
52 |
53 | .__vcbf__popup-header_name {
54 | font-size: 14px;
55 | overflow: hidden;
56 | font-weight: 700;
57 | text-overflow: ellipsis;
58 | white-space: nowrap;
59 | }
60 |
61 | .__vcbf__popup-header_karma {
62 | margin-left: 10px;
63 | text-align: center;
64 | padding: 2px 6px 1px 6px;
65 | min-width: 24px;
66 | color: #595959;
67 | background-color: #eee;
68 | border-radius: 5px;
69 | }
70 |
71 | .__vcbf__popup-header_karma--positive {
72 | color: #0a1;
73 | background-color: #eefbf3;
74 | }
75 |
76 | .__vcbf__popup-header_karma--negative {
77 | color: #cd192e;
78 | background-color: rgba(89,89,89,.1);
79 | }
80 |
81 | .__vcbf__popup-content {
82 | padding: 10px;
83 | margin-bottom: -5px;
84 | }
85 |
86 | .__vcbf__popup-content > div {
87 | white-space: nowrap;
88 | padding-bottom: 5px;
89 | }
90 |
91 | .__vcbf__popup-content_date {
92 | padding-bottom: 10px !important;
93 | }
94 |
95 | .__vcbf__popup-content > div > span {
96 | display: inline !important;
97 | margin: 0 !important;
98 | font-weight: 500;
99 | }
100 |
101 | @media only screen and (max-width: 600px) {
102 | .__vcbf__popup {
103 | max-width: 200px;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/content/content.ts:
--------------------------------------------------------------------------------
1 | const parseUser = async (url: string | null) => {
2 | if (!url) return null;
3 | let userInfo: NodeListOf | undefined;
4 | await fetch(url)
5 | .then((response) => response.text())
6 | .then((html) => {
7 | let parser = new DOMParser();
8 | let doc = parser.parseFromString(html, 'text/html');
9 | userInfo = doc.querySelectorAll('textarea.l-hidden');
10 | });
11 | if (userInfo !== undefined) return JSON.parse(userInfo[0].innerHTML);
12 | return null;
13 | };
14 |
15 | const runScript = async () => {
16 | const commentNames = Array.from(
17 | document.querySelectorAll('.comments__item__space')
18 | );
19 |
20 | for (let username of commentNames) {
21 | const userDom = username.getElementsByClassName('comments__item__user')[0];
22 | const userLink = userDom ? userDom.getAttribute('href') : null;
23 |
24 | if (userLink) {
25 | const botButton = document.createElement('div');
26 | const botDropdown = document.createElement('div');
27 | botDropdown.className = '__vcbf__popup';
28 | botDropdown.style.display = 'none';
29 | botButton.className = 'comments__item__reply __vcbf';
30 | botButton.innerHTML = 'Это бот?';
31 | botButton.onclick = async () => {
32 | window.addEventListener('click', function (e) {
33 | if (
34 | e.target &&
35 | !(e.target).classList.contains('__vcbf')
36 | ) {
37 | if (botDropdown.style.display === 'block') {
38 | botDropdown.style.display = 'none';
39 | }
40 | }
41 | });
42 |
43 | if (botDropdown.style.display === 'none') {
44 | // Preloader
45 | let html = '';
48 | botDropdown.innerHTML = html;
49 | botDropdown.style.display = 'block';
50 | // Load user data
51 | const userData = await parseUser(userLink);
52 | // Parse user data
53 | const user = {
54 | name: userData.header.subsiteData.name,
55 | karma: userData.header.subsiteData.karma || 0,
56 | registerDate: userData.header.stats[0].label,
57 | commentsCount: userData.header.tabs[1].counter,
58 | articlesCount: userData.header.tabs[0].counter,
59 | isPremium: userData.header.subsiteData.isPlus,
60 | };
61 | // Styling karma
62 | let karmaClass = ' ';
63 | if (user.karma > 0) {
64 | karmaClass += '__vcbf__popup-header_karma--positive';
65 | user.karma = '+' + user.karma;
66 | } else if (user.karma < 0) {
67 | karmaClass += '__vcbf__popup-header_karma--negative';
68 | }
69 | // Set popup header
70 | html = '';
71 | html += '';
75 | // Set popup content
76 | html += '';
84 |
85 | botDropdown.innerHTML = html;
86 | } else {
87 | botDropdown.style.display = 'none';
88 | }
89 | };
90 | username
91 | .getElementsByClassName('comments__item__self')[0]
92 | .appendChild(botButton);
93 | username.getElementsByClassName('__vcbf')[0].appendChild(botDropdown);
94 | }
95 | }
96 | };
97 |
98 | chrome.storage.local.get(['bots'], function (data) {
99 | if (data.bots) {
100 | chrome.runtime.onMessage.addListener(function (
101 | request,
102 | sender,
103 | sendResponse
104 | ) {
105 | const botButtonsToRemove = Array.from(
106 | document.querySelectorAll('.__vcbf')
107 | );
108 | for (let botButton of botButtonsToRemove) {
109 | botButton.remove();
110 | }
111 | runScript();
112 | });
113 | }
114 | });
115 |
--------------------------------------------------------------------------------