| Commit message (Collapse) | Author | Age | Lines | 
| ...                      |  | 
| | | | | | | | | | | | |  | 
 | 
| | |\ \ \ \ \ \ \ \ \ \ \  
| | | |_|_|_|_|_|_|/ / /  
| | |/| | | | | | | | |    | 
 | 
| | | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | |  | 
The destination channel can be retrieved be accessing a message's guild.
* Remove unused queue_consumption_tasks attribute.
 | 
| | | | | | | | | | | | |  | 
 | 
| | | | | | | | | | | | |  | 
 | 
| | | | | | | | | | | | |  | 
 | 
| | | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | |  | 
This makes it more likely to successfully save an attachment after it's
been deleted.
 | 
| | | | | | | | | | | | |  | 
 | 
| | | | | | | | | | | | |  | 
 | 
| | | | | | | | | | | | |  | 
 | 
| | | | | | | | | | | | |  | 
 | 
| | | | | | | | | | | | |  | 
 | 
| | |\ \ \ \ \ \ \ \ \ \ \  
| | | |_|_|_|_|_|_|_|_|/  
| | |/| | | | | | | | |    | 
 | 
| | | | | | | | | | | | |  | 
 | 
| | | | | | | | | | | | |  | 
 | 
| | | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | |  | 
Co-Authored-By: Mark <[email protected]>
 | 
| | | | | | | | | | | | |  | 
 | 
| | | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | |  | 
So they are re-uploaded before being deleted
 | 
| | | | | | | | | | | | |  | 
 | 
| | | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | |  | 
Co-authored-by: Shirayuki Nekomata <[email protected]>
 | 
| | | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | |  | 
Before sending the attachments to API for logging, we now re-post them in the channel that have the id
stored in the constant Guild.attachment_repost (it needs to be configured). These new links will never expires.
 | 
| | | | | |\ \ \ \ \ \ \ \  
| |_|_|_|/ / / / / / / /  
|/| | | | | | | | | | |    | 
 | 
| | | | | | | | | | | | |  | 
 | 
| | | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | |  | 
- Added regex back to sub and split by non-alphabet.
- Now use two pointers to move from words to words.
 | 
| | | | | | | | | | | | |  | 
 | 
| | | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | |  | 
- Since it is returning as soon as there are suggestions found for a threshold, this will give a better reflection of what the bot thinks user is searching for.
 | 
| | | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | |  | 
- Added a regex to remove non-alphabet ( `[^a-z]` with `re.IGNORECASE` )
 | 
| | | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | |  | 
- Matching scores will be calculated once now and stored in the dict `scores`.
- Allow `_get_suggestions()` to go through a list of score threshold and return the first list of matching tags that's not empty and above the threshold. This avoid calling the function multiple time like before ( `self._get_suggestions(tag_name, 100) or self._get_suggestions(tag_name, 80)` for example, is calling this function twice, and is inefficient )
- Deleted commented line.
- Added `typing` module for more typehints.
 | 
| | | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | |  | 
- Changed type of `self._last_fetch` to `float` and give it the initial value of `0.0` instead of `None`
- Assigned `time.time()` to `time_now` to avoid calling this function twice.
- Added `self._last_fetch = time_now` after calling the api call.
 | 
| | | | | | |\ \ \ \ \ \ \  
| |_|_|_|_|/ / / / / / /  
|/| | | | | | | | | | |    | 
 | 
| | | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | |  | 
#### Closes #231
Applying the algorithm for `Needles and Haystack` to find and match tag in tags, for example:

This only applies to searching tag_name with more than 3 in length, and at least 80% of its letters are found, from left to right.
There are 3 levels of checking, stop at first found:
- Check if exact name ( case insensitive ) O(1) getting from a dictionary Dict[str, Tag]
- Check for all tags that has 100% matching via algorithm
- Check for all tags that has >= 80% matching
If there are more than one hit, it will be shown as suggestions:

In order to avoid api being called multiple times, I've implemented a cache to only refresh itself when the is a gap of more than 5 minutes from the last api call to get all tags.
Editing / Adding / Deleting tags will also modify the cache directly.
##### What about other solution like fuzzywuzzy?
fuzzywuzzy was considered for using, but from testing, it was giving much lower scores than expected:
Code used to test:
```py
from fuzzywuzzy import fuzz
def _fuzzy_search(search: str, target: str) -> bool:
    found = 0
    index = 0
    _search = search.lower().replace(' ', '')
    _target = target.lower().replace(' ', '')
    for letter in _search:
        index = _target.find(letter, index)
        if index == -1:
            break
        found += index > 0
    # return found / len(_search) * 100
    return (
        found / len(_search) * 100,
        fuzz.ratio(search, target),
        fuzz.partial_ratio(search, target)
    )
tests = (
    'this-is-gonna-be-fun',
    'this-too-will-be-fun'
)
for test in tests:
    print(test, '->', _fuzzy_search('this too fun', test))
```
Result from test:
```py
this-is-gonna-be-fun -> (30.0, 50, 50)
this-too-will-be-fun -> (90.0, 62, 58)
```
 | 
| | | | | | | | | | | | |  | 
 | 
| | | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | |  | 
Fix incorrect docstring and comment
Co-Authored-By: Mark <[email protected]>
 | 
| | | | | | | | | | | | |  | 
 | 
| | | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | | 
| | | | | | | | | | | |  | 
This reverts commit 56696b3b1858ad27dc7f3dce2898c7a6eb151f43.
 | 
| | | | | | | | | | | | |  | 
 | 
| | | | | | | | | | | | |  | 
 | 
| | | | | | | | | | | | |  | 
 | 
| | | | | | | | | | | | |  | 
 | 
| | | | | | | | | | | | |  | 
 | 
| | | | | | | | |\ \ \ \ \  
| |_|_|_|_|_|_|/ / / / /  
|/| | | | | | | | | | |    | 
 | 
| | |_|_|/ / / / / / / /  
|/| | | | | | | | | |    | 
 | 
| |\ \ \ \ \ \ \ \ \ \ \  
| | | | | | | | | | | | 
| | | | | | | | | | | |  | 
Update config-default.yml - Allow .md files
 | 
| |/ / / / / / / / / / /   | 
 | 
| | | | | | | | | | | |  | 
 | 
| |\ \ \ \ \ \ \ \ \ \ \  
| |_|_|_|_|_|_|_|/ / /  
|/| | | | | | | | | |    | 
Add additional resources to the test readme
 | 
| | | | | | | | | | | | 
| | | | | | | | | | | 
| | | | | | | | | | |  | 
Move the link to Ned Batchelder’s talk and link the note to the section
 | 
| |/ / / / / / / / / /   | 
 | 
| | | | | | | | | | | 
| | | | | | | | | | 
| | | | | | | | | | 
| | | | | | | | | | 
| | | | | | | | | |  | 
This will prevent child classes to be instantiated unless they implement
all abstract methods, leading to a more descriptive error message.
 | 
| | | | | | | | | | |  | 
 |