├── .env.example
├── .idea
├── vcs.xml
├── modules.xml
└── misc.xml
├── Pipfile
├── start.py
├── fb-anti-unsend.iml
├── src
├── _settings.py
└── _client.py
├── LICENSE
├── README.md
├── .gitignore
└── Pipfile.lock
/.env.example:
--------------------------------------------------------------------------------
1 | FB_EMAIL = you@example.com
2 | FB_PASSWORD = secret
3 | FB_USE_SESSION = false
4 | FB_SESSION_FILE = session.json
5 |
6 | IGNORE_SELF = true
7 | ALWAYS_ACTIVE = false
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | name = "pypi"
3 | url = "https://pypi.org/simple"
4 | verify_ssl = true
5 |
6 | [dev-packages]
7 |
8 | [packages]
9 | python-dotenv = "*"
10 | fbchat = "*"
11 |
12 | [requires]
13 | python_version = "3.6"
14 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/start.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from src._client import Client
4 | from src._settings import FB_EMAIL, FB_PASSWORD, FB_SESSION, ALWAYS_ACTIVE
5 |
6 |
7 | if __name__ == "__main__":
8 | client = Client(FB_EMAIL, FB_PASSWORD, session_cookies=FB_SESSION)
9 | client.listen(ALWAYS_ACTIVE)
10 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/fb-anti-unsend.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/_settings.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import json
4 | import os
5 |
6 | from dotenv import load_dotenv
7 |
8 | load_dotenv()
9 |
10 |
11 | def is_true(a):
12 | return str(a).lower() in ("true", "yes", "1")
13 |
14 |
15 | FB_EMAIL = os.getenv("FB_EMAIL")
16 | FB_PASSWORD = os.getenv("FB_PASSWORD")
17 | FB_USE_SESSION = is_true(os.getenv("FB_USE_SESSION"))
18 | FB_SESSION_FILE = os.getenv("FB_SESSION_FILE")
19 |
20 | if FB_USE_SESSION:
21 | with open(FB_SESSION_FILE) as f:
22 | FB_SESSION = json.loads(f.read())
23 |
24 | IGNORE_SELF = is_true(os.getenv("IGNORE_SELF"))
25 | ALWAYS_ACTIVE = is_true(os.getenv("ALWAYS_ACTIVE"))
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Kacper Ziubryniewicz
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Facebook Messenger anti-unsend
2 |
3 | ## Description
4 | Are you getting angry when someone unsends the message and you can't see it? This project is for you! When someone will unsend the message, you'll get it immediately in your conversation with yourself.
5 |
6 | ## Installation
7 | Clone the repo
8 | ```console
9 | $ git clone https://github.com/kapi2289/fb-anti-unsend.git
10 | $ cd fb-anti-unsend
11 | ```
12 |
13 | Create the `.env` file and edit it with your favourite text editor
14 | ```console
15 | $ cp .env.example .env
16 | $ nano .env
17 | ```
18 |
19 | Install dependencies
20 | ```console
21 | $ pip install --user pipenv
22 | $ pipenv install
23 | ```
24 |
25 | And now you can run it!
26 | ```console
27 | $ pipenv run python start.py
28 | ```
29 |
30 | ## Tips
31 | This project is using `fbchat` library. You can get your session, turn it to JSON and save it to the `session.json` file
32 | ```python
33 | import fbchat
34 | import json
35 | client = fbchat.Client("you@example.com", "password")
36 | session = client.getSession()
37 |
38 | with open("session.json", 'w') as f:
39 | f.write(json.dumps(session))
40 | ```
41 |
42 | And then you need to set the `FB_USE_SESSION` option to `true` in your `.env` file
43 |
--------------------------------------------------------------------------------
/src/_client.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import re
4 | import time
5 |
6 | import fbchat
7 | import requests
8 | from fbchat.models import *
9 |
10 | from ._settings import *
11 |
12 |
13 | class Client(fbchat.Client):
14 |
15 | def __init__(self, *args, **kwargs):
16 | super(Client, self).__init__(*args, **kwargs)
17 | self.messages = list()
18 | self.setDefaultThread(self.uid, ThreadType.USER)
19 |
20 | def onMessage(self, mid, message_object, thread_id, **kwargs):
21 | message_object.uid = mid
22 | self.messages.append(message_object)
23 | for message in self.messages:
24 | ts = (time.time() - 10 * 60) * 1000
25 | if message.timestamp < ts:
26 | self.messages = list(filter(lambda x: x is not message, self.messages))
27 |
28 | def onMessageUnsent(self, mid, author_id, **kwargs):
29 | if IGNORE_SELF and author_id == self.uid:
30 | return
31 | for message in self.messages:
32 | if message.uid == mid:
33 | files = []
34 | unsendable_files = []
35 | for a in message.attachments:
36 | if isinstance(a, ImageAttachment):
37 | if a.is_animated:
38 | files.append(a.animated_preview_url)
39 | else:
40 | url = a.large_preview_url or a.preview_url or a.thumbnail_url
41 | if url:
42 | files.append(url)
43 | elif isinstance(a, VideoAttachment):
44 | files.append(a.preview_url)
45 | elif isinstance(a, AudioAttachment):
46 | unsendable_files.append(a.url)
47 | elif isinstance(a, FileAttachment):
48 | r = requests.get(a.url)
49 | if r.status_code == 200:
50 | url = re.search("document\.location\.replace\(\"(.*)\"\);", r.text).group(1)
51 | url = url.replace('\\/', '/')
52 | files.append(url)
53 |
54 | author = self.fetchUserInfo(message.author)[message.author]
55 | message.reply_to_id = None
56 | self.send(Message("{} unsent the message:".format(author.name),
57 | mentions=[Mention(author.uid, length=len(author.name))]))
58 | if message.text or message.sticker:
59 | self.send(message)
60 | if unsendable_files:
61 | self.sendMessage("Attachments: \n{}".format("\n----------\n".join(unsendable_files)))
62 | if files:
63 | self.sendRemoteFiles(files)
64 | self.messages = list(filter(lambda x: x is not message, self.messages))
65 | break
66 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/python,intellij
3 | # Edit at https://www.gitignore.io/?templates=python,intellij
4 |
5 | ### Intellij ###
6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
8 |
9 | # User-specific stuff
10 | .idea/**/workspace.xml
11 | .idea/**/tasks.xml
12 | .idea/**/usage.statistics.xml
13 | .idea/**/dictionaries
14 | .idea/**/shelf
15 |
16 | # Generated files
17 | .idea/**/contentModel.xml
18 |
19 | # Sensitive or high-churn files
20 | .idea/**/dataSources/
21 | .idea/**/dataSources.ids
22 | .idea/**/dataSources.local.xml
23 | .idea/**/sqlDataSources.xml
24 | .idea/**/dynamic.xml
25 | .idea/**/uiDesigner.xml
26 | .idea/**/dbnavigator.xml
27 |
28 | # Gradle
29 | .idea/**/gradle.xml
30 | .idea/**/libraries
31 |
32 | # Gradle and Maven with auto-import
33 | # When using Gradle or Maven with auto-import, you should exclude module files,
34 | # since they will be recreated, and may cause churn. Uncomment if using
35 | # auto-import.
36 | # .idea/modules.xml
37 | # .idea/*.iml
38 | # .idea/modules
39 |
40 | # CMake
41 | cmake-build-*/
42 |
43 | # Mongo Explorer plugin
44 | .idea/**/mongoSettings.xml
45 |
46 | # File-based project format
47 | *.iws
48 |
49 | # IntelliJ
50 | out/
51 |
52 | # mpeltonen/sbt-idea plugin
53 | .idea_modules/
54 |
55 | # JIRA plugin
56 | atlassian-ide-plugin.xml
57 |
58 | # Cursive Clojure plugin
59 | .idea/replstate.xml
60 |
61 | # Crashlytics plugin (for Android Studio and IntelliJ)
62 | com_crashlytics_export_strings.xml
63 | crashlytics.properties
64 | crashlytics-build.properties
65 | fabric.properties
66 |
67 | # Editor-based Rest Client
68 | .idea/httpRequests
69 |
70 | # Android studio 3.1+ serialized cache file
71 | .idea/caches/build_file_checksums.ser
72 |
73 | # JetBrains templates
74 | **___jb_tmp___
75 |
76 | ### Intellij Patch ###
77 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
78 |
79 | # *.iml
80 | # modules.xml
81 | # .idea/misc.xml
82 | # *.ipr
83 |
84 | # Sonarlint plugin
85 | .idea/sonarlint
86 |
87 | ### Python ###
88 | # Byte-compiled / optimized / DLL files
89 | __pycache__/
90 | *.py[cod]
91 | *$py.class
92 |
93 | # C extensions
94 | *.so
95 |
96 | # Distribution / packaging
97 | .Python
98 | build/
99 | develop-eggs/
100 | dist/
101 | downloads/
102 | eggs/
103 | .eggs/
104 | lib/
105 | lib64/
106 | parts/
107 | sdist/
108 | var/
109 | wheels/
110 | pip-wheel-metadata/
111 | share/python-wheels/
112 | *.egg-info/
113 | .installed.cfg
114 | *.egg
115 | MANIFEST
116 |
117 | # PyInstaller
118 | # Usually these files are written by a python script from a template
119 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
120 | *.manifest
121 | *.spec
122 |
123 | # Installer logs
124 | pip-log.txt
125 | pip-delete-this-directory.txt
126 |
127 | # Unit test / coverage reports
128 | htmlcov/
129 | .tox/
130 | .nox/
131 | .coverage
132 | .coverage.*
133 | .cache
134 | nosetests.xml
135 | coverage.xml
136 | *.cover
137 | .hypothesis/
138 | .pytest_cache/
139 |
140 | # Translations
141 | *.mo
142 | *.pot
143 |
144 | # Django stuff:
145 | *.log
146 | local_settings.py
147 | db.sqlite3
148 |
149 | # Flask stuff:
150 | instance/
151 | .webassets-cache
152 |
153 | # Scrapy stuff:
154 | .scrapy
155 |
156 | # Sphinx documentation
157 | docs/_build/
158 |
159 | # PyBuilder
160 | target/
161 |
162 | # Jupyter Notebook
163 | .ipynb_checkpoints
164 |
165 | # IPython
166 | profile_default/
167 | ipython_config.py
168 |
169 | # pyenv
170 | .python-version
171 |
172 | # pipenv
173 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
174 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
175 | # having no cross-platform support, pipenv may install dependencies that don’t work, or not
176 | # install all needed dependencies.
177 | #Pipfile.lock
178 |
179 | # celery beat schedule file
180 | celerybeat-schedule
181 |
182 | # SageMath parsed files
183 | *.sage.py
184 |
185 | # Environments
186 | .env
187 | .venv
188 | env/
189 | venv/
190 | ENV/
191 | env.bak/
192 | venv.bak/
193 |
194 | # Spyder project settings
195 | .spyderproject
196 | .spyproject
197 |
198 | # Rope project settings
199 | .ropeproject
200 |
201 | # mkdocs documentation
202 | /site
203 |
204 | # mypy
205 | .mypy_cache/
206 | .dmypy.json
207 | dmypy.json
208 |
209 | # Pyre type checker
210 | .pyre/
211 |
212 | # End of https://www.gitignore.io/api/python,intellij
213 |
214 | # Facebook session file
215 | session.json
216 |
217 | # nohup logs
218 | nohup.out
219 |
--------------------------------------------------------------------------------
/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "5a273e33bf637b4d3ae366742c06c4e8e1c9fd465fe6987b832e6d40bbea8f46"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.6"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {
19 | "aenum": {
20 | "hashes": [
21 | "sha256:260225470b49429f5893a195a8b99c73a8d182be42bf90c37c93e7b20e44eaae",
22 | "sha256:aaebe735508d9cbc72cd6adfb59660a5e676dfbeb6fb24fb090041e7ddb8d3b3",
23 | "sha256:f9d20f7302ce3dc3639b3f75c3b3e146f3b22409a6b4513c1f0bd6dbdfcbd8c1"
24 | ],
25 | "version": "==2.2.6"
26 | },
27 | "attrs": {
28 | "hashes": [
29 | "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6",
30 | "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"
31 | ],
32 | "markers": "python_version >= '3.5'",
33 | "version": "==22.1.0"
34 | },
35 | "beautifulsoup4": {
36 | "hashes": [
37 | "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30",
38 | "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"
39 | ],
40 | "markers": "python_version >= '3.6'",
41 | "version": "==4.11.1"
42 | },
43 | "certifi": {
44 | "hashes": [
45 | "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3",
46 | "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"
47 | ],
48 | "index": "pypi",
49 | "version": "==2022.12.7"
50 | },
51 | "charset-normalizer": {
52 | "hashes": [
53 | "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597",
54 | "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"
55 | ],
56 | "markers": "python_version >= '3'",
57 | "version": "==2.0.12"
58 | },
59 | "fbchat": {
60 | "hashes": [
61 | "sha256:112fe2c20320a278258afacf7f466cd2fd34dc9da49fd0b999467464be6486b8",
62 | "sha256:18c21b0b86c697a16975a78033d4690083d8668235005c56a023de4f4f744394"
63 | ],
64 | "index": "pypi",
65 | "version": "==1.9.1"
66 | },
67 | "idna": {
68 | "hashes": [
69 | "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4",
70 | "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"
71 | ],
72 | "markers": "python_version >= '3'",
73 | "version": "==3.4"
74 | },
75 | "paho-mqtt": {
76 | "hashes": [
77 | "sha256:2a8291c81623aec00372b5a85558a372c747cbca8e9934dfe218638b8eefc26f"
78 | ],
79 | "version": "==1.6.1"
80 | },
81 | "python-dotenv": {
82 | "hashes": [
83 | "sha256:debd928b49dbc2bf68040566f55cdb3252458036464806f4094487244e2a4093",
84 | "sha256:f157d71d5fec9d4bd5f51c82746b6344dffa680ee85217c123f4a0c8117c4544"
85 | ],
86 | "index": "pypi",
87 | "version": "==0.10.3"
88 | },
89 | "requests": {
90 | "hashes": [
91 | "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61",
92 | "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"
93 | ],
94 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
95 | "version": "==2.27.1"
96 | },
97 | "soupsieve": {
98 | "hashes": [
99 | "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759",
100 | "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"
101 | ],
102 | "markers": "python_version >= '3.6'",
103 | "version": "==2.3.2.post1"
104 | },
105 | "urllib3": {
106 | "hashes": [
107 | "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc",
108 | "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"
109 | ],
110 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
111 | "version": "==1.26.13"
112 | }
113 | },
114 | "develop": {}
115 | }
116 |
--------------------------------------------------------------------------------