Skip to content

Commit 345471b

Browse files
committed
Add Mastodon coupon posts
1 parent aacc313 commit 345471b

8 files changed

+101
-10
lines changed

.github/workflows/coupon-distribution.yml

+2
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,5 @@ jobs:
2626
REDDIT_ACCOUNT_PASSWORD: ${{ secrets.REDDIT_ACCOUNT_PASSWORD }}
2727
REDDIT_APP_ID: ${{ secrets.REDDIT_APP_ID }}
2828
REDDIT_APP_SECRET: ${{ secrets.REDDIT_APP_SECRET }}
29+
MASTODON_URL: ${{ secrets.MASTODON_URL }}
30+
MASTODON_TOKEN: ${{ secrets.MASTODON_TOKEN }}

README.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ juicy-coupon-bot
3030
> Next you need a BlueSky account
3131
> to which you obviously have to provide your own identifier (i.e. email) and app password for
3232
> the environment variables `BLUESKY_IDENTIFIER` and
33-
> `BLUESKY_PASSWORD`. For Reddit integration you additionally
34-
> need a [Reddit App](https://www.reddit.com/prefs/apps) and have to
33+
> `BLUESKY_PASSWORD`. For Mastodon integration you have to generate an [Access Token](https://docs.joinmastodon.org/client/authorized/) and pass it in via `MASTODON_TOKEN` along with the `MASTODON_URL` of your instance. For Reddit integration you need a [Reddit App](https://www.reddit.com/prefs/apps) and have to
3534
> define the environment variables `REDDIT_ACCOUNT_NAME`,
3635
> `REDDIT_ACCOUNT_PASSWORD`, `REDDIT_APP_ID` and `REDDIT_APP_SECRET`.
3736

lib/mastodonPost.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright (c) 2019-2024 Bjoern Kimminich & the OWASP Juice Shop contributors.
3+
* SPDX-License-Identifier: MIT
4+
*/
5+
6+
const logger = require('../lib/logger')
7+
const colors = require('colors')
8+
9+
module.exports = (discount, coupon, expiryDate) => {
10+
const texts = [
11+
`[🤖] Enjoy ${discount}% off all our juicy products with this #coupon code: ${coupon} (valid until ${expiryDate})`,
12+
`[🤖] Save ${discount}% during your next shopping frenzy with #coupon code: ${coupon} (expires ${expiryDate})`,
13+
`[🤖] All your favorite juices are now ${discount}% off! Only with #coupon code: ${coupon} (use before ${expiryDate})`,
14+
`[🤖] ${discount}% off!?! We must be crazy! Use our #coupon code before we come to our senses: ${coupon} (valid until ${expiryDate})`,
15+
`[🤖] You're not seriously gonna miss out on ${discount}% off our assortment of juices? Better redeem #coupon code: ${coupon} (latest on ${expiryDate})`
16+
]
17+
const status = texts[Math.floor(Math.random() * texts.length)]
18+
logger.info(`[${colors.green('✔')}] Mastodon post prepared: ${colors.cyan(status)}`)
19+
return status
20+
}

lib/publishBlueSky.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ module.exports = async (post) => {
2525
}
2626
})
2727

28-
logger.info(`[${colors.green('✔')}] Post published: ${res}`)
28+
logger.info(`[${colors.green('✔')}] Post published: ${JSON.stringify(res)}`)
2929
} catch (error) {
3030
logger.warn(`[${colors.red('❌')}] Post failed: ${colors.red(error)}`)
3131
}
3232
} else {
33-
logger.info(`[${colors.yellow('')}] Skipped BlueSky post: ${colors.yellow('Post will only be published when PUBLISHING_MODE is set as an environment variable')}`)
33+
logger.info(`[${colors.yellow('🟡')}] Skipped BlueSky post: ${colors.yellow('Post will only be published when PUBLISHING_MODE is set as an environment variable')}`)
3434
}
3535
}

