diff --git a/BuildData.groovy b/BuildData.groovy index 62666e24..469e5796 100644 --- a/BuildData.groovy +++ b/BuildData.groovy @@ -76,6 +76,7 @@ def getNamePermutations(names) { res = res.findResults{ fn(it) } } out += res + out = out.findAll{ it.length() >= 2 && !(it =~ /^[a-z]/) && it =~ /^[.\p{L}\p{Digit}]/ } // MUST START WITH UNICODE LETTER out = out.unique{ it.toLowerCase().normalizePunctuation() }.findAll{ it.length() > 0 }.toList() out = out.size() <= 4 ? out : out.subList(0, 4) return out diff --git a/source/net/sourceforge/filebot/cli/ArgumentBean.java b/source/net/sourceforge/filebot/cli/ArgumentBean.java index 1ffcb8aa..699d2d5e 100644 --- a/source/net/sourceforge/filebot/cli/ArgumentBean.java +++ b/source/net/sourceforge/filebot/cli/ArgumentBean.java @@ -12,6 +12,8 @@ import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; +import net.sourceforge.filebot.Language; + import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; import org.kohsuke.args4j.spi.ExplicitBooleanOptionHandler; @@ -169,6 +171,10 @@ public class ArgumentBean { return new Locale(lang); } + public Language getLanguage() { + return Language.findLanguage(lang); + } + public Level getLogLevel() { return Level.parse(log.toUpperCase()); } diff --git a/source/net/sourceforge/filebot/ui/AbstractSearchPanel.java b/source/net/sourceforge/filebot/ui/AbstractSearchPanel.java index 911131b9..2e692fa8 100644 --- a/source/net/sourceforge/filebot/ui/AbstractSearchPanel.java +++ b/source/net/sourceforge/filebot/ui/AbstractSearchPanel.java @@ -8,10 +8,11 @@ import java.awt.Dimension; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.net.URI; import java.util.Arrays; import java.util.Collection; -import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -32,13 +33,11 @@ import javax.swing.event.ChangeListener; import net.miginfocom.swing.MigLayout; import net.sourceforge.filebot.ResourceManager; import net.sourceforge.filebot.Settings; -import net.sourceforge.filebot.similarity.SeriesNameMatcher; import net.sourceforge.filebot.web.SearchResult; import net.sourceforge.tuned.ExceptionUtilities; -import net.sourceforge.tuned.ListChangeSynchronizer; import net.sourceforge.tuned.ui.LabelProvider; +import net.sourceforge.tuned.ui.SelectButton; import ca.odell.glazedlists.BasicEventList; -import ca.odell.glazedlists.EventList; import ca.odell.glazedlists.matchers.TextMatcherEditor; import ca.odell.glazedlists.swing.AutoCompleteSupport; @@ -52,7 +51,7 @@ public abstract class AbstractSearchPanel extends JComponent { protected final SelectButtonTextField searchTextField = new SelectButtonTextField(); - protected final EventList searchHistory = createSearchHistory(); + protected final BasicEventList searchHistory = new BasicEventList(100000); public AbstractSearchPanel() { historyPanel.setColumnHeader(2, "Duration"); @@ -74,6 +73,35 @@ public abstract class AbstractSearchPanel extends JComponent { searchTextField.getSelectButton().setModel(Arrays.asList(getSearchEngines())); searchTextField.getSelectButton().setLabelProvider(getSearchEngineLabelProvider()); + searchTextField.getSelectButton().addPropertyChangeListener(SelectButton.SELECTED_VALUE, new PropertyChangeListener() { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + new SwingWorker, Void>() { + + private final S engine = searchTextField.getSelectButton().getSelectedValue(); + + @Override + protected Collection doInBackground() throws Exception { + return getHistory(engine); + } + + @Override + protected void done() { + if (engine == searchTextField.getSelectButton().getSelectedValue()) { + try { + searchHistory.clear(); + searchHistory.addAll(get()); + } catch (Exception e) { + Logger.getLogger(getClass().getName()).log(Level.WARNING, e.getMessage(), e); + } + } + + }; + }.execute(); + } + }); + try { // restore selected subtitle client searchTextField.getSelectButton().setSelectedIndex(Integer.parseInt(getSettings().get("engine.selected", "0"))); @@ -91,10 +119,18 @@ public abstract class AbstractSearchPanel extends JComponent { } }); - AutoCompleteSupport.install(searchTextField.getEditor(), searchHistory).setFilterMode(TextMatcherEditor.CONTAINS); + // high-performance auto-completion + AutoCompleteSupport acs = AutoCompleteSupport.install(searchTextField.getEditor(), searchHistory); + acs.setTextMatchingStrategy(TextMatcherEditor.IDENTICAL_STRATEGY); + acs.setFilterMode(TextMatcherEditor.CONTAINS); + acs.setCorrectsCase(true); + acs.setStrict(false); + installAction(this, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), searchAction); } + protected abstract Collection getHistory(S engine) throws Exception; + protected abstract S[] getSearchEngines(); protected abstract LabelProvider getSearchEngineLabelProvider(); @@ -118,23 +154,6 @@ public abstract class AbstractSearchPanel extends JComponent { new SearchTask(requestProcessor).execute(); } - protected EventList createSearchHistory() { - // create in-memory history - BasicEventList history = new BasicEventList(); - - // get the preferences node that contains the history entries - // and get a StringList that read and writes directly from and to the preferences - List persistentHistory = getSettings().node("history").asList(); - - // add history from the preferences to the current in-memory history (for completion) - history.addAll(persistentHistory); - - // perform all insert/add/remove operations on the in-memory history on the preferences node as well - ListChangeSynchronizer.syncEventListToList(history, persistentHistory); - - return history; - } - private final AbstractAction searchAction = new AbstractAction("Find", ResourceManager.getIcon("action.find")) { @Override @@ -200,12 +219,6 @@ public abstract class AbstractSearchPanel extends JComponent { // set search result requestProcessor.setSearchResult(selectedSearchResult); - String historyEntry = requestProcessor.getHistoryEntry(); - - if (historyEntry != null && !searchHistory.contains(historyEntry)) { - searchHistory.add(historyEntry); - } - tab.setTitle(requestProcessor.getTitle()); // fetch elements of the selected search result @@ -330,14 +343,6 @@ public abstract class AbstractSearchPanel extends JComponent { return request.getSearchText(); } - public String getHistoryEntry() { - SeriesNameMatcher nameMatcher = new SeriesNameMatcher(); - - // the common word sequence of query and search result - // common name will maintain the exact word characters (incl. case) of the first argument - return nameMatcher.matchByFirstCommonWordSequence(searchResult.getName(), request.getSearchText()); - } - public Icon getIcon() { return null; } @@ -362,7 +367,6 @@ public abstract class AbstractSearchPanel extends JComponent { public long getDuration() { return duration; } - } } diff --git a/source/net/sourceforge/filebot/ui/SelectButtonTextField.java b/source/net/sourceforge/filebot/ui/SelectButtonTextField.java index fd41c2a4..fc0dd642 100644 --- a/source/net/sourceforge/filebot/ui/SelectButtonTextField.java +++ b/source/net/sourceforge/filebot/ui/SelectButtonTextField.java @@ -1,7 +1,5 @@ - package net.sourceforge.filebot.ui; - import java.awt.Component; import java.awt.Rectangle; import java.awt.event.ActionEvent; @@ -34,185 +32,170 @@ import net.sourceforge.filebot.ResourceManager; import net.sourceforge.tuned.ui.SelectButton; import net.sourceforge.tuned.ui.TunedUtilities; - public class SelectButtonTextField extends JComponent { - + private SelectButton selectButton = new SelectButton(); - + private JComboBox editor = new JComboBox(); - - + public SelectButtonTextField() { selectButton.addActionListener(textFieldFocusOnClick); - + editor.setBorder(BorderFactory.createMatteBorder(1, 0, 1, 1, ((LineBorder) selectButton.getBorder()).getLineColor())); - + setLayout(new MigLayout("nogrid, fill")); add(selectButton, "h pref!, w pref!, sizegroupy this"); add(editor, "gap 0, w 195px!, sizegroupy this"); - + editor.setRenderer(new CompletionCellRenderer()); + editor.setPrototypeDisplayValue("X"); editor.setUI(new TextFieldComboBoxUI()); - + TunedUtilities.installAction(this, KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.CTRL_MASK), new SpinClientAction(-1)); TunedUtilities.installAction(this, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.CTRL_MASK), new SpinClientAction(1)); } - - + public String getText() { return ((TextFieldComboBoxUI) editor.getUI()).getEditor().getText(); } - - + public JComboBox getEditor() { return editor; } - - + public SelectButton getSelectButton() { return selectButton; } - + private final ActionListener textFieldFocusOnClick = new ActionListener() { - + @Override public void actionPerformed(ActionEvent e) { getEditor().requestFocus(); } - + }; - - + private class SpinClientAction extends AbstractAction { - + private int spin; - - + public SpinClientAction(int spin) { super(String.format("Spin%+d", spin)); this.spin = spin; } - - + @Override public void actionPerformed(ActionEvent e) { selectButton.spinValue(spin); } } - - + private class CompletionCellRenderer extends DefaultListCellRenderer { - + @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - + setBorder(new EmptyBorder(1, 4, 1, 4)); - + String highlightText = SelectButtonTextField.this.getText().substring(0, ((TextFieldComboBoxUI) editor.getUI()).getEditor().getSelectionStart()); - + // highlight the matching sequence Matcher matcher = Pattern.compile(highlightText, Pattern.LITERAL | Pattern.CASE_INSENSITIVE).matcher(value.toString()); - + // use no-break, because we really don't want line-wrapping in our table cells StringBuffer htmlText = new StringBuffer(""); - + if (matcher.find()) { matcher.appendReplacement(htmlText, "$0"); } - + matcher.appendTail(htmlText); - + htmlText.append(""); - + setText(htmlText.toString()); - + return this; } } - - + private class TextFieldComboBoxUI extends BasicComboBoxUI { - + @Override protected JButton createArrowButton() { return new JButton(ResourceManager.getIcon("action.list")); } - - + @Override public void configureArrowButton() { super.configureArrowButton(); - + arrowButton.setContentAreaFilled(false); arrowButton.setFocusable(false); } - - + @Override protected void configureEditor() { JTextComponent editor = getEditor(); - + editor.setEnabled(comboBox.isEnabled()); editor.setFocusable(comboBox.isFocusable()); editor.setFont(comboBox.getFont()); editor.setBorder(BorderFactory.createEmptyBorder(0, 3, 0, 3)); - + editor.addFocusListener(createFocusListener()); - + editor.getDocument().addDocumentListener(new DocumentListener() { - + @Override public void changedUpdate(DocumentEvent e) { popup.getList().repaint(); } - - + @Override public void insertUpdate(DocumentEvent e) { popup.getList().repaint(); } - - + @Override public void removeUpdate(DocumentEvent e) { popup.getList().repaint(); } - + }); + + popup.getList().setPrototypeCellValue("X"); } - - + public JTextComponent getEditor() { return (JTextComponent) editor; } - - + @Override protected ComboPopup createPopup() { return new BasicComboPopup(comboBox) { - + @Override public void show(Component invoker, int x, int y) { super.show(invoker, x - selectButton.getWidth(), y); } - - + @Override protected Rectangle computePopupBounds(int px, int py, int pw, int ph) { Rectangle bounds = super.computePopupBounds(px, py, pw, ph); bounds.width += selectButton.getWidth(); - + return bounds; } }; } - - + @Override protected FocusListener createFocusListener() { return new FocusHandler() { - + /** * Prevent action events from being fired on focusLost. */ @@ -224,7 +207,7 @@ public class SelectButtonTextField extends JComponent { } }; } - + } - + } diff --git a/source/net/sourceforge/filebot/ui/episodelist/EpisodeListPanel.java b/source/net/sourceforge/filebot/ui/episodelist/EpisodeListPanel.java index 955eee26..f295eef4 100644 --- a/source/net/sourceforge/filebot/ui/episodelist/EpisodeListPanel.java +++ b/source/net/sourceforge/filebot/ui/episodelist/EpisodeListPanel.java @@ -13,10 +13,12 @@ import java.beans.PropertyChangeListener; import java.io.File; import java.io.IOException; import java.net.URI; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Locale; +import java.util.TreeSet; import javax.swing.AbstractAction; import javax.swing.Icon; @@ -26,13 +28,15 @@ import javax.swing.JSpinner; import javax.swing.KeyStroke; import net.sourceforge.filebot.Analytics; +import net.sourceforge.filebot.Language; import net.sourceforge.filebot.Settings; import net.sourceforge.filebot.WebServices; +import net.sourceforge.filebot.media.MediaDetection; +import net.sourceforge.filebot.similarity.Normalization; import net.sourceforge.filebot.ui.AbstractSearchPanel; import net.sourceforge.filebot.ui.FileBotList; import net.sourceforge.filebot.ui.FileBotListExportHandler; import net.sourceforge.filebot.ui.FileBotTab; -import net.sourceforge.filebot.Language; import net.sourceforge.filebot.ui.LanguageComboBox; import net.sourceforge.filebot.ui.SelectDialog; import net.sourceforge.filebot.ui.transfer.ArrayTransferable; @@ -77,6 +81,18 @@ public class EpisodeListPanel extends AbstractSearchPanel getHistory(EpisodeListProvider engine) throws Exception { + final List names = new ArrayList(100000); + final SearchResult[] index = (engine == WebServices.AniDB) ? MediaDetection.releaseInfo.getAnidbIndex() : MediaDetection.releaseInfo.getTheTVDBIndex(); + for (SearchResult it : index) { + for (String n : it.getEffectiveNames()) { + names.add(Normalization.removeTrailingBrackets(n)); + } + } + return new TreeSet(names); + } + @Override protected EpisodeListProvider[] getSearchEngines() { return WebServices.getEpisodeListProviders(); diff --git a/source/net/sourceforge/filebot/ui/subtitle/SubtitlePanel.java b/source/net/sourceforge/filebot/ui/subtitle/SubtitlePanel.java index 6d330cf1..400ff625 100644 --- a/source/net/sourceforge/filebot/ui/subtitle/SubtitlePanel.java +++ b/source/net/sourceforge/filebot/ui/subtitle/SubtitlePanel.java @@ -17,6 +17,7 @@ import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.TreeSet; import java.util.logging.Level; import javax.swing.AbstractAction; @@ -32,13 +33,16 @@ import javax.swing.JTextField; import javax.swing.border.TitledBorder; import net.miginfocom.swing.MigLayout; +import net.sourceforge.filebot.Language; import net.sourceforge.filebot.ResourceManager; import net.sourceforge.filebot.Settings; import net.sourceforge.filebot.WebServices; +import net.sourceforge.filebot.media.MediaDetection; +import net.sourceforge.filebot.similarity.Normalization; import net.sourceforge.filebot.ui.AbstractSearchPanel; -import net.sourceforge.filebot.Language; import net.sourceforge.filebot.ui.LanguageComboBox; import net.sourceforge.filebot.ui.SelectDialog; +import net.sourceforge.filebot.web.Movie; import net.sourceforge.filebot.web.OpenSubtitlesClient; import net.sourceforge.filebot.web.SearchResult; import net.sourceforge.filebot.web.SubtitleDescriptor; @@ -138,6 +142,19 @@ public class SubtitlePanel extends AbstractSearchPanel getHistory(SubtitleProvider engine) throws Exception { + final List names = new ArrayList(200000); + for (Movie it : MediaDetection.releaseInfo.getMovieList()) { + names.addAll(it.getEffectiveNamesWithoutYear()); + } + for (SearchResult it : MediaDetection.releaseInfo.getTheTVDBIndex()) { + for (String n : it.getEffectiveNames()) { + names.add(Normalization.removeTrailingBrackets(n)); + } + } + return new TreeSet(names); + }; + @Override protected SubtitleProvider[] getSearchEngines() { return WebServices.getSubtitleProviders(); diff --git a/source/net/sourceforge/filebot/web/AnidbClient.java b/source/net/sourceforge/filebot/web/AnidbClient.java index ecfc9869..aa65e686 100644 --- a/source/net/sourceforge/filebot/web/AnidbClient.java +++ b/source/net/sourceforge/filebot/web/AnidbClient.java @@ -155,7 +155,7 @@ public class AnidbClient extends AbstractEpisodeListProvider { } /** - * This method is (and must be!) overridden by {@link WebServices.AnidbClientWithLocalSearch} to use our own anime index from sourceforge (as to not abuse anidb servers) + * This method is (and must be!) overridden by WebServices.AnidbClientWithLocalSearch to use our own anime index from sourceforge (as to not abuse anidb servers) */ public synchronized List getAnimeTitles() throws Exception { URL url = new URL("http", host, "/api/anime-titles.dat.gz"); @@ -176,6 +176,11 @@ public class AnidbClient extends AbstractEpisodeListProvider { languageOrder.add("en"); languageOrder.add("ja"); + List typeOrder = new ArrayList(); + typeOrder.add("1"); + typeOrder.add("4"); + typeOrder.add("2"); + // fetch data Map> entriesByAnime = new HashMap>(65536); @@ -190,7 +195,7 @@ public class AnidbClient extends AbstractEpisodeListProvider { String language = matcher.group(3); String title = matcher.group(4); - if (aid > 0 && title.length() > 0 && languageOrder.contains(language)) { + if (aid > 0 && title.length() > 0 && typeOrder.contains(type) && languageOrder.contains(language)) { List names = entriesByAnime.get(aid); if (names == null) { names = new ArrayList(); @@ -200,7 +205,7 @@ public class AnidbClient extends AbstractEpisodeListProvider { // resolve HTML entities title = Jsoup.parse(title).text(); - names.add(new Object[] { Integer.parseInt(type), languageOrder.indexOf(language), title }); + names.add(new Object[] { typeOrder.indexOf(type), languageOrder.indexOf(language), title }); } } } diff --git a/source/net/sourceforge/tuned/ui/SelectButton.java b/source/net/sourceforge/tuned/ui/SelectButton.java index 74b6c40d..33cb8f22 100644 --- a/source/net/sourceforge/tuned/ui/SelectButton.java +++ b/source/net/sourceforge/tuned/ui/SelectButton.java @@ -1,7 +1,5 @@ - package net.sourceforge.tuned.ui; - import java.awt.Color; import java.awt.Component; import java.awt.Dimension; @@ -29,82 +27,72 @@ import javax.swing.JPopupMenu; import javax.swing.SingleSelectionModel; import javax.swing.SwingConstants; - public class SelectButton extends JButton { - + public static final String SELECTED_VALUE = "selected value"; - + private final Color beginColor = new Color(0xF0EEE4); private final Color endColor = new Color(0xE0DED4); - + private final Color beginColorHover = beginColor; private final Color endColorHover = new Color(0xD8D7CD); - + private final SelectIcon selectIcon = new SelectIcon(); - + private List model = Collections.emptyList(); private SingleSelectionModel selectionModel = new DefaultSingleSelectionModel(); - + private LabelProvider labelProvider = new NullLabelProvider(); - + private boolean hover = false; - public SelectButton() { setContentAreaFilled(false); setFocusable(false); - + super.setIcon(selectIcon); - + setHorizontalAlignment(SwingConstants.CENTER); setVerticalAlignment(SwingConstants.CENTER); - + setBorder(BorderFactory.createLineBorder(new Color(0xA4A4A4), 1)); setPreferredSize(new Dimension(32, 22)); - + addActionListener(new OpenPopupOnClick()); } - public void setModel(Collection model) { this.model = new ArrayList(model); - setSelectedIndex(0); } - public LabelProvider getLabelProvider() { return labelProvider; } - public void setLabelProvider(LabelProvider labelProvider) { this.labelProvider = labelProvider; - + // update icon this.setIcon(labelProvider.getIcon(getSelectedValue())); } - @Override public void setIcon(Icon icon) { selectIcon.setInnerIcon(icon); repaint(); } - public void setSelectedValue(T value) { setSelectedIndex(model.indexOf(value)); } - public T getSelectedValue() { if (!selectionModel.isSelected()) return null; - + return model.get(selectionModel.getSelectedIndex()); } - public void setSelectedIndex(int i) { if (i < 0 || i >= model.size()) { @@ -112,171 +100,150 @@ public class SelectButton extends JButton { setIcon(null); return; } - - if (i != selectionModel.getSelectedIndex()) { - selectionModel.setSelectedIndex(i); - - T value = model.get(i); - - setIcon(labelProvider.getIcon(value)); - - firePropertyChange(SELECTED_VALUE, null, value); - } + + selectionModel.setSelectedIndex(i); + T value = model.get(i); + setIcon(labelProvider.getIcon(value)); + firePropertyChange(SELECTED_VALUE, null, value); } - public int getSelectedIndex() { return selectionModel.getSelectedIndex(); } - public SingleSelectionModel getSelectionModel() { return selectionModel; } - public void spinValue(int spin) { int size = model.size(); - + spin = spin % size; - + int next = getSelectedIndex() + spin; - + if (next < 0) next += size; else if (next >= size) next -= size; - + setSelectedIndex(next); } - @Override protected void paintComponent(Graphics g) { Graphics2D g2d = (Graphics2D) g; Rectangle bounds = new Rectangle(getSize()); - + if (hover) g2d.setPaint(GradientStyle.TOP_TO_BOTTOM.getGradientPaint(bounds, beginColorHover, endColorHover)); else g2d.setPaint(GradientStyle.TOP_TO_BOTTOM.getGradientPaint(bounds, beginColor, endColor)); - + g2d.fill(bounds); - + super.paintComponent(g); } - @Override protected void processMouseEvent(MouseEvent e) { switch (e.getID()) { - case MouseEvent.MOUSE_ENTERED: - hover = true; - repaint(); - break; - case MouseEvent.MOUSE_EXITED: - hover = false; - repaint(); - break; + case MouseEvent.MOUSE_ENTERED: + hover = true; + repaint(); + break; + case MouseEvent.MOUSE_EXITED: + hover = false; + repaint(); + break; } - + super.processMouseEvent(e); } - private class OpenPopupOnClick implements ActionListener { - + @Override public void actionPerformed(ActionEvent e) { JPopupMenu popup = new JPopupMenu(); - + for (T value : model) { SelectPopupMenuItem item = new SelectPopupMenuItem(labelProvider.getText(value), labelProvider.getIcon(value), value); - + if (value == getSelectedValue()) item.setSelected(true); - + popup.add(item); } - + popup.show(SelectButton.this, 0, getHeight() - 1); } } - private class SelectPopupMenuItem extends JMenuItem implements ActionListener { - + private final T value; - public SelectPopupMenuItem(String text, Icon icon, T value) { super(text, icon); - + this.value = value; - + setMargin(new Insets(3, 0, 3, 0)); addActionListener(this); } - @Override public void setSelected(boolean selected) { setFont(getFont().deriveFont(selected ? Font.BOLD : Font.PLAIN)); } - @Override public void actionPerformed(ActionEvent e) { setSelectedValue(value); } - + } - private static class SelectIcon implements Icon { - + private final GeneralPath arrow; - + private Icon icon; - public SelectIcon() { arrow = new GeneralPath(Path2D.WIND_EVEN_ODD, 3); int x = 25; int y = 10; - + arrow.moveTo(x - 2, y); arrow.lineTo(x, y + 3); arrow.lineTo(x + 3, y); arrow.lineTo(x - 2, y); } - public void setInnerIcon(Icon icon) { this.icon = icon; } - public void paintIcon(Component c, Graphics g, int x, int y) { Graphics2D g2d = (Graphics2D) g; - + if (icon != null) { icon.paintIcon(c, g2d, 4, 3); } - + g2d.setPaint(Color.BLACK); g2d.fill(arrow); } - public int getIconWidth() { return 30; } - public int getIconHeight() { return 20; } } - + } diff --git a/website/data/query-blacklist.txt b/website/data/query-blacklist.txt index f7c40f19..d17f9152 100644 --- a/website/data/query-blacklist.txt +++ b/website/data/query-blacklist.txt @@ -125,7 +125,7 @@ ^Video$ ^VIDEO_TS$ ^Videos$ -^volume[0-9]?$ +^volume\D?\d?$ ^Volumes$ ^watch$ ^www$