lib/publishMastodon.js

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright (c) 2019-2024 Bjoern Kimminich & the OWASP Juice Shop contributors.
3+
* SPDX-License-Identifier: MIT
4+
*/
5+
6+
const logger = require('./logger')
7+
const colors = require('colors')
8+
import { createRestAPIClient } from "masto"
9+
const masto = createRestAPIClient({
10+
url: process.env.MASTODON_URL,
11+
accessToken: process.env.MASTODON_TOKEN,
12+
})
13+
14+
module.exports = async (status) => {
15+
if (process.env.PUBLISHING_MODE) {
16+
try {
17+
const res = await masto.v1.statuses.create({ status })
18+
logger.info(`[${colors.green('✔')}] Post published: ${res.url}`)
19+
} catch (error) {
20+
logger.warn(`[${colors.red('❌')}] Post failed: ${colors.red(error)}`)
21+
}
22+
} else {
23+
logger.info(`[${colors.yellow('🟡')}] Skipped Mastodon post: ${colors.yellow('Post will only be published when PUBLISHING_MODE is set as an environment variable')}`)
24+
}
25+
}

lib/publishReddit.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ const R = new RedditAPI(
1717
module.exports = (text, title) => {
1818
if (process.env.PUBLISHING_MODE) {
1919
R.api.post('/api/submit', { api_type: 'json', sr: 'owasp_juiceshop', kind: 'self', title, text })
20-
.then(function (response) {
21-
logger.info(`[${colors.green('✔')}] Reddit post published: ${JSON.stringify(response[1].json.data)}`)
20+
.then(function (res) {
21+
logger.info(`[${colors.green('✔')}] Reddit post published: ${res[1].json.data.url}`)
2222
})
2323
.catch(function (error) {
2424
logger.warn(`[${colors.red('❌')}] Reddit post failed: ${colors.red(error)}`)
2525
})
2626
} else {
27-
logger.info(`[${colors.yellow('')}] Skipped Reddit post: ${colors.yellow('Post will only be published when PUBLISHING_MODE is set as an environment variable')}`)
27+
logger.info(`[${colors.yellow('🟡')}] Skipped Reddit post: ${colors.yellow('Post will only be published when PUBLISHING_MODE is set as an environment variable')}`)
2828
}
2929
}

package.json

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "juicy-coupon-bot",
3-
"version": "2.0.0",
3+
"version": "2.1.0",
44
"description": "Coupon code generator and distribution bot for OWASP Juice Shop",
55
"homepage": "http://owasp-juice.shop",
66
"bugs": {
@@ -36,10 +36,9 @@
3636
"dependencies": {
3737
"@atproto/api": "^0.13.17",
3838
"colors": "^1.4.0",
39-
"fbgraph": "^1.4.4",
39+
"masto": "^6.10.1",
4040
"reddit-wrapper-v2": "^1.1.6",
4141
"sync-request": "^6.1.0",
42-
"twitter": "^1.7.1",
4342
"winston": "^3.2.1"
4443
},
4544
"devDependencies": {

test/mastodonPost-spec.js

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright (c) 2019-2024 Bjoern Kimminich & the OWASP Juice Shop contributors.
3+
* SPDX-License-Identifier: MIT
4+
*/
5+
6+
const chai = require('chai')
7+
const expect = chai.expect
8+
const mastodonPost = require('../lib/mastodonPost')
9+
10+
describe('Mastodon post', () => {
11+
it('should always include robot emoji', () => {
12+
for (let i = 0; i < 100; i++) {
13+
expect(mastodonPost()).to.include('[🤖]')
14+
}
15+
})
16+
17+
it('should always include #coupon hashtag', () => {
18+
for (let i = 0; i < 100; i++) {
19+
expect(mastodonPost()).to.include('#coupon')
20+
}
21+
})
22+
23+
it('should always mention discount amount', () => {
24+
for (let i = 10; i < 40; i++) {
25+
expect(mastodonPost(i)).to.include(i + '%')
26+
}
27+
})
28+
29+
it('should always contain coupon code', () => {
30+
for (let i = 0; i < 100; i++) {
31+
expect(mastodonPost(undefined, ':COUPON:')).to.include(':COUPON:')
32+
}
33+
})
34+
35+
it('should always mention expiration date', () => {
36+
for (let i = 0; i < 100; i++) {
37+
expect(mastodonPost(undefined, undefined, ':EXPIRATION:')).to.include(':EXPIRATION:')
38+
}
39+
})
40+
41+
it('should never be longer than 500 characters', () => {
42+
for (let i = 0; i < 100; i++) {
43+
expect(mastodonPost().length <= 500).to.equal(true)
44+
}
45+
})
46+
})

0 commit comments

Comments
 (0)